From f8e4be9b36a16969ac53bd9ce12ce8064be10196 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sun, 4 Jan 2026 17:43:59 +0900 Subject: refactor(client): migrate state management from React Context to Jotai MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace AuthProvider and SyncProvider with Jotai atoms for more granular state management and better performance. This migration: - Creates atoms for auth, sync, decks, cards, noteTypes, and study state - Uses atomFamily for parameterized state (e.g., cards by deckId) - Introduces StoreInitializer component for subscription initialization - Updates all components and pages to use useAtomValue/useSetAtom - Updates all tests to use Jotai Provider with createStore pattern 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- src/client/atoms/auth.ts | 57 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 src/client/atoms/auth.ts (limited to 'src/client/atoms/auth.ts') diff --git a/src/client/atoms/auth.ts b/src/client/atoms/auth.ts new file mode 100644 index 0000000..f618ccf --- /dev/null +++ b/src/client/atoms/auth.ts @@ -0,0 +1,57 @@ +import { atom, useSetAtom } from "jotai"; +import { useEffect } from "react"; +import { apiClient, type User } from "../api/client"; + +// Primitive atoms +export const userAtom = atom(null); +export const authLoadingAtom = atom(true); + +// Derived atom - checks if user is authenticated via apiClient +export const isAuthenticatedAtom = atom((get) => { + // We need to trigger re-evaluation when user changes + get(userAtom); + return apiClient.isAuthenticated(); +}); + +// Action atom - login +export const loginAtom = atom( + null, + async ( + _get, + set, + { username, password }: { username: string; password: string }, + ) => { + const response = await apiClient.login(username, password); + set(userAtom, response.user); + }, +); + +// Action atom - logout +export const logoutAtom = atom(null, (_get, set) => { + apiClient.logout(); + set(userAtom, null); +}); + +// Hook to initialize auth state and subscribe to session expiration +export function useAuthInit() { + const setAuthLoading = useSetAtom(authLoadingAtom); + const setUser = useSetAtom(userAtom); + + useEffect(() => { + // Check for existing auth on mount + const tokens = apiClient.getTokens(); + if (tokens) { + // We have tokens stored, but we don't have user info cached + // For now, just set authenticated state. User info will be fetched when needed. + } + setAuthLoading(false); + + // Subscribe to session expired events from the API client + const unsubscribe = apiClient.onSessionExpired(() => { + apiClient.logout(); + setUser(null); + }); + + return unsubscribe; + }, [setAuthLoading, setUser]); +} -- cgit v1.2.3-70-g09d2