import {
Box,
Button,
Checkbox,
CircularProgress,
FormControl,
FormLabel,
Input,
Tab,
TabList,
TabPanel,
Tabs,
Typography,
} from '@mui/joy'
import { useCallback, useEffect, useRef, useState } from 'react'
import { useResponsiveModal } from '../../../hooks/useResponsiveModal'
import { CreateBackup, RestoreBackup } from '../../../utils/Fetcher'
function BackupRestoreModal({ isOpen, onClose, showNotification }) {
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('Encryption key is required')
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: 'Backup created and downloaded successfully',
})
handleClose()
} else {
const errorData = await response.json()
setError(errorData.message || 'Failed to create backup')
}
} catch (err) {
setError('Failed to create backup')
} finally {
setLoading(false)
}
}
const handleFileUpload = event => {
const file = event.target.files[0]
if (file) {
setBackupFile(file)
setError('')
}
}
const handleRestore = async () => {
if (!restoreEncryptionKey.trim()) {
setError('Encryption key is required')
return
}
if (!backupFile) {
setError('Please select a backup file')
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) {
const data = await response.json()
showNotification({
type: 'success',
message: 'Backup restored successfully. Please refresh the page.',
})
// 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('Failed to restore backup')
} finally {
setLoading(false)
}
}
reader.onerror = () => {
setError('Failed to read backup file')
setLoading(false)
}
reader.readAsText(backupFile)
} catch (err) {
setError('Failed to restore backup')
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 = () => (
Create an encrypted backup of your data. This backup will include all
your chores, history, settings, and optionally your uploaded files.
Encryption Key *
setEncryptionKey(e.target.value)}
placeholder='Enter a strong encryption key'
/>
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='Include uploaded files and assets'
/>
{error && (
{error}
)}
)
const renderRestoreTab = () => (
Warning: Restoring a backup will replace all your
current data. This action cannot be undone.
Backup File *
{backupFile && (
Selected: {backupFile.name}
)}
Encryption Key *
setRestoreEncryptionKey(e.target.value)}
placeholder='Enter the encryption key used for this backup'
/>
{error && (
{error}
)}
)
return (
{loading ? (
{activeTab === 0 ? 'Creating backup...' : 'Restoring backup...'}
) : (
setActiveTab(newValue)}
>
Create Backup
Restore Backup
{renderBackupTab()}
{renderRestoreTab()}
)}
)
}
export default BackupRestoreModal