// 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 (
            {error}
          
); }, [] ); 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 (
            {`Failed: ${error}`}
          
); }, [] ); 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)} ); }