From 2a76bb185ba3af52c2465b466f1ca3dd87241dc0 Mon Sep 17 00:00:00 2001 From: Thomas Camlong Date: Tue, 18 Aug 2026 11:11:23 +0200 Subject: [PATCH] fix(backup): show explicit sign-in prompt after database restore Restoring a backup replaces the database, invalidating the database-backed session (Auth.js session strategy: database). The old code silently reloaded the page, leaving users on an empty board or a login screen with no context. Replace the opaque reload with an explicit completion state that informs the user their session was invalidated and auto-redirects to the sign-in page (bypassing bfcache so the stale board cannot be restored). --- .../backup/database-restore-flow.tsx | 49 +++++++++++++++++-- apps/nextjs/src/components/backup/types.ts | 2 +- packages/translation/src/lang/en.json | 3 +- 3 files changed, 48 insertions(+), 6 deletions(-) diff --git a/apps/nextjs/src/components/backup/database-restore-flow.tsx b/apps/nextjs/src/components/backup/database-restore-flow.tsx index 83d8f9f3f..0d693e801 100644 --- a/apps/nextjs/src/components/backup/database-restore-flow.tsx +++ b/apps/nextjs/src/components/backup/database-restore-flow.tsx @@ -1,10 +1,10 @@ "use client"; -import { useCallback, useRef, useState } from "react"; -import { Alert, Button, Group, Loader, rem, Stack, Text } from "@mantine/core"; +import { useCallback, useEffect, useRef, useState } from "react"; +import { Alert, Button, Group, Loader, rem, Stack, Text, ThemeIcon } from "@mantine/core"; import type { FileWithPath } from "@mantine/dropzone"; import { Dropzone, MIME_TYPES } from "@mantine/dropzone"; -import { IconAlertTriangle, IconArrowRight, IconFileZip, IconUpload, IconX } from "@tabler/icons-react"; +import { IconAlertTriangle, IconArrowRight, IconCheck, IconFileZip, IconUpload, IconX } from "@tabler/icons-react"; import "@mantine/dropzone/styles.css"; @@ -30,8 +30,17 @@ export const DatabaseRestoreFlow = ({ variant = "card", onRestoreComplete }: Dat const apiDoneRef = useRef(false); const animDoneRef = useRef(false); const apiErrorRef = useRef(null); + const redirectTimerRef = useRef(null); const { analysis, loading, error: analysisError, migrationProgress, analyzeFile, reset } = useBackupAnalysis(); + useEffect(() => { + return () => { + if (redirectTimerRef.current !== null) { + window.clearTimeout(redirectTimerRef.current); + } + }; + }, []); + const handleFileDrop = useCallback( (files: FileWithPath[]) => { const droppedFile = files[0]; @@ -45,6 +54,10 @@ export const DatabaseRestoreFlow = ({ variant = "card", onRestoreComplete }: Dat ); const handleClear = useCallback(() => { + if (redirectTimerRef.current !== null) { + window.clearTimeout(redirectTimerRef.current); + redirectTimerRef.current = null; + } setFile(null); setStep("upload"); setImportError(null); @@ -65,9 +78,20 @@ export const DatabaseRestoreFlow = ({ variant = "card", onRestoreComplete }: Dat if (!animDoneRef.current) return; onRestoreComplete?.(); - window.location.reload(); + setStep("complete"); + redirectTimerRef.current = window.setTimeout(() => { + window.location.assign("/auth/login"); + }, 2000); }, [onRestoreComplete]); + const handleSignInAgain = useCallback(() => { + if (redirectTimerRef.current !== null) { + window.clearTimeout(redirectTimerRef.current); + redirectTimerRef.current = null; + } + window.location.assign("/auth/login"); + }, []); + const handleConfirm = useCallback(async () => { if (!file) return; setStep("restoring"); @@ -212,6 +236,23 @@ export const DatabaseRestoreFlow = ({ variant = "card", onRestoreComplete }: Dat ); } + if (step === "complete") { + return ( + + + + + + {t("complete.title")} + + + {t("complete.message")} + + + + ); + } + if (step === "error") { return ( diff --git a/apps/nextjs/src/components/backup/types.ts b/apps/nextjs/src/components/backup/types.ts index c197c6544..74b05d7ad 100644 --- a/apps/nextjs/src/components/backup/types.ts +++ b/apps/nextjs/src/components/backup/types.ts @@ -25,6 +25,6 @@ export interface BackupAnalysis { migrations: MigrationStatus; } -export type RestoreStep = "upload" | "preview" | "confirm" | "restoring" | "error"; +export type RestoreStep = "upload" | "preview" | "confirm" | "restoring" | "complete" | "error"; export const PREVIEW_TABLE_KEYS = ["board", "user", "app", "integration", "item", "media", "search_engine"] as const; diff --git a/packages/translation/src/lang/en.json b/packages/translation/src/lang/en.json index 13356dedf..73abd8182 100644 --- a/packages/translation/src/lang/en.json +++ b/packages/translation/src/lang/en.json @@ -5276,7 +5276,8 @@ }, "complete": { "title": "Restore Complete", - "message": "Database has been restored successfully. The server is restarting — please refresh the page in a few seconds." + "message": "Database has been restored successfully and the server has restarted. Your previous session was invalidated, so you need to sign in again to see the restored board.", + "button": "Sign in now" }, "failed": { "title": "Restore Failed"