feat(export): add readable account archive
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import React, { useState } from "react";
|
||||
|
||||
import { Box, Button, Paper, Typography } from "@mui/material";
|
||||
import { Alert, Box, Button, Divider, Paper, Typography } from "@mui/material";
|
||||
import { api, getApiErrorMessage } from "../api";
|
||||
import { useToast } from "../toast";
|
||||
import { useI18n } from "../i18n/I18nProvider";
|
||||
@@ -9,24 +9,38 @@ export default function BackupCard() {
|
||||
const { toast } = useToast();
|
||||
const { t } = useI18n();
|
||||
const [downloading, setDownloading] = useState(false);
|
||||
const [exportingAccount, setExportingAccount] = useState(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" });
|
||||
const blob: Blob = res.data;
|
||||
const url = URL.createObjectURL(blob);
|
||||
const cd = (res.headers?.["content-disposition"] as string) || "";
|
||||
const m = /filename="?([^";]+)"?/i.exec(cd);
|
||||
const filename = m?.[1] ?? `jobtracker_backup_${new Date().toISOString().slice(0, 10)}.jtbackup`;
|
||||
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.download = filename;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
link.remove();
|
||||
window.setTimeout(() => URL.revokeObjectURL(url), 5000);
|
||||
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");
|
||||
@@ -40,11 +54,22 @@ export default function BackupCard() {
|
||||
<Typography variant="h6" sx={{ mb: 1 }}>
|
||||
{t("backupTitle")}
|
||||
</Typography>
|
||||
<Typography sx={{ color: "text.secondary", mb: 1 }}>
|
||||
{t("accountExportBody")}
|
||||
</Typography>
|
||||
<Box sx={{ display: "flex", gap: 1, flexWrap: "wrap" }}>
|
||||
<Button variant="contained" onClick={downloadAccountExport} disabled={exportingAccount || downloading}>
|
||||
{exportingAccount ? t("accountExportPreparing") : t("accountExportDownload")}
|
||||
</Button>
|
||||
</Box>
|
||||
<Alert severity="info" sx={{ mt: 1.5 }}>{t("accountExportRecentSignIn")}</Alert>
|
||||
<Divider sx={{ my: 2 }} />
|
||||
<Typography variant="subtitle1" sx={{ fontWeight: 800, mb: 0.5 }}>{t("backupEncryptedTitle")}</Typography>
|
||||
<Typography sx={{ color: "text.secondary", mb: 1 }}>
|
||||
{t("backupBody")}
|
||||
</Typography>
|
||||
<Box sx={{ display: "flex", gap: 1, flexWrap: "wrap" }}>
|
||||
<Button variant="contained" onClick={downloadEncrypted} disabled={downloading}>
|
||||
<Button variant="contained" onClick={downloadEncrypted} disabled={downloading || exportingAccount}>
|
||||
{downloading ? t("backupPreparing") : t("backupDownload")}
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
Reference in New Issue
Block a user