diff --git a/components/options/BackupSection.tsx b/components/options/BackupSection.tsx new file mode 100644 index 0000000..91ace85 --- /dev/null +++ b/components/options/BackupSection.tsx @@ -0,0 +1,64 @@ +import { useRef, useState } from 'react'; +import { useTranslation } from '../../hooks/useTranslation'; +import { useSettingsStore } from '../../store/settings'; +import { exportSettingsJson, parseImportedSettings } from '../../utils/settings-transfer'; + +export function BackupSection() { + const t = useTranslation(); + const settings = useSettingsStore(); + const [importError, setImportError] = useState(false); + const fileInputRef = useRef(null); + + function handleExport() { + const json = exportSettingsJson(settings); + const url = URL.createObjectURL(new Blob([json], { type: 'application/json' })); + const a = document.createElement('a'); + a.href = url; + a.download = 'bleep-settings.json'; + a.click(); + URL.revokeObjectURL(url); + } + + async function handleImportFile(e: React.ChangeEvent) { + const file = e.target.files?.[0]; + e.target.value = ''; + if (!file) return; + try { + const data = parseImportedSettings(await file.text()); + settings.importSettings(data); + setImportError(false); + } catch (err) { + console.error('Bleep: settings import failed', err); + setImportError(true); + } + } + + return ( +
+ +

{t('backupHint')}

+
+ + + +
+ {importError &&

{t('importInvalidFile')}

} +
+ ); +} diff --git a/components/options/ClearLogSection.tsx b/components/options/ClearLogSection.tsx new file mode 100644 index 0000000..048734f --- /dev/null +++ b/components/options/ClearLogSection.tsx @@ -0,0 +1,48 @@ +import { useTranslation } from '../../hooks/useTranslation'; +import { useClearLogStore } from '../../store/clearLog'; +import type { TranslationKey } from '../../utils/i18n'; + +export function ClearLogSection() { + const t = useTranslation(); + const entries = useClearLogStore((s) => s.entries); + const clearLog = useClearLogStore((s) => s.clearLog); + + return ( +
+
+

+ {t('clearLogTitle')} +

+ {entries.length > 0 && ( + + )} +
+ {entries.length === 0 ? ( +

{t('clearLogEmpty')}

+ ) : ( +
    + {entries.map((entry) => ( +
  • +
    + {entry.hostname} + + {new Date(entry.timestamp).toLocaleString()} + +
    +

    + {entry.types.map((id) => t(`dt_${id}` as TranslationKey)).join(', ')} + {entry.linkedTargets.length > 0 && + ` + ${t('clearLogLinkedCount', { count: entry.linkedTargets.length })}`} +

    +
  • + ))} +
+ )} +
+ ); +} diff --git a/components/options/GlobalSection.tsx b/components/options/GlobalSection.tsx index 47cdb82..9420bda 100644 --- a/components/options/GlobalSection.tsx +++ b/components/options/GlobalSection.tsx @@ -18,6 +18,7 @@ interface GlobalSectionProps { historyStatus: ClearStatus; onClearAllHistory: () => void; onDeleteHistoryItem: (url: string) => void; + hasProtectedSites: boolean; } export function GlobalSection({ @@ -32,6 +33,7 @@ export function GlobalSection({ historyStatus, onClearAllHistory, onDeleteHistoryItem, + hasProtectedSites, }: GlobalSectionProps) { const t = useTranslation(); @@ -51,6 +53,7 @@ export function GlobalSection({ onConfirm={onClear} /> + {hasProtectedSites &&

{t('protectedSitesGlobalNote')}

} {showHistory && ( void; linkedOrigins: string; onLinkedOriginsChange: (value: string) => void; + protectedSites: string; + onProtectedSitesChange: (value: string) => void; siteFilter: string; onSiteFilterChange: (value: string) => void; bulkStatus: ClearStatus; @@ -48,6 +51,8 @@ export function PerSiteSection({ onUseOriginMappingsChange, linkedOrigins, onLinkedOriginsChange, + protectedSites, + onProtectedSitesChange, siteFilter, onSiteFilterChange, bulkStatus, @@ -104,6 +109,8 @@ export function PerSiteSection({ + +
{!isGeckoBased() && ( diff --git a/components/options/ProtectedSitesSection.tsx b/components/options/ProtectedSitesSection.tsx new file mode 100644 index 0000000..ea50eb7 --- /dev/null +++ b/components/options/ProtectedSitesSection.tsx @@ -0,0 +1,24 @@ +import { useTranslation } from '../../hooks/useTranslation'; + +interface ProtectedSitesSectionProps { + value: string; + onChange: (value: string) => void; +} + +export function ProtectedSitesSection({ value, onChange }: ProtectedSitesSectionProps) { + const t = useTranslation(); + + return ( +
+ +