learn and share notes on atproto (wip) 馃
malfestio.stormlightlabs.org/
readability
solid
axum
atproto
srs
1import { createRoot, createSignal } from "solid-js";
2import { api } from "./api";
3import type { Persona, User, UserPreferences } from "./model";
4
5export type AuthState = {
6 user: User | null;
7 accessJwt: string | null;
8 refreshJwt: string | null;
9 isAuthenticated: boolean;
10};
11
12function createAuthStore() {
13 const [user, setUser] = createSignal<User | null>(
14 localStorage.getItem("did")
15 ? { did: localStorage.getItem("did")!, handle: localStorage.getItem("handle") || "" }
16 : null,
17 );
18 const [accessJwt, setAccessJwt] = createSignal(localStorage.getItem("accessJwt"));
19 const [_, setRefreshJwt] = createSignal(localStorage.getItem("refreshJwt"));
20
21 const login = (data: { accessJwt: string; refreshJwt: string; did: string; handle: string }) => {
22 setAccessJwt(data.accessJwt);
23 setRefreshJwt(data.refreshJwt);
24 setUser({ did: data.did, handle: data.handle });
25
26 localStorage.setItem("accessJwt", data.accessJwt);
27 localStorage.setItem("refreshJwt", data.refreshJwt);
28 localStorage.setItem("did", data.did);
29 localStorage.setItem("handle", data.handle);
30 };
31
32 const logout = () => {
33 setUser(null);
34 setAccessJwt(null);
35 setRefreshJwt(null);
36 localStorage.clear();
37 };
38
39 const [loading, setLoading] = createSignal(true);
40
41 const init = async () => {
42 await new Promise((resolve) => setTimeout(resolve, 1500));
43 setLoading(false);
44 };
45
46 init();
47
48 return { user, accessJwt, isAuthenticated: () => !!accessJwt(), login, logout, loading };
49}
50
51export const authStore = createRoot(createAuthStore);
52
53function createPrefStore() {
54 const [prefs, setPrefs] = createSignal<UserPreferences | null>(null);
55 const [loading, setLoading] = createSignal(false);
56
57 const fetchPrefs = async () => {
58 if (!authStore.isAuthenticated()) return;
59 setLoading(true);
60 try {
61 const res = await api.getPreferences();
62 if (res.ok) {
63 setPrefs(await res.json());
64 }
65 } catch (e) {
66 console.error("Failed to fetch preferences:", e);
67 } finally {
68 setLoading(false);
69 }
70 };
71
72 const updatePreferences = async (
73 updates: {
74 persona?: Persona;
75 complete_onboarding?: boolean;
76 density_mode?: "compact" | "comfortable" | "spacious";
77 },
78 ) => {
79 try {
80 const res = await api.updatePreferences(updates);
81 if (res.ok) {
82 setPrefs(await res.json());
83 }
84 } catch (e) {
85 console.error("Failed to update preferences:", e);
86 }
87 };
88
89 const needsOnboarding = () => {
90 const preferences = prefs();
91 return preferences !== null && preferences.onboarding_completed_at === null;
92 };
93
94 const persona = () => prefs()?.persona ?? null;
95 const densityMode = () => prefs()?.density_mode ?? "comfortable";
96 return { prefs, loading, fetchPrefs, updatePreferences, needsOnboarding, persona, densityMode };
97}
98
99export const prefStore = createRoot(createPrefStore);