token = $token; $this->body = $body; $this->danger = $danger; $this->icon = $icon; // Localized fallbacks for any copy the opener did not pass. $this->heading = $heading !== '' ? $heading : __('modals.confirm_action.default_heading'); $this->confirmLabel = $confirmLabel !== '' ? $confirmLabel : __('common.confirm'); $this->notify = $notify ?? __('modals.confirm_action.default_notify'); } public static function modalMaxWidth(): string { return 'md'; } /** Audit-target line for the view — read from the signed token, not client state. */ #[Computed] public function targetLabel(): ?string { return ConfirmToken::peek($this->token)['auditTarget'] ?? null; } public function confirm(): void { try { // Flip the token pending → confirmed (single transition). This IS the // confirmation: an apply handler may only consume a confirmed token. $payload = ConfirmToken::confirm($this->token); } catch (InvalidConfirmToken) { // Forged / tampered / expired / already-confirmed — never audit or dispatch. $this->dispatch('notify', message: __('modals.confirm_action.rejected')); $this->closeModal(); return; } if (($payload['auditAction'] ?? '') !== '') { AuditEvent::create([ 'user_id' => Auth::id(), 'server_id' => $payload['serverId'] ?? null, 'actor' => Auth::user()?->name ?? 'system', 'action' => $payload['auditAction'], 'target' => $payload['auditTarget'] ?? null, 'ip' => request()->ip(), ]); } // Re-dispatch the sealed event, handing the token on so the apply handler can // burn it (single-use) and read the sealed params itself. $this->dispatch($payload['event'], confirmToken: $this->token); // Empty notify = the handler will report the real outcome itself. if ($this->notify !== '') { $this->dispatch('notify', message: $this->notify); } $this->closeModal(); } public function render() { return view('livewire.modals.confirm-action'); } }