+33
-3
package-lock.json
+33
-3
package-lock.json
···
26
26
"lucide-react": "^0.544.0",
27
27
"react": "^18.3.1",
28
28
"react-dom": "^18.3.1",
29
-
"zod": "^4.2.1"
29
+
"zod": "^4.2.1",
30
+
"zustand": "^5.0.9"
30
31
},
31
32
"devDependencies": {
32
33
"@types/jszip": "^3.4.0",
···
2859
2860
"version": "19.1.14",
2860
2861
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.14.tgz",
2861
2862
"integrity": "sha512-ukd93VGzaNPMAUPy0gRDSC57UuQbnH9Kussp7HBjM06YFi9uZTFhOvMSO2OKqXm1rSgzOE+pVx1k1PYHGwlc8Q==",
2862
-
"dev": true,
2863
+
"devOptional": true,
2863
2864
"license": "MIT",
2864
2865
"dependencies": {
2865
2866
"csstype": "^3.0.2"
···
4242
4243
"version": "3.1.3",
4243
4244
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
4244
4245
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
4245
-
"dev": true,
4246
+
"devOptional": true,
4246
4247
"license": "MIT"
4247
4248
},
4248
4249
"node_modules/date-fns": {
···
8279
8280
"license": "MIT",
8280
8281
"funding": {
8281
8282
"url": "https://github.com/sponsors/colinhacks"
8283
+
}
8284
+
},
8285
+
"node_modules/zustand": {
8286
+
"version": "5.0.9",
8287
+
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.9.tgz",
8288
+
"integrity": "sha512-ALBtUj0AfjJt3uNRQoL1tL2tMvj6Gp/6e39dnfT6uzpelGru8v1tPOGBzayOWbPJvujM8JojDk3E1LxeFisBNg==",
8289
+
"license": "MIT",
8290
+
"engines": {
8291
+
"node": ">=12.20.0"
8292
+
},
8293
+
"peerDependencies": {
8294
+
"@types/react": ">=18.0.0",
8295
+
"immer": ">=9.0.6",
8296
+
"react": ">=18.0.0",
8297
+
"use-sync-external-store": ">=1.2.0"
8298
+
},
8299
+
"peerDependenciesMeta": {
8300
+
"@types/react": {
8301
+
"optional": true
8302
+
},
8303
+
"immer": {
8304
+
"optional": true
8305
+
},
8306
+
"react": {
8307
+
"optional": true
8308
+
},
8309
+
"use-sync-external-store": {
8310
+
"optional": true
8311
+
}
8282
8312
}
8283
8313
}
8284
8314
}
+2
-1
package.json
+2
-1
package.json
+1
-1
src/App.tsx
+1
-1
src/App.tsx
···
14
14
import type { UserSettings, SearchResult } from "./types";
15
15
import { apiClient } from "./lib/api/client";
16
16
import { ATPROTO_APPS } from "./config/atprotoApps";
17
-
import { useSettings } from "./contexts/SettingsContext";
17
+
import { useSettings } from "./stores/useSettingsStore";
18
18
19
19
// Lazy load page components
20
20
const LoginPage = lazy(() => import("./pages/Login"));
-87
src/contexts/SettingsContext.tsx
-87
src/contexts/SettingsContext.tsx
···
1
-
import React, {
2
-
createContext,
3
-
useContext,
4
-
useState,
5
-
useEffect,
6
-
useCallback,
7
-
ReactNode,
8
-
} from "react";
9
-
import { DEFAULT_SETTINGS, UserSettings } from "../types/settings";
10
-
11
-
interface SettingsContextType {
12
-
settings: UserSettings;
13
-
updateSettings: (newSettings: Partial<UserSettings>) => void;
14
-
resetSettings: () => void;
15
-
isLoading: boolean;
16
-
}
17
-
18
-
const SettingsContext = createContext<SettingsContextType | undefined>(
19
-
undefined,
20
-
);
21
-
22
-
export const useSettings = (): SettingsContextType => {
23
-
const context = useContext(SettingsContext);
24
-
if (!context) {
25
-
throw new Error("useSettings must be used within a SettingsProvider");
26
-
}
27
-
return context;
28
-
};
29
-
30
-
interface SettingsProviderProps {
31
-
children: ReactNode;
32
-
}
33
-
34
-
export const SettingsProvider: React.FC<SettingsProviderProps> = ({
35
-
children,
36
-
}) => {
37
-
const [settings, setSettings] = useState<UserSettings>(DEFAULT_SETTINGS);
38
-
const [isLoading, setIsLoading] = useState(true);
39
-
40
-
// Load settings from localStorage on mount
41
-
useEffect(() => {
42
-
try {
43
-
const saved = localStorage.getItem("atlast_settings");
44
-
if (saved) {
45
-
const parsed = JSON.parse(saved);
46
-
setSettings(parsed);
47
-
}
48
-
} catch (error) {
49
-
console.error("Failed to load settings:", error);
50
-
} finally {
51
-
setIsLoading(false);
52
-
}
53
-
}, []);
54
-
55
-
// Save settings to localStorage whenever they change
56
-
useEffect(() => {
57
-
if (!isLoading) {
58
-
try {
59
-
localStorage.setItem("atlast_settings", JSON.stringify(settings));
60
-
} catch (error) {
61
-
console.error("Failed to save settings:", error);
62
-
}
63
-
}
64
-
}, [settings, isLoading]);
65
-
66
-
const updateSettings = useCallback((newSettings: Partial<UserSettings>) => {
67
-
setSettings((prev) => ({ ...prev, ...newSettings }));
68
-
}, []);
69
-
70
-
const resetSettings = useCallback(() => {
71
-
setSettings(DEFAULT_SETTINGS);
72
-
localStorage.removeItem("atlast_settings");
73
-
}, []);
74
-
75
-
const value: SettingsContextType = {
76
-
settings,
77
-
updateSettings,
78
-
resetSettings,
79
-
isLoading,
80
-
};
81
-
82
-
return (
83
-
<SettingsContext.Provider value={value}>
84
-
{children}
85
-
</SettingsContext.Provider>
86
-
);
87
-
};
+1
-4
src/main.tsx
+1
-4
src/main.tsx
···
1
1
import React from "react";
2
2
import ReactDOM from "react-dom/client";
3
3
import App from "./App";
4
-
import { SettingsProvider } from "./contexts/SettingsContext";
5
4
import "./index.css";
6
5
7
6
ReactDOM.createRoot(document.getElementById("root")!).render(
8
7
<React.StrictMode>
9
-
<SettingsProvider>
10
-
<App />
11
-
</SettingsProvider>
8
+
<App />
12
9
</React.StrictMode>,
13
10
);
+67
src/stores/useSettingsStore.ts
+67
src/stores/useSettingsStore.ts
···
1
+
import { create } from "zustand";
2
+
import { persist, createJSONStorage } from "zustand/middleware";
3
+
import { DEFAULT_SETTINGS, UserSettings } from "../types/settings";
4
+
5
+
interface SettingsStore {
6
+
settings: UserSettings;
7
+
isLoading: boolean;
8
+
updateSettings: (newSettings: Partial<UserSettings>) => void;
9
+
resetSettings: () => void;
10
+
setIsLoading: (loading: boolean) => void;
11
+
}
12
+
13
+
export const useSettingsStore = create<SettingsStore>()(
14
+
persist(
15
+
(set) => ({
16
+
settings: DEFAULT_SETTINGS,
17
+
isLoading: true,
18
+
19
+
updateSettings: (newSettings) =>
20
+
set((state) => ({
21
+
settings: { ...state.settings, ...newSettings },
22
+
})),
23
+
24
+
resetSettings: () =>
25
+
set({
26
+
settings: DEFAULT_SETTINGS,
27
+
}),
28
+
29
+
setIsLoading: (loading) => set({ isLoading: loading }),
30
+
}),
31
+
{
32
+
name: "atlast-settings",
33
+
storage: createJSONStorage(() => {
34
+
// SSR-safe storage
35
+
if (typeof window === "undefined") {
36
+
return {
37
+
getItem: () => null,
38
+
setItem: () => {},
39
+
removeItem: () => {},
40
+
};
41
+
}
42
+
return window.localStorage;
43
+
}),
44
+
// Called after rehydration from storage
45
+
onRehydrateStorage: () => (state) => {
46
+
if (state) {
47
+
state.setIsLoading(false);
48
+
}
49
+
},
50
+
},
51
+
),
52
+
);
53
+
54
+
// Backwards-compatible hook that matches the old context API
55
+
export const useSettings = () => {
56
+
const settings = useSettingsStore((state) => state.settings);
57
+
const updateSettings = useSettingsStore((state) => state.updateSettings);
58
+
const resetSettings = useSettingsStore((state) => state.resetSettings);
59
+
const isLoading = useSettingsStore((state) => state.isLoading);
60
+
61
+
return {
62
+
settings,
63
+
updateSettings,
64
+
resetSettings,
65
+
isLoading,
66
+
};
67
+
};