// Copyright 2025 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React, { useState, useCallback } from 'react';
import type { LocalizerType } from '../types/I18N';
import { toLogFormat } from '../types/errors';
import { formatFileSize } from '../util/formatFileSize';
import { SECOND } from '../util/durations';
import type { ValidationResultType as BackupValidationResultType } from '../services/backups';
import type { ValidateLocalBackupStructureResultType } from '../services/backups/util/localBackup';
import { SettingsRow, SettingsControl } from './PreferencesUtil';
import { Button, ButtonVariant } from './Button';
import { Spinner } from './Spinner';
export function PreferencesInternal({
i18n,
exportLocalBackup: doExportLocalBackup,
importLocalBackup: doImportLocalBackup,
validateBackup: doValidateBackup,
}: {
i18n: LocalizerType;
exportLocalBackup: () => Promise;
importLocalBackup: () => Promise;
validateBackup: () => Promise;
}): JSX.Element {
const [isExportPending, setIsExportPending] = useState(false);
const [exportResult, setExportResult] = useState<
BackupValidationResultType | undefined
>();
const [importResult, setImportResult] = useState<
ValidateLocalBackupStructureResultType | undefined
>();
const [isValidationPending, setIsValidationPending] = useState(false);
const [validationResult, setValidationResult] = useState<
BackupValidationResultType | undefined
>();
const validateBackup = useCallback(async () => {
setIsValidationPending(true);
setValidationResult(undefined);
try {
setValidationResult(await doValidateBackup());
} catch (error) {
setValidationResult({ error: toLogFormat(error) });
} finally {
setIsValidationPending(false);
}
}, [doValidateBackup]);
const renderValidationResult = useCallback(
(
backupResult: BackupValidationResultType | undefined
): JSX.Element | undefined => {
if (backupResult == null) {
return;
}
if ('result' in backupResult) {
const {
result: { totalBytes, stats, duration },
} = backupResult;
let snapshotDirEl: JSX.Element | undefined;
if ('snapshotDir' in backupResult.result) {
snapshotDirEl = (
Backup path:
{backupResult.result.snapshotDir}
);
}
return (
{snapshotDirEl}
Main file size: {formatFileSize(totalBytes)}
Duration: {Math.round(duration / SECOND)}s
{JSON.stringify(stats, null, 2)}
);
}
const { error } = backupResult;
return (
);
},
[]
);
const exportLocalBackup = useCallback(async () => {
setIsExportPending(true);
setExportResult(undefined);
try {
setExportResult(await doExportLocalBackup());
} catch (error) {
setExportResult({ error: toLogFormat(error) });
} finally {
setIsExportPending(false);
}
}, [doExportLocalBackup]);
const importLocalBackup = useCallback(async () => {
setImportResult(undefined);
try {
setImportResult(await doImportLocalBackup());
} catch (error) {
setImportResult({
success: false,
error: toLogFormat(error),
snapshotDir: undefined,
});
}
}, [doImportLocalBackup]);
const renderImportResult = useCallback(
(
didImportResult: ValidateLocalBackupStructureResultType | undefined
): JSX.Element | undefined => {
if (didImportResult == null) {
return;
}
const { success, error, snapshotDir } = didImportResult;
if (success) {
return (
{`Staged: ${snapshotDir}\n\nPlease link to finish import.`}
);
}
return (
);
},
[]
);
return (
<>
{i18n('icu:Preferences__button--internal')}
{isValidationPending ? (
) : (
i18n('icu:Preferences__internal__validate-backup')
)}
}
/>
{renderValidationResult(validationResult)}
{isExportPending ? (
) : (
i18n('icu:Preferences__internal__export-local-backup')
)}
}
/>
{renderValidationResult(exportResult)}
{i18n('icu:Preferences__internal__import-local-backup')}
}
/>
{renderImportResult(importResult)}
>
);
}