diff --git a/apps/extension/src/entrypoints/pages/components/Settings.tsx b/apps/extension/src/entrypoints/pages/components/Settings.tsx new file mode 100644 index 0000000..aa2ca87 --- /dev/null +++ b/apps/extension/src/entrypoints/pages/components/Settings.tsx @@ -0,0 +1,152 @@ +import { Download, Upload } from "lucide-solid"; +import { createSignal, Show } from "solid-js"; +import type { BangAliases, RankingsV2 } from "@/utils/storage"; +import { + bangAliasesData, + items, + quickBangsData, + syncedRankings, +} from "@/utils/storage"; + +interface SyncSettingsBackup { + version: 1; + exportedAt: string; + data: { + rankings: RankingsV2; + quick_bangs: string[]; + bang_aliases: BangAliases; + }; +} + +const Settings = () => { + const [importStatus, setImportStatus] = createSignal<{ + type: "success" | "error"; + message: string; + } | null>(null); + + const exportSettings = () => { + const backup: SyncSettingsBackup = { + version: 1, + exportedAt: new Date().toISOString(), + data: { + rankings: syncedRankings() ?? {}, + quick_bangs: quickBangsData() ?? [], + bang_aliases: bangAliasesData() ?? {}, + }, + }; + + const blob = new Blob([JSON.stringify(backup, null, 2)], { + type: "application/json", + }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = `searchtuner-backup-${new Date().toISOString().split("T")[0]}.json`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + }; + + const importSettings = () => { + const input = document.createElement("input"); + input.type = "file"; + input.accept = ".json"; + input.onchange = async (e) => { + const file = (e.target as HTMLInputElement).files?.[0]; + if (!file) return; + + try { + const text = await file.text(); + const backup = JSON.parse(text) as SyncSettingsBackup; + + // Validate backup structure + if ( + !backup.version || + !backup.data || + typeof backup.data !== "object" + ) { + throw new Error("Invalid backup file format"); + } + + // Import each setting if present + if (backup.data.rankings && typeof backup.data.rankings === "object") { + await items.rankings.setValue(backup.data.rankings); + } + + if (Array.isArray(backup.data.quick_bangs)) { + await items.quick_bangs.setValue(backup.data.quick_bangs); + } + + if ( + backup.data.bang_aliases && + typeof backup.data.bang_aliases === "object" + ) { + await items.bang_aliases.setValue(backup.data.bang_aliases); + } + + setImportStatus({ + type: "success", + message: "Settings imported successfully!", + }); + + // Clear status after 3 seconds + setTimeout(() => setImportStatus(null), 3000); + } catch (err) { + setImportStatus({ + type: "error", + message: + err instanceof Error ? err.message : "Failed to import settings", + }); + + // Clear status after 5 seconds + setTimeout(() => setImportStatus(null), 5000); + } + }; + input.click(); + }; + + return ( +
+ Export your synced settings to a file or import from a previous + backup. This includes your domain rankings, quick bangs, and bang + aliases. +
+ ++ {status().message} +
+ )} +