mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 13:01:00 +00:00
fix(settings): show a pending state on the danger-zone confirm buttons
The data-reset and account-delete confirmations fired their mutation from an uncontrolled AlertDialog, so the action dismissed the dialog the instant it was clicked and the only spinner lived on the now-hidden trigger. A user on a slow link saw no feedback and could re-open and re-submit a destructive, irreversible action. Drive both dialogs from controlled open state and add an in-dialog pending treatment to the confirm button — disabled, aria-busy, and a spinner — mirroring the bulk-delete confirmation in the medication intake history. The dialog now holds open while the request is in flight; the data-reset closes on success, the account-delete keeps its pending state through the post-delete redirect.
This commit is contained in:
1 parent
c24f42848e
commit
227dd2b73f
1 file changed
+53
-7
@@ -235,10 +235,12 @@ function DataResetCard() {
|
||||
const { t } = useTranslations();
|
||||
const queryClient = useQueryClient();
|
||||
const [deleting, setDeleting] = useState(false);
|
||||
const [confirmOpen, setConfirmOpen] = useState(false);
|
||||
const [msg, setMsg] = useState<string | null>(null);
|
||||
const [msgType, setMsgType] = useState<"success" | "error" | null>(null);
|
||||
|
||||
async function handleDeleteAllData() {
|
||||
if (deleting) return;
|
||||
setDeleting(true);
|
||||
setMsg(null);
|
||||
setMsgType(null);
|
||||
@@ -258,6 +260,7 @@ function DataResetCard() {
|
||||
await queryClient.invalidateQueries();
|
||||
setMsg(t("settings.dangerZoneSuccess"));
|
||||
setMsgType("success");
|
||||
setConfirmOpen(false);
|
||||
} catch {
|
||||
setMsg(t("settings.dangerZoneDeleteFailed"));
|
||||
setMsgType("error");
|
||||
@@ -285,7 +288,16 @@ function DataResetCard() {
|
||||
{t("settings.dangerZoneDescription")}
|
||||
</p>
|
||||
</div>
|
||||
<AlertDialog>
|
||||
<AlertDialog
|
||||
open={confirmOpen}
|
||||
onOpenChange={(open) => {
|
||||
// Hold the dialog open while the destructive mutation is in
|
||||
// flight so the in-dialog pending state stays visible and a
|
||||
// stray backdrop tap can't dismiss it mid-request.
|
||||
if (deleting) return;
|
||||
setConfirmOpen(open);
|
||||
}}
|
||||
>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button
|
||||
variant="destructive"
|
||||
@@ -311,11 +323,22 @@ function DataResetCard() {
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>{t("common.cancel")}</AlertDialogCancel>
|
||||
<AlertDialogCancel disabled={deleting}>
|
||||
{t("common.cancel")}
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||
onClick={handleDeleteAllData}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
void handleDeleteAllData();
|
||||
}}
|
||||
disabled={deleting}
|
||||
aria-busy={deleting || undefined}
|
||||
data-slot="settings-data-reset-confirm"
|
||||
>
|
||||
{deleting && (
|
||||
<Loader2 className="mr-1 h-3.5 w-3.5 animate-spin motion-reduce:animate-none" />
|
||||
)}
|
||||
{t("settings.finalDelete")}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
@@ -352,10 +375,12 @@ function DataResetCard() {
|
||||
function AccountDeleteCard() {
|
||||
const { t } = useTranslations();
|
||||
const [deleting, setDeleting] = useState(false);
|
||||
const [confirmOpen, setConfirmOpen] = useState(false);
|
||||
const [msg, setMsg] = useState<string | null>(null);
|
||||
const [msgType, setMsgType] = useState<"success" | "error" | null>(null);
|
||||
|
||||
async function handleDeleteAccount() {
|
||||
if (deleting) return;
|
||||
setDeleting(true);
|
||||
setMsg(null);
|
||||
setMsgType(null);
|
||||
@@ -373,19 +398,21 @@ function AccountDeleteCard() {
|
||||
setMsg(t("settings.deleteAccountFailed"));
|
||||
}
|
||||
setMsgType("error");
|
||||
setDeleting(false);
|
||||
return;
|
||||
}
|
||||
setMsg(t("settings.deleteAccountSuccess"));
|
||||
setMsgType("success");
|
||||
// The route destroyed every session before deleting the row;
|
||||
// give the toast a beat to paint, then bounce to the login page.
|
||||
// Leave `deleting` set so the confirm button keeps its pending
|
||||
// state through the redirect — the row is gone, nothing to undo.
|
||||
setTimeout(() => {
|
||||
window.location.href = "/auth/login";
|
||||
}, 1_500);
|
||||
} catch {
|
||||
setMsg(t("settings.deleteAccountFailed"));
|
||||
setMsgType("error");
|
||||
} finally {
|
||||
setDeleting(false);
|
||||
}
|
||||
}
|
||||
@@ -404,7 +431,16 @@ function AccountDeleteCard() {
|
||||
{t("settings.deleteAccountCardDescription")}
|
||||
</p>
|
||||
</div>
|
||||
<AlertDialog>
|
||||
<AlertDialog
|
||||
open={confirmOpen}
|
||||
onOpenChange={(open) => {
|
||||
// Hold the dialog open while the irreversible delete runs so
|
||||
// the pending state stays on screen and a stray dismissal
|
||||
// can't fire a second request.
|
||||
if (deleting) return;
|
||||
setConfirmOpen(open);
|
||||
}}
|
||||
>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button
|
||||
variant="destructive"
|
||||
@@ -431,12 +467,22 @@ function AccountDeleteCard() {
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>{t("common.cancel")}</AlertDialogCancel>
|
||||
<AlertDialogCancel disabled={deleting}>
|
||||
{t("common.cancel")}
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||
onClick={handleDeleteAccount}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
void handleDeleteAccount();
|
||||
}}
|
||||
disabled={deleting}
|
||||
aria-busy={deleting || undefined}
|
||||
data-slot="settings-account-delete-confirm"
|
||||
>
|
||||
{deleting && (
|
||||
<Loader2 className="mr-1 h-3.5 w-3.5 animate-spin motion-reduce:animate-none" />
|
||||
)}
|
||||
{t("settings.deleteAccountFinal")}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
|
||||
Reference in new issue
Block a user