fix: improve export filename and import confirmation UX (#232)

Export filename:
- Include username for multi-user/instance distinction
- Include timestamp with time (YYYYMMDD-HHMM) instead of date only
- Example: medassist-export-daniel-20260216-2108.json

Import confirmation:
- Show friendly 'Import Data?' dialog on empty instances instead of
  scary 'Replace All Data?' warning with danger button
- Only show destructive warning when there is existing data to replace
- Use primary button style for empty-state import

Closes #231
This commit is contained in:
Daniel Volz
2026-02-16 22:20:20 +01:00
committed by GitHub
parent e41efdf98b
commit 08a18fc14a
6 changed files with 30 additions and 10 deletions
+5 -1
View File
@@ -475,7 +475,11 @@ export async function exportRoutes(app: FastifyInstance) {
};
// Set download headers
const filename = `medassist-export-${new Date().toISOString().split("T")[0]}.json`;
const now = new Date();
const dateStr = now.toISOString().replace(/[-:]/g, "").replace(/T/, "-").slice(0, 13);
const authUser = env.AUTH_ENABLED ? (request.user as unknown as AuthUser | null) : null;
const userPart = authUser?.username ? `-${authUser.username}` : "";
const filename = `medassist-export${userPart}-${dateStr}.json`;
reply.header("Content-Type", "application/json");
reply.header("Content-Disposition", `attachment; filename="${filename}"`);
+4 -2
View File
@@ -522,9 +522,11 @@ export function AppProvider({ children }: { children: React.ReactNode }) {
const blob = new Blob([JSON.stringify(data, null, 2)], { type: "application/json" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
const dateStr = new Date().toISOString().split("T")[0];
const now = new Date();
const dateStr = now.toISOString().replace(/[-:]/g, "").replace(/T/, "-").slice(0, 13);
const userPart = user?.username ? `-${user.username}` : "";
a.href = url;
a.download = `${t("exportImport.downloadFilename")}-${dateStr}.json`;
a.download = `${t("exportImport.downloadFilename")}${userPart}-${dateStr}.json`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
+3
View File
@@ -500,6 +500,9 @@
"confirmImportMessage": "Dies löscht dauerhaft alle deine aktuellen Medikamente, Einnahmehistorie, Einstellungen und Teilen-Links und ersetzt sie durch die importierten Daten.",
"confirmImportWarning": "Diese Aktion kann nicht rückgängig gemacht werden!",
"confirmButton": "Ja, alles ersetzen",
"confirmImportEmpty": "Daten importieren?",
"confirmImportEmptyMessage": "Alle Medikamente, Einnahmehistorie, Einstellungen und Teilen-Links aus der ausgewählten Datei werden importiert.",
"confirmButtonEmpty": "Importieren",
"cancelButton": "Abbrechen",
"exportSuccess": "Daten erfolgreich exportiert",
"importSuccess": "Daten erfolgreich importiert",
+3
View File
@@ -500,6 +500,9 @@
"confirmImportMessage": "This will permanently delete all your current medications, dose history, settings, and share links, then replace them with the imported data.",
"confirmImportWarning": "This action cannot be undone!",
"confirmButton": "Yes, Replace All",
"confirmImportEmpty": "Import Data?",
"confirmImportEmptyMessage": "This will import all medications, dose history, settings, and share links from the selected file.",
"confirmButtonEmpty": "Import",
"cancelButton": "Cancel",
"exportSuccess": "Data exported successfully",
"importSuccess": "Data imported successfully",
+10 -3
View File
@@ -30,8 +30,11 @@ export function SettingsPage() {
handleImportConfirm,
importResult,
setImportResult,
meds,
} = useAppContext();
const hasExistingData = meds.length > 0;
return (
<section className="grid">
{settingsLoading ? (
@@ -799,21 +802,25 @@ export function SettingsPage() {
{/* Import Confirmation Modal */}
{showImportConfirm && (
<ConfirmModal
title={t("exportImport.confirmImport")}
title={t(hasExistingData ? "exportImport.confirmImport" : "exportImport.confirmImportEmpty")}
message={
hasExistingData ? (
<>
<p style={{ marginBottom: "12px" }}>{t("exportImport.confirmImportMessage")}</p>
<p className="warning-text"> {t("exportImport.confirmImportWarning")}</p>
</>
) : (
<p>{t("exportImport.confirmImportEmptyMessage")}</p>
)
}
confirmLabel={t("exportImport.confirmButton")}
confirmLabel={t(hasExistingData ? "exportImport.confirmButton" : "exportImport.confirmButtonEmpty")}
cancelLabel={t("exportImport.cancelButton")}
onConfirm={handleImportConfirm}
onCancel={() => {
setShowImportConfirm(false);
setPendingImportData(null);
}}
confirmVariant="danger"
confirmVariant={hasExistingData ? "danger" : "primary"}
/>
)}
@@ -86,6 +86,7 @@ const createMockContext = (overrides = {}) => ({
handleImportConfirm: vi.fn(),
importResult: null,
setImportResult: vi.fn(),
meds: [],
...overrides,
});