From 973fcb3f2de966fa07c3302d7b9c13e5f3a82dd4 Mon Sep 17 00:00:00 2001 From: nexxo Date: Tue, 28 Jul 2026 19:34:27 +0200 Subject: [PATCH] Replace native confirm() dialogs with the app's own modal pattern --- CLAUDE.md | 55 +++++++++++++++ .../Admin/ConfirmDisableTwoFactor.php | 24 +++++++ app/Livewire/Admin/ConfirmForgetSecret.php | 39 +++++++++++ app/Livewire/Admin/ConfirmReissueVpnPeer.php | 39 +++++++++++ app/Livewire/Admin/ConfirmSaveSecret.php | 42 ++++++++++++ app/Livewire/Admin/Secrets.php | 19 ++++++ app/Livewire/Admin/TwoFactorSetup.php | 12 ++++ app/Livewire/Admin/Vpn.php | 10 +++ app/Livewire/ConfirmDisableTwoFactor.php | 24 +++++++ app/Livewire/ConfirmRevokeSeat.php | 43 ++++++++++++ app/Livewire/Settings.php | 12 ++++ app/Livewire/Users.php | 11 +++ lang/de/secrets.php | 13 +++- lang/de/settings.php | 7 +- lang/de/two_factor_setup.php | 6 +- lang/de/users.php | 7 +- lang/de/vpn.php | 7 +- lang/en/secrets.php | 12 +++- lang/en/settings.php | 7 +- lang/en/two_factor_setup.php | 7 +- lang/en/users.php | 7 +- lang/en/vpn.php | 7 +- .../confirm-disable-two-factor.blade.php | 17 +++++ .../admin/confirm-forget-secret.blade.php | 17 +++++ .../admin/confirm-reissue-vpn-peer.blade.php | 17 +++++ .../admin/confirm-save-secret.blade.php | 17 +++++ .../views/livewire/admin/secrets.blade.php | 10 ++- .../livewire/admin/two-factor-setup.blade.php | 5 +- resources/views/livewire/admin/vpn.blade.php | 4 +- .../confirm-disable-two-factor.blade.php | 17 +++++ .../livewire/confirm-revoke-seat.blade.php | 17 +++++ resources/views/livewire/settings.blade.php | 5 +- resources/views/livewire/users.blade.php | 4 +- .../Admin/OperatorTwoFactorEnrollmentTest.php | 48 ++++++++++++- tests/Feature/Admin/SecretsPageTest.php | 62 +++++++++++++++++ tests/Feature/Admin/VpnTest.php | 55 +++++++++++++++ tests/Feature/ConfirmInModalTest.php | 63 +++++++++++++++++ tests/Feature/CustomerTwoFactorTest.php | 51 +++++++++++++- tests/Feature/SeatsTest.php | 68 +++++++++++++++++++ 39 files changed, 856 insertions(+), 31 deletions(-) create mode 100644 app/Livewire/Admin/ConfirmDisableTwoFactor.php create mode 100644 app/Livewire/Admin/ConfirmForgetSecret.php create mode 100644 app/Livewire/Admin/ConfirmReissueVpnPeer.php create mode 100644 app/Livewire/Admin/ConfirmSaveSecret.php create mode 100644 app/Livewire/ConfirmDisableTwoFactor.php create mode 100644 app/Livewire/ConfirmRevokeSeat.php create mode 100644 resources/views/livewire/admin/confirm-disable-two-factor.blade.php create mode 100644 resources/views/livewire/admin/confirm-forget-secret.blade.php create mode 100644 resources/views/livewire/admin/confirm-reissue-vpn-peer.blade.php create mode 100644 resources/views/livewire/admin/confirm-save-secret.blade.php create mode 100644 resources/views/livewire/confirm-disable-two-factor.blade.php create mode 100644 resources/views/livewire/confirm-revoke-seat.blade.php create mode 100644 tests/Feature/ConfirmInModalTest.php diff --git a/CLAUDE.md b/CLAUDE.md index def703b..9be1550 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -212,3 +212,58 @@ Die Betreiber-Identität — eine Tabelle, ein Guard, eine Anmeldeseite — hat zehn Codex-Runden und mehrere Re-Reviews gebraucht. Die Funde waren echt, aber die Reihenfolge war falsch: Sicherheitsgrenzen zuerst, Randfälle in den Folgepunkt. Der Nutzer wartet auf ein Ergebnis, nicht auf ein Verfahren. + +--- + +## R23 — Bestätigung passiert im Modal, nie im nativen Browser-Dialog + +**Eine Aktion mit Folgen wird im Design dieses Produkts bestätigt — nicht in +einem Fenster, das der Browser zeichnet.** + +Verboten: + +1. **`wire:confirm`.** Das Attribut ruft `window.confirm()` auf, bevor die + Anfrage beim Server ankommt — eine Systemmeldung mit dem Hostnamen darin, + kein Bestandteil der Oberfläche, die dieses Produkt zeichnet, nicht + gestaltbar und nur so übersetzt, wie der Browser gerade eingestellt ist. +2. **`confirm(` aus eigenem JavaScript.** Derselbe native Dialog, nur ohne die + Livewire-Beschriftung. Ein Skript, das ihn direkt aufruft, hat dasselbe + Problem unter anderem Namen. + +### Warum das aufgeschrieben wurde + +Die Konsole fragte vor dem Übernehmen eines Zahlungs-Schlüssels „Auf +admin.clupilot.com wird Folgendes angezeigt: Diesen Schlüssel wirklich +übernehmen?" — ein Systemfenster mit der Domain darin, mitten im sonst +durchgestalteten Betreiber-Bereich. Sechs Stellen in beiden Bereichen taten +dasselbe: ein Schlüssel speichern oder entfernen, ein VPN-Zugang neu +ausgestellt, Zwei-Faktor entfernt (Konsole UND Portal, zwei getrennte +Identitäten nach R21), ein Benutzerzugang entzogen. + +### Wie es jetzt gebaut ist + +- Sechs eigene Bestätigungs-Modals nach dem Muster von `ConfirmRemoveHost` und + den anderen bestehenden Lösch-Bestätigungen — `App\Livewire\Admin\ + ConfirmSaveSecret`, `ConfirmForgetSecret`, `ConfirmReissueVpnPeer`, + `ConfirmDisableTwoFactor` in der Konsole; `App\Livewire\ + ConfirmDisableTwoFactor` und `ConfirmRevokeSeat` im Portal. Geöffnet über + `$dispatch('openModal', { component: '…', arguments: { … } })`, derselbe + Mechanismus, dieselbe Knopf-Reihenfolge (Abbrechen sekundär, Bestätigen + benannt statt „OK"). +- Das Modal mutiert nichts selbst. Der eigentliche Vorgang (`save()`, + `forget()`, `reissue()`, `disableTwoFactor()`, `revoke()`) bleibt + unverändert auf der Seiten-Komponente stehen, mit ihren eigenen + `guard()`-/Passwort-Prüfungen. Der Bestätigen-Knopf im Modal löst nur ein + Event aus (z. B. `secret-save-confirmed`), das die Seiten-Komponente per + `#[On(...)]` auffängt und an die bestehende Methode weiterreicht — das hält + die Berechtigungsprüfung an der einen Stelle, an der sie schon stand, statt + sie im Modal zu verdoppeln. +- Der bisherige Bestätigungssatz bleibt wortgleich als `_body` stehen; nur der + neue `_title` (und bei den Zwei-Faktor-Aktionen kein weiterer Text) kam + hinzu, `_confirm` heißt jetzt wie überall sonst im Repo die kurze + Knopfbeschriftung statt eines ganzen Satzes. + +### Erzwungen durch + +`tests/Feature/ConfirmInModalTest.php` — kein Blade-File im Repo darf +`wire:confirm` enthalten, und kein JavaScript darf `confirm(` aufrufen. diff --git a/app/Livewire/Admin/ConfirmDisableTwoFactor.php b/app/Livewire/Admin/ConfirmDisableTwoFactor.php new file mode 100644 index 0000000..a95db7d --- /dev/null +++ b/app/Livewire/Admin/ConfirmDisableTwoFactor.php @@ -0,0 +1,24 @@ +dispatch('two-factor-disable-confirmed'); + $this->closeModal(); + } + + public function render() + { + return view('livewire.admin.confirm-disable-two-factor'); + } +} diff --git a/app/Livewire/Admin/ConfirmForgetSecret.php b/app/Livewire/Admin/ConfirmForgetSecret.php new file mode 100644 index 0000000..e2113d9 --- /dev/null +++ b/app/Livewire/Admin/ConfirmForgetSecret.php @@ -0,0 +1,39 @@ +authorize('secrets.manage'); + abort_if(! array_key_exists($key, SecretVault::REGISTRY), 404); + + $this->key = $key; + $this->label = __(SecretVault::REGISTRY[$key]['label']); + } + + public function confirm(): void + { + $this->authorize('secrets.manage'); + $this->dispatch('secret-forget-confirmed', key: $this->key); + $this->closeModal(); + } + + public function render() + { + return view('livewire.admin.confirm-forget-secret'); + } +} diff --git a/app/Livewire/Admin/ConfirmReissueVpnPeer.php b/app/Livewire/Admin/ConfirmReissueVpnPeer.php new file mode 100644 index 0000000..7365b4f --- /dev/null +++ b/app/Livewire/Admin/ConfirmReissueVpnPeer.php @@ -0,0 +1,39 @@ +where('uuid', $uuid)->firstOrFail(); + $this->authorize('update', $peer); + abort_if($peer->kind !== VpnPeer::KIND_STAFF, 404); + + $this->uuid = $uuid; + $this->name = $peer->name; + } + + public function confirm(): void + { + $this->dispatch('vpn-peer-reissue-confirmed', uuid: $this->uuid); + $this->closeModal(); + } + + public function render() + { + return view('livewire.admin.confirm-reissue-vpn-peer'); + } +} diff --git a/app/Livewire/Admin/ConfirmSaveSecret.php b/app/Livewire/Admin/ConfirmSaveSecret.php new file mode 100644 index 0000000..3a896f5 --- /dev/null +++ b/app/Livewire/Admin/ConfirmSaveSecret.php @@ -0,0 +1,42 @@ +authorize('secrets.manage'); + abort_if(! array_key_exists($key, SecretVault::REGISTRY), 404); + + $this->key = $key; + $this->label = __(SecretVault::REGISTRY[$key]['label']); + } + + public function confirm(): void + { + $this->authorize('secrets.manage'); + $this->dispatch('secret-save-confirmed', key: $this->key); + $this->closeModal(); + } + + public function render() + { + return view('livewire.admin.confirm-save-secret'); + } +} diff --git a/app/Livewire/Admin/Secrets.php b/app/Livewire/Admin/Secrets.php index b90b01c..d3f435f 100644 --- a/app/Livewire/Admin/Secrets.php +++ b/app/Livewire/Admin/Secrets.php @@ -7,6 +7,7 @@ use App\Services\Secrets\SecretVault; use App\Services\Stripe\StripeCheck; use Illuminate\Support\Facades\Auth; use Livewire\Attributes\Layout; +use Livewire\Attributes\On; use Livewire\Component; use Throwable; @@ -81,6 +82,17 @@ class Secrets extends Component $this->dispatch('notify', message: __('secrets.saved')); } + /** + * The save button opens ConfirmSaveSecret instead of calling save() + * directly (R23) — that modal cannot see `entered` itself, so its confirm + * button dispatches back here rather than mutating anything on its own. + */ + #[On('secret-save-confirmed')] + public function onSaveConfirmed(string $key): void + { + $this->save($key); + } + public function forget(string $key): void { $this->guard(); @@ -90,6 +102,13 @@ class Secrets extends Component $this->dispatch('notify', message: __('secrets.removed')); } + /** See onSaveConfirmed() — same reasoning, for ConfirmForgetSecret. */ + #[On('secret-forget-confirmed')] + public function onForgetConfirmed(string $key): void + { + $this->forget($key); + } + /** * Try the key that is in force — or the one being typed, before storing it. * diff --git a/app/Livewire/Admin/TwoFactorSetup.php b/app/Livewire/Admin/TwoFactorSetup.php index 0bb2c97..e060baf 100644 --- a/app/Livewire/Admin/TwoFactorSetup.php +++ b/app/Livewire/Admin/TwoFactorSetup.php @@ -10,6 +10,7 @@ use Laravel\Fortify\Actions\DisableTwoFactorAuthentication; use Laravel\Fortify\Actions\EnableTwoFactorAuthentication; use Laravel\Fortify\Actions\GenerateNewRecoveryCodes; use Livewire\Attributes\Layout; +use Livewire\Attributes\On; use Livewire\Component; /** @@ -122,6 +123,17 @@ class TwoFactorSetup extends Component $this->dispatch('notify', message: __('two_factor_setup.off')); } + /** + * The disable button opens ConfirmDisableTwoFactor instead of calling + * disableTwoFactor() directly (R23); its confirm button dispatches back + * here, and disableTwoFactor() still runs its own password check. + */ + #[On('two-factor-disable-confirmed')] + public function onDisableConfirmed(): void + { + $this->disableTwoFactor(); + } + /** * Every two-factor action goes through this. * diff --git a/app/Livewire/Admin/Vpn.php b/app/Livewire/Admin/Vpn.php index 969c4eb..7484421 100644 --- a/app/Livewire/Admin/Vpn.php +++ b/app/Livewire/Admin/Vpn.php @@ -225,6 +225,16 @@ class Vpn extends Component $this->dispatch('notify', message: __('vpn.deleted')); } + /** + * The reissue button opens ConfirmReissueVpnPeer instead of calling + * reissue() directly (R23); its confirm button dispatches back here. + */ + #[On('vpn-peer-reissue-confirmed')] + public function onReissueConfirmed(string $uuid): void + { + $this->reissue($uuid); + } + /** * Replace an access's keypair. * diff --git a/app/Livewire/ConfirmDisableTwoFactor.php b/app/Livewire/ConfirmDisableTwoFactor.php new file mode 100644 index 0000000..a8ac2cf --- /dev/null +++ b/app/Livewire/ConfirmDisableTwoFactor.php @@ -0,0 +1,24 @@ +dispatch('twofa-disable-confirmed'); + $this->closeModal(); + } + + public function render() + { + return view('livewire.confirm-disable-two-factor'); + } +} diff --git a/app/Livewire/ConfirmRevokeSeat.php b/app/Livewire/ConfirmRevokeSeat.php new file mode 100644 index 0000000..0335ac4 --- /dev/null +++ b/app/Livewire/ConfirmRevokeSeat.php @@ -0,0 +1,43 @@ +customer()?->seats()->where('uuid', $uuid)->first(); + + // Mirrors the button's own visibility: the row never offers "revoke" + // for the owner seat, so a stale or forged uuid gets 404 here too. + abort_if($seat === null || $seat->role === 'owner', 404); + + $this->uuid = $uuid; + $this->name = (string) ($seat->name ?: $seat->email); + } + + public function confirm(): void + { + $this->dispatch('seat-revoke-confirmed', uuid: $this->uuid); + $this->closeModal(); + } + + public function render() + { + return view('livewire.confirm-revoke-seat'); + } +} diff --git a/app/Livewire/Settings.php b/app/Livewire/Settings.php index 01e0a02..a94504f 100644 --- a/app/Livewire/Settings.php +++ b/app/Livewire/Settings.php @@ -5,6 +5,7 @@ namespace App\Livewire; use App\Livewire\Concerns\ResolvesCustomer; use Illuminate\Support\Facades\Storage; use Livewire\Attributes\Layout; +use Livewire\Attributes\On; use Livewire\Attributes\Validate; use Livewire\Component; use Livewire\WithFileUploads; @@ -74,6 +75,17 @@ class Settings extends Component $this->dispatch('notify', message: __('settings.twofa_off')); } + /** + * The disable button opens ConfirmDisableTwoFactor instead of calling + * disableTwoFactor() directly (R23); its confirm button dispatches back + * here, and disableTwoFactor() still runs its own password check. + */ + #[On('twofa-disable-confirmed')] + public function onTwoFaDisableConfirmed(): void + { + $this->disableTwoFactor(); + } + /** * Every two-factor action goes through this. * diff --git a/app/Livewire/Users.php b/app/Livewire/Users.php index b6fa041..8e14415 100644 --- a/app/Livewire/Users.php +++ b/app/Livewire/Users.php @@ -8,6 +8,7 @@ use App\Models\Seat; use Illuminate\Database\UniqueConstraintViolationException; use Illuminate\Support\Facades\DB; use Livewire\Attributes\Layout; +use Livewire\Attributes\On; use Livewire\Attributes\Validate; use Livewire\Component; @@ -182,6 +183,16 @@ class Users extends Component } } + /** + * The revoke button opens ConfirmRevokeSeat instead of calling revoke() + * directly (R23); its confirm button dispatches back here. + */ + #[On('seat-revoke-confirmed')] + public function onRevokeConfirmed(string $uuid): void + { + $this->revoke($uuid); + } + public function resend(string $uuid): void { // Invite delivery is mocked for now. diff --git a/lang/de/secrets.php b/lang/de/secrets.php index a27cda0..096ea69 100644 --- a/lang/de/secrets.php +++ b/lang/de/secrets.php @@ -28,9 +28,18 @@ return [ 'test' => 'Verbindung testen', 'save' => 'Speichern', - 'save_confirm' => 'Diesen Schlüssel wirklich übernehmen? Er wirkt sofort — ein falscher Wert legt Zahlungen still.', + // Modal statt wire:confirm (R23): der Titel nennt, anders als der alte + // Fließtext, welcher der drei Schlüssel gemeint ist — sonst nicht zu + // unterscheiden. Der Fließtext selbst (body) ist unverändert der frühere + // Bestätigungssatz; _confirm ist jetzt die kurze Knopfbeschriftung, wie + // überall sonst im Repo. + 'save_title' => ':label wirklich übernehmen?', + 'save_body' => 'Er wirkt sofort — ein falscher Wert legt Zahlungen still.', + 'save_confirm' => 'Übernehmen', 'forget' => 'Hier entfernen', - 'forget_confirm' => 'Hinterlegten Wert entfernen? Danach gilt wieder der Wert aus der Serverdatei — falls dort einer steht.', + 'forget_title' => ':label hier entfernen?', + 'forget_body' => 'Danach gilt wieder der Wert aus der Serverdatei — falls dort einer steht.', + 'forget_confirm' => 'Entfernen', 'saved' => 'Gespeichert. Der neue Wert gilt ab sofort.', 'removed' => 'Entfernt. Es gilt wieder der Wert aus der Serverdatei.', 'empty' => 'Bitte einen Wert eingeben.', diff --git a/lang/de/settings.php b/lang/de/settings.php index cbcdfd7..0c07866 100644 --- a/lang/de/settings.php +++ b/lang/de/settings.php @@ -67,7 +67,12 @@ return [ 'twofa_code_wrong' => 'Dieser Code stimmt nicht.', 'twofa_on' => 'Zwei-Faktor-Anmeldung ist aktiv.', 'twofa_off' => 'Zwei-Faktor-Anmeldung wurde entfernt.', - 'twofa_off_confirm' => 'Zwei-Faktor-Anmeldung wirklich entfernen? Das Konto ist danach nur noch durch das Passwort geschützt.', + // Modal statt wire:confirm (R23): title+body derselbe frühere Satz, nur + // am Fragezeichen geteilt; twofa_off_confirm ist jetzt die kurze + // Knopfbeschriftung. + 'twofa_off_title' => 'Zwei-Faktor-Anmeldung wirklich entfernen?', + 'twofa_off_body' => 'Das Konto ist danach nur noch durch das Passwort geschützt.', + 'twofa_off_confirm' => 'Entfernen', 'twofa_disable' => 'Entfernen', 'twofa_new_codes' => 'Neue Wiederherstellungscodes', 'twofa_codes_title' => 'Wiederherstellungscodes', diff --git a/lang/de/two_factor_setup.php b/lang/de/two_factor_setup.php index 4e71e4e..b9e9b0b 100644 --- a/lang/de/two_factor_setup.php +++ b/lang/de/two_factor_setup.php @@ -14,7 +14,11 @@ return [ 'code_wrong' => 'Dieser Code stimmt nicht.', 'on' => 'Zwei-Faktor-Anmeldung ist aktiv.', 'off' => 'Zwei-Faktor-Anmeldung wurde entfernt.', - 'off_confirm' => 'Zwei-Faktor-Anmeldung wirklich entfernen? Ihr Konto ist danach nur noch durch das Passwort geschützt.', + // Modal statt wire:confirm (R23): title+body derselbe frühere Satz, nur + // am Fragezeichen geteilt; _confirm ist jetzt die kurze Knopfbeschriftung. + 'off_title' => 'Zwei-Faktor-Anmeldung wirklich entfernen?', + 'off_body' => 'Ihr Konto ist danach nur noch durch das Passwort geschützt.', + 'off_confirm' => 'Entfernen', 'disable' => 'Entfernen', 'new_codes' => 'Neue Wiederherstellungscodes', 'codes_title' => 'Wiederherstellungscodes', diff --git a/lang/de/users.php b/lang/de/users.php index 90122cd..f0719f0 100644 --- a/lang/de/users.php +++ b/lang/de/users.php @@ -50,7 +50,12 @@ return [ 'name_optional' => 'Name (optional)', // Steht statt eines leeren Feldes: der Inhaber behält seinen eigenen Zugang. 'owner_protected' => 'Geschützt', - 'revoke_confirm' => ':name entfernen? Der Zugang endet sofort.', + // Modal statt wire:confirm (R23): title+body derselbe frühere Satz, nur + // am Fragezeichen geteilt; revoke_confirm ist jetzt die kurze + // Knopfbeschriftung. + 'revoke_title' => ':name entfernen?', + 'revoke_body' => 'Der Zugang endet sofort.', + 'revoke_confirm' => 'Entfernen', 'resend' => 'Erneut senden', 'revoke' => 'Entfernen', diff --git a/lang/de/vpn.php b/lang/de/vpn.php index 1d49770..e00b40b 100644 --- a/lang/de/vpn.php +++ b/lang/de/vpn.php @@ -78,7 +78,12 @@ return [ 'no_stored_config' => 'Für diesen Zugang wurde die Konfiguration nicht gespeichert — der private Schlüssel existiert nur noch auf Ihrem Gerät. Über „Neu ausstellen" bekommen Sie eine neue.', 'reissue' => 'Neu ausstellen', - 'reissue_confirm' => 'Für :name ein neues Schlüsselpaar erzeugen? Die bisherige Konfiguration wird sofort ungültig.', + // Modal statt wire:confirm (R23): title+body sind derselbe frühere Satz, + // nur am Fragezeichen geteilt; _confirm ist jetzt die kurze + // Knopfbeschriftung, wie bei delete_confirm oben. + 'reissue_title' => 'Für :name ein neues Schlüsselpaar erzeugen?', + 'reissue_body' => 'Die bisherige Konfiguration wird sofort ungültig.', + 'reissue_confirm' => 'Neu ausstellen', 'reissued' => 'Neuer Schlüssel erzeugt — die alte Konfiguration gilt nicht mehr.', 'reissue_staff_only' => 'Host-Zugänge werden über die Host-Verwaltung erneuert, nicht hier.', diff --git a/lang/en/secrets.php b/lang/en/secrets.php index 0b964bd..f03264c 100644 --- a/lang/en/secrets.php +++ b/lang/en/secrets.php @@ -28,9 +28,17 @@ return [ 'test' => 'Test connection', 'save' => 'Save', - 'save_confirm' => 'Really apply this key? It takes effect immediately — a wrong value stops payments.', + // Modal instead of wire:confirm (R23): the title names which of the + // three keys is meant — the old sentence alone did not. The body is + // unchanged, the former confirmation sentence; _confirm is now the short + // confirm-button label, as everywhere else in the repo. + 'save_title' => 'Really apply :label?', + 'save_body' => 'It takes effect immediately — a wrong value stops payments.', + 'save_confirm' => 'Apply', 'forget' => 'Remove from here', - 'forget_confirm' => 'Remove the stored value? The server file applies again — if it has one.', + 'forget_title' => 'Remove :label from here?', + 'forget_body' => 'The server file applies again — if it has one.', + 'forget_confirm' => 'Remove', 'saved' => 'Saved. The new value applies immediately.', 'removed' => 'Removed. The server file applies again.', 'empty' => 'Enter a value.', diff --git a/lang/en/settings.php b/lang/en/settings.php index 2782d09..dd28239 100644 --- a/lang/en/settings.php +++ b/lang/en/settings.php @@ -67,7 +67,12 @@ return [ 'twofa_code_wrong' => 'That code is not right.', 'twofa_on' => 'Two-factor sign-in is active.', 'twofa_off' => 'Two-factor sign-in has been removed.', - 'twofa_off_confirm' => 'Really remove two-factor sign-in? The account will then be protected by its password alone.', + // Modal instead of wire:confirm (R23): title+body are the same former + // sentence, split at the full stop; twofa_off_confirm is now the short + // confirm-button label. + 'twofa_off_title' => 'Really remove two-factor sign-in?', + 'twofa_off_body' => 'The account will then be protected by its password alone.', + 'twofa_off_confirm' => 'Remove', 'twofa_disable' => 'Remove', 'twofa_new_codes' => 'New recovery codes', 'twofa_codes_title' => 'Recovery codes', diff --git a/lang/en/two_factor_setup.php b/lang/en/two_factor_setup.php index 7162c5c..27be514 100644 --- a/lang/en/two_factor_setup.php +++ b/lang/en/two_factor_setup.php @@ -14,7 +14,12 @@ return [ 'code_wrong' => 'That code is not correct.', 'on' => 'Two-factor login is active.', 'off' => 'Two-factor login was removed.', - 'off_confirm' => 'Really remove two-factor login? Your account is then protected by the password alone.', + // Modal instead of wire:confirm (R23): title+body are the same former + // sentence, split at the full stop; _confirm is now the short + // confirm-button label. + 'off_title' => 'Really remove two-factor login?', + 'off_body' => 'Your account is then protected by the password alone.', + 'off_confirm' => 'Remove', 'disable' => 'Remove', 'new_codes' => 'New recovery codes', 'codes_title' => 'Recovery codes', diff --git a/lang/en/users.php b/lang/en/users.php index 9b07a0d..7ddc885 100644 --- a/lang/en/users.php +++ b/lang/en/users.php @@ -50,7 +50,12 @@ return [ 'name_optional' => 'Name (optional)', // Shown instead of an empty cell: the owner keeps their own access. 'owner_protected' => 'Protected', - 'revoke_confirm' => 'Remove :name? Their access ends immediately.', + // Modal instead of wire:confirm (R23): title+body are the same former + // sentence, split at the full stop; revoke_confirm is now the short + // confirm-button label. + 'revoke_title' => 'Remove :name?', + 'revoke_body' => 'Their access ends immediately.', + 'revoke_confirm' => 'Remove', 'resend' => 'Resend', 'revoke' => 'Remove', diff --git a/lang/en/vpn.php b/lang/en/vpn.php index 8e82ef4..d3bcb13 100644 --- a/lang/en/vpn.php +++ b/lang/en/vpn.php @@ -78,7 +78,12 @@ return [ 'no_stored_config' => 'This access was created without storing its configuration — the private key only exists on your device. Use "Re-issue" to get a new one.', 'reissue' => 'Re-issue', - 'reissue_confirm' => 'Generate a new keypair for :name? The current configuration stops working immediately.', + // Modal instead of wire:confirm (R23): title+body are the same former + // sentence, split at the full stop; _confirm is now the short + // confirm-button label, like delete_confirm above. + 'reissue_title' => 'Generate a new keypair for :name?', + 'reissue_body' => 'The current configuration stops working immediately.', + 'reissue_confirm' => 'Re-issue', 'reissued' => 'New key issued — the old configuration no longer works.', 'reissue_staff_only' => 'Host accesses are renewed through host management, not here.', diff --git a/resources/views/livewire/admin/confirm-disable-two-factor.blade.php b/resources/views/livewire/admin/confirm-disable-two-factor.blade.php new file mode 100644 index 0000000..0788455 --- /dev/null +++ b/resources/views/livewire/admin/confirm-disable-two-factor.blade.php @@ -0,0 +1,17 @@ +
+
+ + + +
+

{{ __('two_factor_setup.off_title') }}

+

{{ __('two_factor_setup.off_body') }}

+
+
+
+ {{ __('common.cancel') }} + + {{ __('two_factor_setup.off_confirm') }} + +
+
diff --git a/resources/views/livewire/admin/confirm-forget-secret.blade.php b/resources/views/livewire/admin/confirm-forget-secret.blade.php new file mode 100644 index 0000000..5c465b9 --- /dev/null +++ b/resources/views/livewire/admin/confirm-forget-secret.blade.php @@ -0,0 +1,17 @@ +
+
+ + + +
+

{{ __('secrets.forget_title', ['label' => $label]) }}

+

{{ __('secrets.forget_body') }}

+
+
+
+ {{ __('common.cancel') }} + + {{ __('secrets.forget_confirm') }} + +
+
diff --git a/resources/views/livewire/admin/confirm-reissue-vpn-peer.blade.php b/resources/views/livewire/admin/confirm-reissue-vpn-peer.blade.php new file mode 100644 index 0000000..2e0e263 --- /dev/null +++ b/resources/views/livewire/admin/confirm-reissue-vpn-peer.blade.php @@ -0,0 +1,17 @@ +
+
+ + + +
+

{{ __('vpn.reissue_title', ['name' => $name]) }}

+

{{ __('vpn.reissue_body', ['name' => $name]) }}

+
+
+
+ {{ __('vpn.cancel') }} + + {{ __('vpn.reissue_confirm') }} + +
+
diff --git a/resources/views/livewire/admin/confirm-save-secret.blade.php b/resources/views/livewire/admin/confirm-save-secret.blade.php new file mode 100644 index 0000000..78b458b --- /dev/null +++ b/resources/views/livewire/admin/confirm-save-secret.blade.php @@ -0,0 +1,17 @@ +
+
+ + + +
+

{{ __('secrets.save_title', ['label' => $label]) }}

+

{{ __('secrets.save_body') }}

+
+
+
+ {{ __('common.cancel') }} + + {{ __('secrets.save_confirm') }} + +
+
diff --git a/resources/views/livewire/admin/secrets.blade.php b/resources/views/livewire/admin/secrets.blade.php index 5c258fd..54f0a59 100644 --- a/resources/views/livewire/admin/secrets.blade.php +++ b/resources/views/livewire/admin/secrets.blade.php @@ -81,16 +81,14 @@ @endif - + {{ __('secrets.save') }} @if ($entry['source'] === 'stored') - + {{ __('secrets.forget') }} @endif diff --git a/resources/views/livewire/admin/two-factor-setup.blade.php b/resources/views/livewire/admin/two-factor-setup.blade.php index 96689b9..ad34dfc 100644 --- a/resources/views/livewire/admin/two-factor-setup.blade.php +++ b/resources/views/livewire/admin/two-factor-setup.blade.php @@ -34,9 +34,8 @@ {{ __('two_factor_setup.new_codes') }} - + {{ __('two_factor_setup.disable') }} diff --git a/resources/views/livewire/admin/vpn.blade.php b/resources/views/livewire/admin/vpn.blade.php index 15d3e09..9cc0a83 100644 --- a/resources/views/livewire/admin/vpn.blade.php +++ b/resources/views/livewire/admin/vpn.blade.php @@ -167,8 +167,8 @@ @endcan @can('update', $peer) @if ($peer->kind === \App\Models\VpnPeer::KIND_STAFF) - -