diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-05-02 11:46:13 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-05-02 11:46:13 +0900 |
| commit | 023d0fcfce575030ee503c5f60df8c28dba7ab07 (patch) | |
| tree | 2f8ab3915f338232619ec66bb272e4756a96e021 /src/client/components/CreateDeckModal.tsx | |
| parent | 13a3d16ffc88845d7bc65fb0778da9aaff53b653 (diff) | |
| download | kioku-023d0fcfce575030ee503c5f60df8c28dba7ab07.tar.gz kioku-023d0fcfce575030ee503c5f60df8c28dba7ab07.tar.zst kioku-023d0fcfce575030ee503c5f60df8c28dba7ab07.zip | |
feat(decks): make deck CRUD work fully offline-first
Create / Edit / Delete deck modals now write through localDeckRepository
and fire-and-forget syncActionAtom so the change is pushed when the
network is up. EditDeckModal reads its note-type list from the
local-first noteTypesAtom instead of fetching, and the "reconnect to..."
guards on the submit buttons are gone — the user can keep working while
offline.
Soft-delete intentionally does NOT cascade to notes/cards, matching the
server's existing deck.softDelete: the deck disappears from listings and
its children become unreachable that way.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'src/client/components/CreateDeckModal.tsx')
| -rw-r--r-- | src/client/components/CreateDeckModal.tsx | 53 |
1 files changed, 21 insertions, 32 deletions
diff --git a/src/client/components/CreateDeckModal.tsx b/src/client/components/CreateDeckModal.tsx index 34d46e7..11dc712 100644 --- a/src/client/components/CreateDeckModal.tsx +++ b/src/client/components/CreateDeckModal.tsx @@ -1,7 +1,7 @@ -import { useAtomValue } from "jotai"; +import { useAtomValue, useSetAtom } from "jotai"; import { type FormEvent, useState } from "react"; -import { ApiClientError, apiClient } from "../api"; -import { isOnlineAtom } from "../atoms"; +import { syncActionAtom, userAtom } from "../atoms"; +import { localDeckRepository } from "../db/repositories"; interface CreateDeckModalProps { isOpen: boolean; @@ -18,7 +18,8 @@ export function CreateDeckModal({ const [description, setDescription] = useState(""); const [error, setError] = useState<string | null>(null); const [isSubmitting, setIsSubmitting] = useState(false); - const isOnline = useAtomValue(isOnlineAtom); + const user = useAtomValue(userAtom); + const triggerSync = useSetAtom(syncActionAtom); const resetForm = () => { setName(""); @@ -33,40 +34,29 @@ export function CreateDeckModal({ const handleSubmit = async (e: FormEvent) => { e.preventDefault(); + if (!user) { + setError("You must be signed in to create a deck."); + return; + } setError(null); setIsSubmitting(true); try { - const res = await apiClient.rpc.api.decks.$post( - { - json: { - name: name.trim(), - description: description.trim() || null, - }, - }, - { - headers: apiClient.getAuthHeader(), - }, - ); - - if (!res.ok) { - const errorBody = await res.json().catch(() => ({})); - throw new ApiClientError( - (errorBody as { error?: string }).error || - `Request failed with status ${res.status}`, - res.status, - ); - } + await localDeckRepository.create({ + userId: user.id, + name: name.trim(), + description: description.trim() || null, + defaultNoteTypeId: null, + }); resetForm(); onDeckCreated(); onClose(); - } catch (err) { - if (err instanceof ApiClientError) { - setError(err.message); - } else { - setError("Failed to create deck. Please try again."); - } + // Fire-and-forget: server push will be retried by the sync engine if it + // fails (e.g. offline), so we deliberately do not await or surface errors. + void triggerSync().catch(() => {}); + } catch { + setError("Failed to create deck. Please try again."); } finally { setIsSubmitting(false); } @@ -163,8 +153,7 @@ export function CreateDeckModal({ </button> <button type="submit" - disabled={isSubmitting || !name.trim() || !isOnline} - title={!isOnline ? "Reconnect to create a deck" : undefined} + disabled={isSubmitting || !name.trim()} className="px-4 py-2 bg-primary hover:bg-primary-dark text-white font-medium rounded-lg transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed" > {isSubmitting ? "Creating..." : "Create Deck"} |
