import { useState } from 'react' import { useTranslation } from 'react-i18next' import { Archive, Download, Check, AlertTriangle } from 'lucide-react' import { downloadBackupBlob, exportLogbookBackup } from '../services/logbookBackup.js' import { PlausibleEvents, trackPlausibleEvent } from '../services/analytics.js' interface LogbookBackupPanelProps { logbookId: string } function mapBackupError(code: string, t: (key: string) => string): string { switch (code) { case 'BACKUP_PASSPHRASE_TOO_SHORT': return t('settings.backup_passphrase_short') case 'BACKUP_NOT_OWNER': return t('settings.backup_not_owner') case 'BACKUP_INVALID_JSON': return t('settings.backup_invalid_json') case 'BACKUP_INVALID_ARCHIVE': return t('settings.backup_invalid_archive') case 'BACKUP_VERSION_UNSUPPORTED': return t('settings.backup_version_unsupported') case 'BACKUP_WRONG_PASSPHRASE': return t('settings.backup_wrong_passphrase') case 'BACKUP_INVALID_FORMAT': return t('settings.backup_invalid_format') case 'BACKUP_NOT_AUTHENTICATED': return t('settings.backup_not_authenticated') case 'BACKUP_ID_CONFLICT': return t('settings.backup_id_conflict') default: if (code.includes('decrypt') || code.includes('operation')) { return t('settings.backup_wrong_passphrase') } return code } } export default function LogbookBackupPanel({ logbookId }: LogbookBackupPanelProps) { const { t } = useTranslation() const [exportPassphrase, setExportPassphrase] = useState('') const [exportConfirm, setExportConfirm] = useState('') const [exporting, setExporting] = useState(false) const [exportProgress, setExportProgress] = useState(null) const [error, setError] = useState(null) const [success, setSuccess] = useState(null) const exportPassphrasesMatch = exportPassphrase.length >= 8 && exportPassphrase === exportConfirm const handleExportSubmit = async (e: React.FormEvent) => { e.preventDefault() await handleExport() } const handleExport = async () => { setError(null) setSuccess(null) if (exportPassphrase.length < 8) { setError(t('settings.backup_passphrase_short')) return } if (exportPassphrase !== exportConfirm) { setError(t('settings.backup_passphrase_mismatch')) return } setExporting(true) setExportProgress(null) try { const { blob, filename, manifest } = await exportLogbookBackup(logbookId, exportPassphrase, { onProgress: (p) => { if (p.phase === 'pack') { setExportProgress( t('settings.backup_export_progress', { current: p.current, total: p.total }) ) } } }) downloadBackupBlob(blob, filename) setSuccess(t('settings.backup_export_success', { count: manifest.counts.entries })) setExportPassphrase('') setExportConfirm('') trackPlausibleEvent(PlausibleEvents.BACKUP_EXPORTED, { entries: manifest.counts.entries, photos: manifest.counts.photos, voiceMemos: manifest.counts.voiceMemos, bytes: manifest.totalUncompressedBytes }) } catch (err: unknown) { const message = err instanceof Error ? err.message : String(err) setError(mapBackupError(message, t)) } finally { setExporting(false) setExportProgress(null) } } return (

{t('settings.backup_title')}

{t('settings.backup_desc')}

{error && (
{error}
)} {success && (
{success}
)}

{t('settings.backup_export_desc')}

setExportPassphrase(e.target.value)} placeholder={t('settings.backup_passphrase_placeholder')} autoComplete="new-password" disabled={exporting} required />
setExportConfirm(e.target.value)} autoComplete="new-password" disabled={exporting} required />
{exportProgress && (

{exportProgress}

)}
) }