import { Box, Checkbox, CircularProgress, FormControl, FormLabel, Input, Tab, TabList, TabPanel, Tabs, Typography, } from '@mui/joy' import { useTranslation } from 'react-i18next' import { useCallback, useEffect, useRef, useState } from 'react' import ModalActions from '../../../components/common/ModalActions' import { useResponsiveModal } from '../../../hooks/useResponsiveModal' import { CreateBackup, RestoreBackup } from '../../../utils/Fetcher' function BackupRestoreModal({ isOpen, onClose, showNotification }) { const { t } = useTranslation('settings') const { ResponsiveModal } = useResponsiveModal() const [activeTab, setActiveTab] = useState(0) const [loading, setLoading] = useState(false) const [error, setError] = useState('') // Backup state const [encryptionKey, setEncryptionKey] = useState('') const [backupName, setBackupName] = useState('') const [includeAssets, setIncludeAssets] = useState(true) // Restore state const [restoreEncryptionKey, setRestoreEncryptionKey] = useState('') const [backupFile, setBackupFile] = useState(null) const fileInputRef = useRef(null) const resetModal = useCallback(() => { setActiveTab(0) setEncryptionKey('') setBackupName('') setIncludeAssets(true) setRestoreEncryptionKey('') setBackupFile(null) setError('') setLoading(false) if (fileInputRef.current) { fileInputRef.current.value = '' } }, []) const handleClose = useCallback(() => { resetModal() onClose() }, [onClose, resetModal]) const downloadFile = (data, filename) => { const blob = new Blob([data], { type: 'application/octet-stream' }) const url = window.URL.createObjectURL(blob) const link = document.createElement('a') link.href = url link.download = filename document.body.appendChild(link) link.click() document.body.removeChild(link) window.URL.revokeObjectURL(url) } const handleCreateBackup = async () => { if (!encryptionKey.trim()) { setError(t('backup.keyRequired')) return } setLoading(true) setError('') try { const response = await CreateBackup( encryptionKey, includeAssets, backupName, ) if (response.ok) { const data = await response.json() const timestamp = new Date() .toISOString() .slice(0, 19) .replace(/:/g, '-') const filename = backupName ? `${backupName}-${timestamp}.backup` : `donetick-backup-${timestamp}.backup` // Download the backup file downloadFile(data.backup_data, filename) showNotification({ type: 'success', message: t('backup.created'), }) handleClose() } else { const errorData = await response.json() setError(errorData.message || 'Failed to create backup') } } catch (err) { setError(t('backup.createFailed')) } finally { setLoading(false) } } const handleFileUpload = event => { const file = event.target.files[0] if (file) { setBackupFile(file) setError('') } } const handleRestore = async () => { if (!restoreEncryptionKey.trim()) { setError(t('backup.keyRequired')) return } if (!backupFile) { setError(t('backup.selectFile')) return } setLoading(true) setError('') try { const reader = new FileReader() reader.onload = async e => { try { const backupData = e.target.result const response = await RestoreBackup(restoreEncryptionKey, backupData) if (response.ok) { showNotification({ type: 'success', message: t('backup.restored'), }) // Refresh the page after a short delay to allow user to see the message setTimeout(() => { window.location.reload() }, 2000) handleClose() } else { const errorData = await response.json() setError(errorData.message || 'Failed to restore backup') } } catch (err) { setError(t('backup.restoreFailed')) } finally { setLoading(false) } } reader.onerror = () => { setError(t('backup.readFailed')) setLoading(false) } reader.readAsText(backupFile) } catch (err) { setError(t('backup.restoreFailed')) setLoading(false) } } // Keyboard shortcuts useEffect(() => { const handleKeyDown = event => { if (!isOpen) return if (event.key === 'Escape') { event.preventDefault() handleClose() return } } if (isOpen) { document.addEventListener('keydown', handleKeyDown) } return () => { document.removeEventListener('keydown', handleKeyDown) } }, [isOpen, handleClose]) const renderBackupTab = () => ( {t('backup.createIntro')} Encryption Key * setEncryptionKey(e.target.value)} placeholder={t('backup.keyPlaceholder')} /> Keep this key safe—you'll need it to restore your backup Backup Name (Optional) setBackupName(e.target.value)} placeholder='e.g., weekly-backup' /> setIncludeAssets(e.target.checked)} label={t('backup.includeAssets')} /> {error && ( {error} )} ) const renderRestoreTab = () => ( {t('backup.warningLabel')}{' '} {t('backup.restoreWarning')} Backup File * {backupFile && ( Selected: {backupFile.name} )} Encryption Key * setRestoreEncryptionKey(e.target.value)} placeholder={t('backup.restoreKeyPlaceholder')} /> {error && ( {error} )} ) return ( } > {loading ? ( {activeTab === 0 ? 'Creating backup...' : 'Restoring backup...'} ) : ( setActiveTab(newValue)} > Create Backup Restore Backup {renderBackupTab()} {renderRestoreTab()} )} ) } export default BackupRestoreModal