diff --git a/src/components/admin/backups-section.tsx b/src/components/admin/backups-section.tsx
index a337889b3..ba4f8fe63 100644
--- a/src/components/admin/backups-section.tsx
+++ b/src/components/admin/backups-section.tsx
@@ -15,10 +15,7 @@ import { BookOpen, Database, Loader2, PlayCircle } from "lucide-react";
import { BackupPassActivityNotice } from "@/components/admin/backup-pass-activity";
import { SettingsCardActions } from "@/components/settings/_card-actions";
import { SettingsCardHeader } from "@/components/settings/_card-header";
-import {
- ReproofCancelledError,
- useRecentProof,
-} from "@/components/settings/security-section/use-recent-proof";
+import { useRecentProof } from "@/components/settings/security-section/use-recent-proof";
import { SettingsCard } from "@/components/settings/settings-card";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
@@ -32,8 +29,7 @@ import type { BackupRow, BackupsList } from "@/types/backups";
import { RestoreJobStatus, visibleRestoreJobs } from "./backup-restore-status";
import { BackupRowsCards, BackupRowsTable } from "./backups/backup-rows";
import { DatabaseDumpRow } from "./backups/database-dump-row";
-import { BackupTargetDialog } from "./backups/backup-target-dialog";
-import { BackupUploadCard } from "./backups/backup-upload-card";
+import { BackupUpload } from "./backups/backup-upload-card";
import {
RestoreRefusalNotice,
RestoreSkipReport,
@@ -41,7 +37,6 @@ import {
} from "./backups/restore-notices";
import { useBackupDownload, useRunBackup } from "./backups/use-backup-actions";
import { useBackupRestores } from "./backups/use-backup-restores";
-import { useBackupUpload } from "./backups/use-backup-upload";
export function BackupsSection() {
const { t } = useTranslations();
@@ -59,14 +54,6 @@ export function BackupsSection() {
// a fresh proof unless this session signed in or re-proved within minutes.
const recentProof = useRecentProof();
const { downloadingId, handleDownload } = useBackupDownload(recentProof);
- const {
- upload,
- fileInputRef,
- handleFileChange,
- orphan,
- uploadInto,
- closeOrphan,
- } = useBackupUpload(recentProof);
const {
restoreJobs,
dismissedRestores,
@@ -136,25 +123,7 @@ export function BackupsSection() {
) : null}
-
- {/* Mounted per refused file, so every file starts from a clean form. */}
- {orphan ? (
- uploadInto.mutate(target)}
- onClose={closeOrphan}
- />
- ) : null}
+
{visibleRestoreJobs(restoreJobs, dismissedRestores).map((job) => (
;
+}) {
+ const {
+ upload,
+ fileInputRef,
+ handleFileChange,
+ orphan,
+ uploadInto,
+ closeOrphan,
+ } = useBackupUpload(recentProof);
+ return (
+ <>
+
+ {orphan ? (
+ uploadInto.mutate(target)}
+ onClose={closeOrphan}
+ />
+ ) : null}
+ >
+ );
+}
+
/**
* The upload card: separate from the table so admins can ingest a backup file
* independently of any existing rows. The visible button proxies a hidden
diff --git a/src/components/admin/backups/use-backup-upload.ts b/src/components/admin/backups/use-backup-upload.ts
index 97f263bf6..c38e29f8f 100644
--- a/src/components/admin/backups/use-backup-upload.ts
+++ b/src/components/admin/backups/use-backup-upload.ts
@@ -141,19 +141,24 @@ export function useBackupUpload(
// Upload state — single-file flow, drives the file input + button.
const fileInputRef = useRef(null);
const [orphan, setOrphan] = useState(null);
+
+ /** A stored copy, said once: for its own account, or for the one chosen. */
+ function announceStored(summary: UploadSummary, username: string | null) {
+ const count = String(uploadedRecordCount(summary));
+ toast.success(
+ username === null
+ ? t("admin.section.backups.uploadSuccess", { count })
+ : t("admin.section.backups.target.success", { count, username }),
+ );
+ queryClient.invalidateQueries({ queryKey: queryKeys.adminBackups() });
+ }
const upload = useMutation({
mutationFn: async (file: File) => {
// Ask first, so a large file is not sent once only to be refused.
await recentProof.run(() => apiGet("/api/auth/reproof"));
return uploadOnce(file);
},
- onSuccess: (data) => {
- const total = uploadedRecordCount(data.summary);
- toast.success(
- t("admin.section.backups.uploadSuccess", { count: String(total) }),
- );
- queryClient.invalidateQueries({ queryKey: queryKeys.adminBackups() });
- },
+ onSuccess: (data) => announceStored(data.summary, null),
onError: (err, file) => {
if (err instanceof ReproofCancelledError) return;
// Not a dead end: the dialog offers an account to restore it into.
@@ -182,15 +187,11 @@ export function useBackupUpload(
return { data: await uploadOnce(orphan, target), target };
},
onSuccess: ({ data, target }) => {
- const total = uploadedRecordCount(data.summary);
- toast.success(
- t("admin.section.backups.target.success", {
- count: String(total),
- username: target.into === "new" ? target.username : target.confirm,
- }),
- );
setOrphan(null);
- queryClient.invalidateQueries({ queryKey: queryKeys.adminBackups() });
+ announceStored(
+ data.summary,
+ target.into === "new" ? target.username : target.confirm,
+ );
queryClient.invalidateQueries({ queryKey: queryKeys.adminUsers() });
},
// A refusal stays in the dialog, beside the choice it refuses.