fix(settings): confirm VAPID regenerate in-app; let labs subtitle wrap

The VAPID regenerate overwrite guard used a native window.confirm for a
subscription-invalidating destructive action, where the rest of the
release uses the project's AlertDialog. Drive the overwrite confirm
through an AlertDialog (calm in-app dialog, destructive action variant)
and retry with force from its action.

The /labs header subtitle used truncate, clipping mid-word in longer
locales on a 375px viewport. Drop truncate so it wraps; the add button
stays shrink-0 and the row aligns to the top.
This commit is contained in:
Marc-André Bombeck committed 2026-06-15 01:28:12 +02:00
1 parent 790fecddb8
commit b336ed5b73
8 files changed
+58 -8

No files matched your search

+2
View File
@@ -4932,6 +4932,8 @@
"webPushVapidGenerated": "VAPID-Schlüssel erzeugt. Betreff speichern, um abzuschließen.",
"webPushVapidGenerateFailed": "VAPID-Schlüssel konnten nicht erzeugt werden.",
"webPushVapidGenerateConfirm": "VAPID-Schlüssel sind bereits konfiguriert. Ein neues Schlüsselpaar macht alle Browser-Push-Abos ungültig — jedes Gerät muss sich neu anmelden. Fortfahren?",
"webPushVapidGenerateConfirmTitle": "VAPID-Schlüssel neu erzeugen?",
"webPushVapidRegenerate": "Neu erzeugen",
"configured": "Konfiguriert",
"monitoringTestSuccess": "Testevent wurde gesendet",
"monitoringTestFailed": "Monitoring-Test fehlgeschlagen",
+2
View File
@@ -4932,6 +4932,8 @@
"webPushVapidGenerated": "VAPID keys generated. Save the subject to finish.",
"webPushVapidGenerateFailed": "Could not generate VAPID keys.",
"webPushVapidGenerateConfirm": "VAPID keys are already configured. Generating a new pair invalidates every browser push subscription — each device must re-subscribe. Continue?",
"webPushVapidGenerateConfirmTitle": "Regenerate VAPID keys?",
"webPushVapidRegenerate": "Regenerate",
"configured": "Configured",
"monitoringTestSuccess": "Test event sent",
"monitoringTestFailed": "Monitoring test failed",
+2
View File
@@ -4932,6 +4932,8 @@
"webPushVapidGenerated": "Claves VAPID generadas. Guarda el asunto para terminar.",
"webPushVapidGenerateFailed": "No se pudieron generar las claves VAPID.",
"webPushVapidGenerateConfirm": "Las claves VAPID ya están configuradas. Generar un par nuevo invalida todas las suscripciones push del navegador: cada dispositivo debe volver a suscribirse. ¿Continuar?",
"webPushVapidGenerateConfirmTitle": "¿Regenerar las claves VAPID?",
"webPushVapidRegenerate": "Regenerar",
"configured": "Configurado",
"monitoringTestSuccess": "Evento de prueba enviado",
"monitoringTestFailed": "La prueba de monitorización falló",
+2
View File
@@ -4932,6 +4932,8 @@
"webPushVapidGenerated": "Clés VAPID générées. Enregistrez le sujet pour terminer.",
"webPushVapidGenerateFailed": "Impossible de générer les clés VAPID.",
"webPushVapidGenerateConfirm": "Les clés VAPID sont déjà configurées. Générer une nouvelle paire invalide tous les abonnements push du navigateur — chaque appareil doit se réabonner. Continuer ?",
"webPushVapidGenerateConfirmTitle": "Régénérer les clés VAPID ?",
"webPushVapidRegenerate": "Régénérer",
"configured": "Configuré",
"monitoringTestSuccess": "Événement de test envoyé",
"monitoringTestFailed": "Échec du test de surveillance",
+2
View File
@@ -4932,6 +4932,8 @@
"webPushVapidGenerated": "Chiavi VAPID generate. Salva l'oggetto per completare.",
"webPushVapidGenerateFailed": "Impossibile generare le chiavi VAPID.",
"webPushVapidGenerateConfirm": "Le chiavi VAPID sono già configurate. Generare una nuova coppia invalida tutte le sottoscrizioni push del browser: ogni dispositivo deve ri-sottoscriversi. Continuare?",
"webPushVapidGenerateConfirmTitle": "Rigenerare le chiavi VAPID?",
"webPushVapidRegenerate": "Rigenera",
"configured": "Configurato",
"monitoringTestSuccess": "Evento di prova inviato",
"monitoringTestFailed": "Test di monitoraggio non riuscito",
+2
View File
@@ -4932,6 +4932,8 @@
"webPushVapidGenerated": "Klucze VAPID wygenerowane. Zapisz temat, aby zakończyć.",
"webPushVapidGenerateFailed": "Nie udało się wygenerować kluczy VAPID.",
"webPushVapidGenerateConfirm": "Klucze VAPID są już skonfigurowane. Wygenerowanie nowej pary unieważnia wszystkie subskrypcje push w przeglądarce — każde urządzenie musi zasubskrybować ponownie. Kontynuować?",
"webPushVapidGenerateConfirmTitle": "Wygenerować nowe klucze VAPID?",
"webPushVapidRegenerate": "Wygeneruj ponownie",
"configured": "Skonfigurowano",
"monitoringTestSuccess": "Wysłano zdarzenie testowe",
"monitoringTestFailed": "Test monitorowania nie powiódł się",
+2 -2
View File
@@ -50,12 +50,12 @@ export default function LabsPage() {
return (
<div className="space-y-6">
<PullToRefreshIndicator {...pull} />
<div className="flex items-center justify-between">
<div className="flex items-start justify-between gap-3">
<div className="min-w-0">
<h1 className="text-2xl font-bold tracking-tight">
{t("labs.title")}
</h1>
<p className="text-muted-foreground truncate text-xs sm:text-sm">
<p className="text-muted-foreground text-xs sm:text-sm">
{t("labs.subtitle")}
</p>
</div>
@@ -6,6 +6,16 @@ import { toast } from "sonner";
import { BellRing, KeyRound, Loader2 } from "lucide-react";
import { SettingsCardHeader } from "@/components/settings/_card-header";
import { Button } from "@/components/ui/button";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { useTranslations } from "@/lib/i18n/context";
@@ -32,6 +42,11 @@ export function WebPushVapidSection() {
string | null
>(null);
const [generating, setGenerating] = useState(false);
// Overwrite guard: the first generate returns 409 when keys already exist;
// we surface the project's AlertDialog (not a native window.confirm) before
// retrying with force, since regenerating invalidates every live Web-Push
// subscription.
const [overwriteConfirmOpen, setOverwriteConfirmOpen] = useState(false);
const webPushVapidPublicKeyValue =
webPushVapidPublicKeyDraft ?? settings?.webPushVapidPublicKey ?? "";
@@ -71,12 +86,10 @@ export function WebPushVapidSection() {
);
if (res.status === 409) {
// Overwrite guard — existing keys would be replaced. Confirm with
// the operator (regenerating invalidates current subscriptions),
// then retry with force.
if (window.confirm(t("admin.webPushVapidGenerateConfirm"))) {
await generateVapidKeys(true);
}
// Overwrite guard — existing keys would be replaced. Surface the
// in-app confirm dialog (regenerating invalidates current
// subscriptions); the dialog action retries with force.
setOverwriteConfirmOpen(true);
return;
}
@@ -201,6 +214,31 @@ export function WebPushVapidSection() {
{t("common.save")}
</Button>
</div>
<AlertDialog
open={overwriteConfirmOpen}
onOpenChange={setOverwriteConfirmOpen}
>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
{t("admin.webPushVapidGenerateConfirmTitle")}
</AlertDialogTitle>
<AlertDialogDescription>
{t("admin.webPushVapidGenerateConfirm")}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>{t("common.cancel")}</AlertDialogCancel>
<AlertDialogAction
variant="destructive"
onClick={() => void generateVapidKeys(true)}
>
{t("admin.webPushVapidRegenerate")}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
);
}