import { useEffect, useState } from 'react'; import { Download, X, Settings, Bell, FileText, Plug, Printer, Palette, Wrench, Archive, Loader2, Key, AlertTriangle, Link, FolderKanban, Upload, Camera } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { Card, CardContent } from './Card'; import { Button } from './Button'; import { Toggle } from './Toggle'; interface BackupCategory { id: string; labelKey: string; defaultLabel: string; icon: React.ReactNode; default: boolean; description: string; requiresPrinters?: boolean; } const BACKUP_CATEGORIES: BackupCategory[] = [ { id: 'settings', labelKey: 'backup.categories.settings', defaultLabel: 'App Settings', icon: , default: true, description: 'Language, theme, update preferences', }, { id: 'notifications', labelKey: 'backup.categories.notifications', defaultLabel: 'Notification Providers', icon: , default: true, description: 'ntfy, Pushover, Discord, etc.', }, { id: 'templates', labelKey: 'backup.categories.templates', defaultLabel: 'Notification Templates', icon: , default: true, description: 'Custom message templates', }, { id: 'smart_plugs', labelKey: 'backup.categories.smartPlugs', defaultLabel: 'Smart Plugs', icon: , default: true, description: 'Tasmota plug configurations', }, { id: 'external_links', labelKey: 'backup.categories.externalLinks', defaultLabel: 'External Links', icon: , default: true, description: 'Sidebar links to external services', }, { id: 'printers', labelKey: 'backup.categories.printers', defaultLabel: 'Printers', icon: , default: false, description: 'Printer info (access codes excluded)', }, { id: 'plate_calibration', labelKey: 'backup.categories.plateCalibration', defaultLabel: 'Plate Detection', icon: , default: false, description: 'Empty plate reference images', requiresPrinters: true, }, { id: 'filaments', labelKey: 'backup.categories.filaments', defaultLabel: 'Filament Inventory', icon: , default: false, description: 'Filament types and costs', }, { id: 'maintenance', labelKey: 'backup.categories.maintenance', defaultLabel: 'Maintenance Types', icon: , default: false, description: 'Custom maintenance schedules', }, { id: 'archives', labelKey: 'backup.categories.archives', defaultLabel: 'Print Archives', icon: , default: false, description: 'All print data + files (3MF, thumbnails, photos)', }, { id: 'projects', labelKey: 'backup.categories.projects', defaultLabel: 'Projects', icon: , default: false, description: 'Projects, BOM items, and attachments', }, { id: 'pending_uploads', labelKey: 'backup.categories.pendingUploads', defaultLabel: 'Pending Uploads', icon: , default: false, description: 'Virtual printer uploads awaiting review', }, { id: 'api_keys', labelKey: 'backup.categories.apiKeys', defaultLabel: 'API Keys', icon: , default: false, description: 'Webhook API keys (new keys generated on import)', }, ]; interface BackupModalProps { onClose: () => void; onExport: (categories: Record) => Promise; } export function BackupModal({ onClose, onExport }: BackupModalProps) { const { t } = useTranslation(); const [selected, setSelected] = useState>(() => { const initial: Record = {}; BACKUP_CATEGORIES.forEach((cat) => { initial[cat.id] = cat.default; }); return initial; }); const [includeAccessCodes, setIncludeAccessCodes] = useState(false); const [isExporting, setIsExporting] = useState(false); // Close on Escape key useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); }; window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); }, [onClose]); const toggleCategory = (id: string) => { setSelected((prev) => ({ ...prev, [id]: !prev[id] })); }; const selectAll = () => { const all: Record = {}; BACKUP_CATEGORIES.forEach((cat) => { all[cat.id] = true; }); setSelected(all); }; const selectNone = () => { const none: Record = {}; BACKUP_CATEGORIES.forEach((cat) => { none[cat.id] = false; }); setSelected(none); }; const selectedCount = Object.values(selected).filter(Boolean).length; const handleExport = async () => { setIsExporting(true); try { await onExport({ ...selected, access_codes: includeAccessCodes && selected.printers }); } finally { setIsExporting(false); } }; return ( e.stopPropagation()}> {/* Header */} {t('backup.exportTitle', { defaultValue: 'Export Backup' })} {t('backup.selectCategories', { defaultValue: 'Select data to include' })} {/* Quick actions */} {t('common.selectAll', { defaultValue: 'Select All' })} | {t('common.selectNone', { defaultValue: 'Select None' })} {/* Categories */} {BACKUP_CATEGORIES.map((category) => { const isDisabled = isExporting || (category.requiresPrinters && !selected.printers); return ( toggleCategory(category.id)} disabled={isDisabled} className="w-4 h-4 rounded border-bambu-gray bg-bambu-dark text-bambu-green focus:ring-bambu-green focus:ring-offset-0" /> {category.icon} {t(category.labelKey, { defaultValue: category.defaultLabel })} {category.requiresPrinters && !selected.printers ? 'Requires Printers to be selected' : category.description} ); })} {/* Archive warning */} {selected.archives && ( ZIP file will be created. Includes all 3MF files, thumbnails, timelapses, and photos. This may take a while and result in a large file. )} {/* Access codes option - only shown when printers are selected */} {selected.printers && ( Include Access Codes For transferring to another machine {includeAccessCodes && ( Access codes will be included in plain text. Keep this backup file secure! )} )} {/* Footer */} {t('backup.selectedCount', { count: selectedCount, defaultValue: `${selectedCount} categories selected`, })} {t('common.cancel', { defaultValue: 'Cancel' })} {isExporting ? ( <> {t('backup.exporting', { defaultValue: 'Exporting...' })} > ) : ( <> {t('backup.export', { defaultValue: 'Export' })} > )} ); }
{t('backup.selectCategories', { defaultValue: 'Select data to include' })}
Include Access Codes
For transferring to another machine