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/pages/NoteTypesPage.tsx | 271 ++++++++++++++++--------------------- 1 file changed, 118 insertions(+), 153 deletions(-) (limited to 'src/client/pages/NoteTypesPage.tsx') diff --git a/src/client/pages/NoteTypesPage.tsx b/src/client/pages/NoteTypesPage.tsx index 5b50c61..8e742a7 100644 --- a/src/client/pages/NoteTypesPage.tsx +++ b/src/client/pages/NoteTypesPage.tsx @@ -4,31 +4,119 @@ import { faLayerGroup, faPen, faPlus, - faSpinner, faTrash, } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { useCallback, useEffect, useState } from "react"; +import { useAtomValue, useSetAtom } from "jotai"; +import { Suspense, useState, useTransition } from "react"; import { Link } from "wouter"; -import { ApiClientError, apiClient } from "../api"; +import { type NoteType, noteTypesAtom } from "../atoms"; import { CreateNoteTypeModal } from "../components/CreateNoteTypeModal"; import { DeleteNoteTypeModal } from "../components/DeleteNoteTypeModal"; +import { ErrorBoundary } from "../components/ErrorBoundary"; +import { LoadingSpinner } from "../components/LoadingSpinner"; import { NoteTypeEditor } from "../components/NoteTypeEditor"; -interface NoteType { - id: string; - name: string; - frontTemplate: string; - backTemplate: string; - isReversible: boolean; - createdAt: string; - updatedAt: string; +function NoteTypeList({ + onEditNoteType, + onDeleteNoteType, +}: { + onEditNoteType: (id: string) => void; + onDeleteNoteType: (noteType: NoteType) => void; +}) { + const noteTypes = useAtomValue(noteTypesAtom); + + if (noteTypes.length === 0) { + return ( +
+
+
+

+ No note types yet +

+

+ Create a note type to define how your cards are structured +

+
+ ); + } + + return ( +
+ {noteTypes.map((noteType, index) => ( +
+
+
+
+
+
+ + Front: {noteType.frontTemplate} + + + Back: {noteType.backTemplate} + + {noteType.isReversible && ( + + Reversible + + )} +
+
+
+ + +
+
+
+ ))} +
+ ); } export function NoteTypesPage() { - const [noteTypes, setNoteTypes] = useState([]); - const [isLoading, setIsLoading] = useState(true); - const [error, setError] = useState(null); + const reloadNoteTypes = useSetAtom(noteTypesAtom); + const [, startTransition] = useTransition(); + const [isCreateModalOpen, setIsCreateModalOpen] = useState(false); const [editingNoteTypeId, setEditingNoteTypeId] = useState( null, @@ -37,30 +125,11 @@ export function NoteTypesPage() { null, ); - const fetchNoteTypes = useCallback(async () => { - setIsLoading(true); - setError(null); - - try { - const res = await apiClient.rpc.api["note-types"].$get(); - const data = await apiClient.handleResponse<{ noteTypes: NoteType[] }>( - res, - ); - setNoteTypes(data.noteTypes); - } catch (err) { - if (err instanceof ApiClientError) { - setError(err.message); - } else { - setError("Failed to load note types. Please try again."); - } - } finally { - setIsLoading(false); - } - }, []); - - useEffect(() => { - fetchNoteTypes(); - }, [fetchNoteTypes]); + const handleNoteTypeMutation = () => { + startTransition(() => { + reloadNoteTypes(); + }); + }; return (
@@ -107,140 +176,36 @@ export function NoteTypesPage() {
- {/* Loading State */} - {isLoading && ( -
-
- )} - - {/* Error State */} - {error && ( -
- {error} - -
- )} - - {/* Empty State */} - {!isLoading && !error && noteTypes.length === 0 && ( -
-
-
-

- No note types yet -

-

- Create a note type to define how your cards are structured -

-
- )} - - {/* Note Type List */} - {!isLoading && !error && noteTypes.length > 0 && ( -
- {noteTypes.map((noteType, index) => ( -
-
-
-
-
-
- - Front: {noteType.frontTemplate} - - - Back: {noteType.backTemplate} - - {noteType.isReversible && ( - - Reversible - - )} -
-
-
- - -
-
-
- ))} -
- )} + + {/* Modals */} setIsCreateModalOpen(false)} - onNoteTypeCreated={fetchNoteTypes} + onNoteTypeCreated={handleNoteTypeMutation} /> setEditingNoteTypeId(null)} - onNoteTypeUpdated={fetchNoteTypes} + onNoteTypeUpdated={handleNoteTypeMutation} /> setDeletingNoteType(null)} - onNoteTypeDeleted={fetchNoteTypes} + onNoteTypeDeleted={handleNoteTypeMutation} /> ); -- cgit v1.2.3-70-g09d2