Controller

B.M. Restu Pandhu P.

/ 19

1

AdminUserController

<?php

namespace App\Http\Controllers\Admin;

use App\Actions\User\CreateStaffAccountAction;
use App\Http\Controllers\Controller;
use App\Http\Requests\StoreStaffRequest;
use App\Models\User;
use Illuminate\Http\RedirectResponse;
use Illuminate\View\View;

class AdminUserController extends Controller
{
    public function index(): View
    {
        return view('admin.admins.index', [
            'admins' => User::query()->where('role', 'admin')->orderBy('name')->get(),
        ]);
    }

    public function store(StoreStaffRequest $request, CreateStaffAccountAction $action): RedirectResponse
    {
        $action->handle($request->validated(), 'admin');

        return back()->with('success', 'Akun admin dibuat. Email berisi password login sudah dikirim.');
    }

    public function update(StoreStaffRequest $request, User $admin): RedirectResponse
    {
        $admin->update($request->safe()->only(['name', 'username', 'email']));

        return back()->with('success', 'Data admin diperbarui.');
    }

    /**
     * Toggle active/inactive, guarding against deactivating the last active
     * admin (the system would then have no one left to manage it).
     */
    public function toggleActive(User $admin): RedirectResponse
    {
        if ($admin->is_active) {
            $otherActiveAdmins = User::query()
                ->where('role', 'admin')
                ->where('is_active', true)
                ->where('id', '!=', $admin->id)
                ->exists();

            if (! $otherActiveAdmins) {
                return back()->with('error', 'Tidak bisa menonaktifkan admin aktif terakhir.');
            }
        }

        $admin->update(['is_active' => ! $admin->is_active]);

        return back()->with('success', $admin->is_active ? 'Akun admin diaktifkan.' : 'Akun admin dinonaktifkan.');
    }
}

ApplicationController

<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Http\Requests\StoreApplicationRequest;
use App\Models\Application;
use App\Models\Client;
use App\Models\User;
use Illuminate\Http\RedirectResponse;
use Illuminate\View\View;

class ApplicationController extends Controller
{
    public function index(): View
    {
        return view('admin.applications.index', [
            'applications' => Application::query()->with(['client', 'dedicatedTechnician'])->orderBy('name')->get(),
            'clients' => Client::query()->orderBy('name')->get(),
            'technicians' => User::query()->where('role', 'technician')->where('is_active', true)->orderBy('name')->get(),
        ]);
    }

    public function store(StoreApplicationRequest $request): RedirectResponse
    {
        Application::create($request->validated() + ['is_active' => true]);

        return back()->with('success', 'Aplikasi baru ditambahkan.');
    }

    public function update(StoreApplicationRequest $request, Application $application): RedirectResponse
    {
        $application->update($request->validated());

        return back()->with('success', 'Data aplikasi diperbarui.');
    }

    /**
     * Deactivate instead of delete: past complaints tied to this application
     * must stay intact (same reasoning as Client::toggleActive, §11/§12).
     */
    public function toggleActive(Application $application): RedirectResponse
    {
        $application->update(['is_active' => ! $application->is_active]);

        return back()->with('success', $application->is_active ? 'Aplikasi diaktifkan.' : 'Aplikasi dinonaktifkan.');
    }
}

ClientController

<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Http\Requests\StoreClientRequest;
use App\Models\Client;
use Illuminate\Http\RedirectResponse;
use Illuminate\View\View;

class ClientController extends Controller
{
    public function index(): View
    {
        return view('admin.clients.index', [
            'clients' => Client::query()->orderBy('name')->get(),
        ]);
    }

    public function store(StoreClientRequest $request): RedirectResponse
    {
        $data = $request->safe()->except('logo');

        if ($request->hasFile('logo')) {
            $data['logo'] = $request->file('logo')->store('client-logos', 'public');
        }

        Client::create($data + ['is_active' => true]);

        return back()->with('success', 'Instansi baru ditambahkan.');
    }

    public function update(StoreClientRequest $request, Client $client): RedirectResponse
    {
        $data = $request->safe()->except('logo');

        if ($request->hasFile('logo')) {
            $data['logo'] = $request->file('logo')->store('client-logos', 'public');
        }

        $client->update($data);

        return back()->with('success', 'Data instansi diperbarui.');
    }

    /**
     * Deactivate instead of delete: past complaints tied to this client must
     * stay intact for the monthly recap (§14), so we just hide it from the
     * public dropdown (§11).
     */
    public function toggleActive(Client $client): RedirectResponse
    {
        $client->update(['is_active' => ! $client->is_active]);

        return back()->with('success', $client->is_active ? 'Instansi diaktifkan.' : 'Instansi dinonaktifkan.');
    }
}

ComplaintController

<?php

namespace App\Http\Controllers\Admin;

use App\Actions\Complaint\CloseComplaintAction;
use App\Http\Controllers\Controller;
use App\Http\Requests\CloseComplaintRequest;
use App\Models\Complaint;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;

class ComplaintController extends Controller
{
    public function index(Request $request): View
    {
        $complaints = Complaint::query()
            ->with(['client', 'assignedTechnician'])
            ->when($request->filled('status'), fn ($q) => $q->where('status', $request->string('status')))
            ->latest('report_date')
            ->paginate(20)
            ->withQueryString();

        return view('admin.complaints.index', [
            'complaints' => $complaints,
            'statusFilter' => $request->string('status')->value(),
        ]);
    }

    public function show(Complaint $complaint): View
    {
        $complaint->load(['client', 'application.dedicatedTechnician', 'assignedTechnician', 'responses.technician', 'activities.causer']);

        return view('admin.complaints.show', ['complaint' => $complaint]);
    }

    /**
     * Admin sets the invoice price for a resolved ticket and closes it.
     * There is no manual "assign to technician" step anymore — technicians
     * claim open tickets themselves from their own dashboard (§4).
     */
    public function close(CloseComplaintRequest $request, Complaint $complaint, CloseComplaintAction $action): RedirectResponse
    {
        $action->handle($complaint, $request->user(), $request->validated('price'));

        return back()->with('success', 'Tagihan disimpan, tiket ditutup.');
    }
}

DashboardController

<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\Complaint;
use Illuminate\View\View;

class DashboardController extends Controller
{
    public function index(): View
    {
        $statusCounts = Complaint::query()
            ->selectRaw('status, count(*) as total')
            ->groupBy('status')
            ->pluck('total', 'status');

        $overdue = Complaint::query()
            ->whereIn('status', ['open', 'in_progress'])
            ->where(function ($query) {
                $query->where(fn ($q) => $q->where('priority', 'urgent')->where('report_date', '<=', now()->subHours(4)))
                    ->orWhere(fn ($q) => $q->where('priority', 'normal')->where('report_date', '<=', now()->subHours(48)));
            })
            ->count();

        return view('admin.dashboard', [
            'statusCounts' => $statusCounts,
            'overdueCount' => $overdue,
            'recentComplaints' => Complaint::query()->with('client')->latest('report_date')->take(10)->get(),
            // "open" always means unclaimed now — technicians self-claim, admin no longer pushes assignments (§4).
            'unclaimedComplaints' => Complaint::query()->where('status', 'open')->count(),
        ]);
    }
}

RecapController

<?php

namespace App\Http\Controllers\Admin;

use App\Actions\Recap\GenerateVerificationQrCode;
use App\Actions\Recap\StoreRecapReport;
use App\Http\Controllers\Controller;
use App\Models\Client;
use App\Models\Complaint;
use Barryvdh\DomPDF\Facade\Pdf;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\View\View;

class RecapController extends Controller
{
    public function index(): View
    {
        return view('admin.recap.index', [
            'clients' => Client::query()->orderBy('name')->get(),
        ]);
    }

    /**
     * Monthly recap per client. Price is set by admin per ticket when it's
     * closed (§14/§16) — only closed tickets have a price and count toward
     * the billable total; resolved-but-not-yet-invoiced tickets are shown
     * separately as still pending.
     */
    public function show(
        Request $request,
        GenerateVerificationQrCode $qrCode,
        StoreRecapReport $storeRecapReport,
    ): Response {
        $request->validate([
            'client_id' => ['required', 'uuid', 'exists:client,id'],
            'month' => ['required', 'integer', 'between:1,12'],
            'year' => ['required', 'integer', 'min:2020'],
        ]);

        $client = Client::findOrFail($request->string('client_id'));
        $month = $request->integer('month');
        $year = $request->integer('year');

        $complaints = Complaint::query()
            ->where('client_id', $client->id)
            ->whereYear('report_date', $year)
            ->whereMonth('report_date', $month)
            ->with('assignedTechnician')
            ->orderBy('report_date')
            ->get();

        $billable = $complaints->where('status', 'closed');
        $report = $storeRecapReport->handle($client, $month, $year, $complaints);

        $pdf = Pdf::loadView('admin.recap.pdf', [
            'client' => $client,
            'complaints' => $complaints,
            'billableTotal' => $billable->sum('price'),
            'billableCount' => $billable->count(),
            'month' => $month,
            'year' => $year,
            'report' => $report,
            'verificationQrCode' => $qrCode->handle(route('recap.verify', $report->token)),
        ])->setPaper('a4');

        $filename = "rekap-{$client->name}-{$month}-{$year}.pdf";

        return $pdf->stream($filename);
    }
}

SettingController

<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Http\Requests\UpdateConfigRequest;
use App\Models\Config;
use Illuminate\Http\RedirectResponse;
use Illuminate\View\View;

class SettingController extends Controller
{
    /**
     * Identitas perusahaan/instansi pengelola helpdesk ini — dipakai di
     * letterhead login, footer form pengaduan, dan header cetak rekap.
     * Selalu tepat satu baris (singleton), tidak ada create/delete.
     */
    public function edit(): View
    {
        return view('admin.settings.edit', [
            'config' => Config::query()->firstOrNew(),
        ]);
    }

    public function update(UpdateConfigRequest $request): RedirectResponse
    {
        $config = Config::query()->first();
        $data = $request->safe()->except('company_logo');

        if ($request->hasFile('company_logo')) {
            $data['company_logo'] = $request->file('company_logo')->store('config-logo', 'public');
        }

        if ($config) {
            $config->update($data);
        } else {
            Config::create($data);
        }

        return back()->with('success', 'Pengaturan berhasil disimpan.');
    }
}

TechnicianController

<?php

namespace App\Http\Controllers\Admin;

use App\Actions\User\CreateStaffAccountAction;
use App\Http\Controllers\Controller;
use App\Http\Requests\StoreStaffRequest;
use App\Models\User;
use Illuminate\Http\RedirectResponse;
use Illuminate\View\View;

class TechnicianController extends Controller
{
    public function index(): View
    {
        return view('admin.technicians.index', [
            'technicians' => User::query()->where('role', 'technician')->orderBy('name')->get(),
        ]);
    }

    public function store(StoreStaffRequest $request, CreateStaffAccountAction $action): RedirectResponse
    {
        $action->handle($request->validated(), 'technician');

        return back()->with('success', 'Akun teknisi dibuat. Email berisi password login sudah dikirim.');
    }

    public function update(StoreStaffRequest $request, User $technician): RedirectResponse
    {
        $technician->update($request->safe()->only(['name', 'username', 'email']));

        return back()->with('success', 'Data teknisi diperbarui.');
    }

    /**
     * Toggle active/inactive instead of a hard delete, so response/activity
     * history stays intact (see docs/ALUR_APLIKASI.md §12).
     */
    public function toggleActive(User $technician): RedirectResponse
    {
        $technician->update(['is_active' => ! $technician->is_active]);

        return back()->with('success', $technician->is_active ? 'Akun teknisi diaktifkan.' : 'Akun teknisi dinonaktifkan.');
    }
}

2

ForceChangePasswordController

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\Rules\Password;
use Illuminate\View\View;

class ForceChangePasswordController extends Controller
{
    /**
     * Shown right after login when the account still carries the
     * system-generated password from account creation (see §3.3/§3.4).
     */
    public function create(Request $request): View|RedirectResponse
    {
        if (! $request->user()->password_must_change) {
            return redirect()->route('login.redirect');
        }

        return view('auth.force-change-password');
    }

    public function store(Request $request): RedirectResponse
    {
        $request->validate([
            'password' => ['required', 'confirmed', Password::min(8)],
        ]);

        $request->user()->update([
            'password' => $request->string('password')->value(),
            'password_must_change' => false,
        ]);

        return redirect()->route('login.redirect');
    }
}

ForgotPasswordController

<?php

namespace App\Http\Controllers\Auth;

use App\Actions\Auth\ResetPasswordAction;
use App\Actions\Auth\SendPasswordResetCodeAction;
use App\Http\Controllers\Controller;
use App\Http\Requests\ResetPasswordRequest;
use App\Http\Requests\SendPasswordResetCodeRequest;
use App\Http\Requests\VerifyPasswordResetCodeRequest;
use App\Models\Config;
use App\Models\User;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\View\View;

class ForgotPasswordController extends Controller
{
    /**
     * Step 1 — masukkan email.
     */
    public function showEmailForm(): View
    {
        return view('auth.forgot-password', ['company' => Config::query()->first()]);
    }

    public function sendCode(SendPasswordResetCodeRequest $request, SendPasswordResetCodeAction $action): RedirectResponse
    {
        $user = User::query()->where('email', $request->validated('email'))->where('is_active', true)->first();

        if (! $user) {
            return back()->withInput()->withErrors(['email' => 'Email tidak ditemukan atau akun sudah dinonaktifkan.']);
        }

        $action->handle($user);

        session([
            'password_reset_user_id' => $user->id,
            'password_reset_email' => $user->email,
        ]);

        return redirect()->route('password.forgot.verify')->with('success', 'Kode verifikasi terkirim ke email.');
    }

    /**
     * Step 2 — masukkan kode OTP 6 digit dari email.
     */
    public function showVerifyForm(): View|RedirectResponse
    {
        $user = $this->userFromSession();
        if (! $user) {
            return redirect()->route('password.forgot');
        }

        return view('auth.forgot-password-verify', ['user' => $user, 'company' => Config::query()->first()]);
    }

    public function verifyCode(VerifyPasswordResetCodeRequest $request): RedirectResponse
    {
        $user = $this->userFromSession();
        if (! $user) {
            return redirect()->route('password.forgot');
        }

        $throttleKey = 'password-reset-verify:'.$user->id;
        if (RateLimiter::tooManyAttempts($throttleKey, 5)) {
            return back()->withErrors(['code' => 'Terlalu banyak percobaan. Minta kode baru lewat "Kirim Ulang Kode".']);
        }

        if ($user->isPasswordResetCodeExpired()) {
            return back()->withErrors(['code' => 'Kode verifikasi telah kadaluarsa. Silakan kirim ulang kode.']);
        }

        if ($user->password_reset_code !== $request->validated('code')) {
            RateLimiter::hit($throttleKey, 600);

            return back()->withErrors(['code' => 'Kode verifikasi salah. Silakan coba lagi.']);
        }

        RateLimiter::clear($throttleKey);
        session(['password_reset_verified' => true]);

        return redirect()->route('password.forgot.reset')->with('success', 'Kode terverifikasi. Silakan buat password baru.');
    }

    public function resend(SendPasswordResetCodeAction $action): RedirectResponse
    {
        $user = $this->userFromSession();
        if (! $user) {
            return redirect()->route('password.forgot');
        }

        $action->handle($user, forceNew: true);

        return back()->with('success', 'Kode verifikasi baru terkirim.');
    }

    /**
     * Step 3 — password baru + konfirmasi.
     */
    public function showResetForm(): View|RedirectResponse
    {
        $user = $this->userFromSession();
        if (! $user || ! session('password_reset_verified')) {
            return redirect()->route('password.forgot');
        }

        return view('auth.forgot-password-reset', ['company' => Config::query()->first()]);
    }

    public function resetPassword(ResetPasswordRequest $request, ResetPasswordAction $action): RedirectResponse
    {
        $user = $this->userFromSession();
        if (! $user || ! session('password_reset_verified')) {
            return redirect()->route('password.forgot');
        }

        $action->handle($user, $request->validated('password'));

        session()->forget(['password_reset_user_id', 'password_reset_email', 'password_reset_verified']);

        return redirect()->route('login')->with('success', 'Password berhasil diatur ulang. Silakan masuk dengan password baru Anda.');
    }

    private function userFromSession(): ?User
    {
        $id = session('password_reset_user_id');

        return $id ? User::query()->where('is_active', true)->find($id) : null;
    }
}

LoginController

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\Http\Requests\LoginRequest;
use App\Models\Config;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\View\View;

class LoginController extends Controller
{
    /**
     * There is no registration flow: admin and technician accounts are only
     * created by an admin (see Admin\TechnicianController / AdminUserController).
     */
    public function create(): View
    {
        return view('auth.login', [
            'company' => Config::query()->first(),
        ]);
    }

    public function store(LoginRequest $request): RedirectResponse
    {
        $request->authenticate();
        $request->session()->regenerate();

        $user = Auth::user();

        if ($user->password_must_change) {
            return redirect()->route('password.force-change');
        }

        return redirect()->intended($user->isAdmin() ? route('admin.dashboard') : route('technician.tickets.index'));
    }

    public function destroy(Request $request): RedirectResponse
    {
        Auth::logout();
        $request->session()->invalidate();
        $request->session()->regenerateToken();

        return redirect()->route('login');
    }
}

3

TicketController

<?php

namespace App\Http\Controllers\Technician;

use App\Actions\Complaint\AddProgressNoteAction;
use App\Actions\Complaint\ClaimComplaintAction;
use App\Actions\Complaint\ReleaseComplaintAction;
use App\Actions\Complaint\ResolveComplaintAction;
use App\Http\Controllers\Controller;
use App\Http\Requests\ResolveComplaintRequest;
use App\Models\Complaint;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;

class TicketController extends Controller
{
    /**
     * Technician dashboard: the open pool they're allowed to claim, plus
     * their own already-claimed tickets. There is no admin push-assign step
     * anymore — technicians pick tickets up themselves (§4).
     */
    public function index(Request $request): View
    {
        $technician = $request->user();

        $availableTickets = Complaint::query()
            ->with(['client', 'application'])
            ->where('status', 'open')
            ->where(function ($query) use ($technician) {
                // Include tickets with no application, or with an application
                // that isn't locked to a different specialist (§12).
                $query->whereDoesntHave('application', function ($appQuery) use ($technician) {
                    $appQuery->whereNotNull('dedicated_technician_id')
                        ->where('dedicated_technician_id', '!=', $technician->id);
                });
            })
            ->latest('report_date')
            ->get();

        $myTickets = Complaint::query()
            ->with('client')
            ->where('assigned_technician_id', $technician->id)
            ->latest('report_date')
            ->paginate(20);

        return view('technician.tickets.index', [
            'availableTickets' => $availableTickets,
            'myTickets' => $myTickets,
        ]);
    }

    public function show(Request $request, Complaint $complaint): View
    {
        $technician = $request->user();
        $isMine = $complaint->assigned_technician_id === $technician->id;

        abort_unless($isMine || $complaint->status === 'open', 403);

        $complaint->load(['client', 'application', 'responses', 'activities.causer']);

        return view('technician.tickets.show', [
            'complaint' => $complaint,
            'isMine' => $isMine,
        ]);
    }

    public function claim(Request $request, Complaint $complaint, ClaimComplaintAction $action): RedirectResponse
    {
        try {
            $action->handle($complaint, $request->user());
        } catch (\DomainException $e) {
            return back()->with('error', $e->getMessage());
        }

        return redirect()->route('technician.tickets.show', $complaint)->with('success', "Tiket {$complaint->ticket_number} berhasil diambil.");
    }

    public function release(Request $request, Complaint $complaint, ReleaseComplaintAction $action): RedirectResponse
    {
        try {
            $action->handle($complaint, $request->user());
        } catch (\DomainException $e) {
            return back()->with('error', $e->getMessage());
        }

        return redirect()->route('technician.tickets.index')->with('success', "Tiket {$complaint->ticket_number} dilepas kembali ke antrian.");
    }

    /**
     * Technician logs a progress update without resolving the ticket —
     * a ticket can collect several of these across days before it's done.
     */
    public function progress(ResolveComplaintRequest $request, Complaint $complaint, AddProgressNoteAction $action): RedirectResponse
    {
        try {
            $action->handle($complaint, $request->user(), $request->validated('respons'), $request->file('photos', []));
        } catch (\DomainException $e) {
            return back()->with('error', $e->getMessage());
        }

        return redirect()->route('technician.tickets.show', $complaint)->with('success', 'Update progres berhasil disimpan.');
    }

    /**
     * Technician submits their final response; complaint moves to resolved.
     */
    public function resolve(ResolveComplaintRequest $request, Complaint $complaint, ResolveComplaintAction $action): RedirectResponse
    {
        $action->handle($complaint, $request->user(), $request->validated('respons'), $request->file('photos', []));

        return redirect()->route('technician.tickets.index')->with('success', "Tiket {$complaint->ticket_number} diselesaikan.");
    }
}

4

ComplaintController

<?php

namespace App\Http\Controllers;

use App\Actions\Complaint\CreateComplaintAction;
use App\Http\Requests\StoreComplaintRequest;
use App\Models\Application;
use App\Models\Client;
use App\Models\Complaint;
use App\Models\Config;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;

class ComplaintController extends Controller
{
    /**
     * GET /complaint — public form, no login required.
     */
    public function create(): View
    {
        return view('complaint.create', [
            'clients' => Client::query()->where('is_active', true)->orderBy('name')->get(),
            // Every active application for every active client, sent once as
            // JSON and filtered client-side by the picked instansi — no extra
            // request needed to reveal the Aplikasi dropdown (§2).
            'applications' => Application::query()->where('is_active', true)->orderBy('name')->get(['id', 'client_id', 'name']),
            'company' => Config::query()->first(),
        ]);
    }

    /**
     * POST /complaint — submit the form.
     */
    public function store(StoreComplaintRequest $request, CreateComplaintAction $action): RedirectResponse
    {
        $complaint = $action->handle(
            $request->safe()->only(['client_id', 'application_id', 'subject', 'description', 'report_by'])
                + ['priority' => $request->boolean('is_urgent') ? 'urgent' : 'normal'],
            $request->file('photos', []),
        );

        return redirect()
            ->route('complaint.create')
            ->with('ticket_number', $complaint->ticket_number);
    }

    /**
     * GET /complaint/track — read-only status lookup by ticket number, no login.
     */
    public function trackForm(): View
    {
        return view('complaint.track', ['company' => Config::query()->first()]);
    }

    public function track(Request $request): View
    {
        $request->validate(['ticket_number' => ['required', 'string']]);

        $complaint = Complaint::query()
            ->with(['client', 'application', 'responses' => fn ($q) => $q->latest('respons_date')])
            ->where('ticket_number', $request->string('ticket_number'))
            ->first();

        return view('complaint.track', [
            'complaint' => $complaint,
            'searched' => true,
            'company' => Config::query()->first(),
        ]);
    }
}

LegalController

<?php

namespace App\Http\Controllers;

use App\Models\Config;
use Illuminate\View\View;

class LegalController extends Controller
{
    public function privacy(): View
    {
        return view('legal.privacy', ['company' => Config::query()->first()]);
    }

    public function terms(): View
    {
        return view('legal.terms', ['company' => Config::query()->first()]);
    }
}

ReportVerificationController

<?php

namespace App\Http\Controllers;

use App\Models\RecapReport;
use Illuminate\View\View;

class ReportVerificationController extends Controller
{
    /**
     * Public page a "Nomor : ..." letter's QR code links to. Only a real
     * RecapReport row (created the moment its PDF was first generated) has
     * a matching token, so finding one is itself the proof this letter was
     * issued by this system and not forged/altered. Everything shown is the
     * snapshot from that moment, not live data.
     */
    public function show(string $token): View
    {
        $report = RecapReport::query()->where('token', $token)->first();

        if (! $report) {
            return view('recap.verify-invalid');
        }

        return view('recap.verify-valid', ['report' => $report]);
    }
}

Komentar

Belum ada komentar.