From 0615f3587890fb34bc8e25b12e008d43668a0746 Mon Sep 17 00:00:00 2001 From: Shubham Bhardwaj <15147944+ShoeBoom@users.noreply.github.com> Date: Fri, 16 Jan 2026 17:54:17 -0500 Subject: [PATCH] feat(settings): add backup & restore UI and import/export logic Add a Settings component providing Backup & Restore functionality for synced user settings. Implement exportSettings to serialize current rankings, quick bangs, and bang aliases into a JSON file and trigger a download. Implement importSettings to accept a JSON file, validate its structure, and apply contained rankings, quick_bangs, and bang_aliases to storage with error handling and user feedback. Wire the new Settings component into the main page navigation. Rename the lucide-solid Settings icon import to SettingsIcon to avoid naming conflict with the component. Provide transient success/error status messages that auto-clear after a few seconds. Validate backup format and handle malformed input with a clear error message. --- .../entrypoints/pages/components/Settings.tsx | 152 ++++++++++++++++++ apps/extension/src/entrypoints/pages/main.tsx | 15 +- 2 files changed, 161 insertions(+), 6 deletions(-) create mode 100644 apps/extension/src/entrypoints/pages/components/Settings.tsx 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 ( +
+
+

Backup & Restore

+

+ Export your synced settings to a file or import from a previous + backup. This includes your domain rankings, quick bangs, and bang + aliases. +

+ +
+ + +
+ + + {(status) => ( +

+ {status().message} +

+ )} +
+
+
+ ); +}; + +export default Settings; diff --git a/apps/extension/src/entrypoints/pages/main.tsx b/apps/extension/src/entrypoints/pages/main.tsx index dde0cff..509cea4 100644 --- a/apps/extension/src/entrypoints/pages/main.tsx +++ b/apps/extension/src/entrypoints/pages/main.tsx @@ -1,10 +1,16 @@ import { A, HashRouter, Route, useNavigate } from "@solidjs/router"; -import { ArrowUpDown, Hash, Info, Settings } from "lucide-solid"; +import { + ArrowUpDown, + Hash, + Info, + Settings as SettingsIcon, +} from "lucide-solid"; import { render } from "solid-js/web"; import logo from "@/assets/icon.webp"; import { RankingsTable } from "@/entrypoints/pages/components/rankingsTable"; import About from "./components/About"; import Bangs from "./components/Bangs"; +import Settings from "./components/Settings"; function App() { return ( @@ -18,7 +24,7 @@ function App() { Rankings - + Settings @@ -44,10 +50,7 @@ function App() { return <>; }} /> -
Settings
} - /> +