import React, { useEffect, useState } from "react"; import { Alert, Box, Button, Divider, Typography } from "@mui/material"; import { PageSection } from "../layout/PageLayout"; import { api, getApiErrorMessage } from "../api"; import { clearAuthClientState } from "../auth"; import { useToast } from "../toast"; import { useI18n } from "../i18n/I18nProvider"; import { usePrompt } from "../prompt"; type AccountLifecycleStatus = { deletionEnabled: boolean; deletionStatus: string; requiredConfirmation: string; request?: { status: string; stage: string } | null; }; export default function BackupCard() { const { toast } = useToast(); const { t } = useI18n(); const { prompt } = usePrompt(); const [downloading, setDownloading] = useState(false); const [exportingAccount, setExportingAccount] = useState(false); const [deletingAccount, setDeletingAccount] = useState(false); const [lifecycle, setLifecycle] = useState(null); useEffect(() => { let active = true; api.get("/account-lifecycle/status") .then((response) => { if (active) setLifecycle(response.data); }) .catch(() => { if (active) setLifecycle(null); }); return () => { active = false; }; }, []); const downloadBlob = (blob: Blob, contentDisposition: string | undefined, fallbackName: string) => { const url = URL.createObjectURL(blob); const match = /filename="?([^";]+)"?/i.exec(contentDisposition || ""); const link = document.createElement("a"); link.href = url; link.download = match?.[1] ?? fallbackName; document.body.appendChild(link); link.click(); link.remove(); window.setTimeout(() => URL.revokeObjectURL(url), 5000); }; const downloadAccountExport = async () => { setExportingAccount(true); try { const res = await api.post("/export/account", null, { responseType: "blob" }); downloadBlob(res.data as Blob, res.headers?.["content-disposition"] as string | undefined, `jobjakt-account-export-${new Date().toISOString().slice(0, 10)}.zip`); toast(t("accountExportDownloaded"), "success"); } catch (error: any) { toast(getApiErrorMessage(error, t("accountExportFailed")), "error"); } finally { setExportingAccount(false); } }; const downloadEncrypted = async () => { setDownloading(true); try { const res = await api.post("/backup/encrypted", null, { responseType: "blob" }); downloadBlob(res.data as Blob, res.headers?.["content-disposition"] as string | undefined, `jobtracker_backup_${new Date().toISOString().slice(0, 10)}.jtbackup`); toast(t("backupDownloaded"), "success"); } catch (error: any) { toast(getApiErrorMessage(error, t("backupFailed")), "error"); } finally { setDownloading(false); } }; const deleteAccount = async () => { if (!lifecycle?.deletionEnabled || lifecycle.deletionStatus !== "active") return; const confirmation = await prompt({ title: t("accountDeleteTitle"), message: t("accountDeletePrompt", { confirmation: lifecycle.requiredConfirmation }), confirmLabel: t("accountDeleteButton"), cancelLabel: t("cancel"), }); if (confirmation === null) return; if (confirmation.trim() !== lifecycle.requiredConfirmation) { toast(t("accountDeleteConfirmationMismatch"), "error"); return; } setDeletingAccount(true); try { const response = await api.post("/account-lifecycle/delete", { confirmation }); setLifecycle((current) => current ? { ...current, deletionStatus: "pending", request: response.data } : current); clearAuthClientState(); window.location.assign("/login"); } catch (error: any) { toast(getApiErrorMessage(error, t("accountDeleteFailed")), "error"); setDeletingAccount(false); } }; return ( {t("accountExportBody")} {t("accountExportRecentSignIn")} {t("backupEncryptedTitle")} {t("backupBody")} {t("accountDeleteTitle")} {lifecycle?.deletionStatus === "pending" ? ( {t("accountDeletePending", { stage: lifecycle.request?.stage ?? "pending" })} ) : lifecycle?.deletionEnabled ? ( {t("accountDeleteWarning")} ) : ( {t("accountDeleteDisabled")} )} ); }