aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/client/components/CreateDeckModal.tsx
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-05-02 11:46:13 +0900
committernsfisis <nsfisis@gmail.com>2026-05-02 11:46:13 +0900
commit023d0fcfce575030ee503c5f60df8c28dba7ab07 (patch)
tree2f8ab3915f338232619ec66bb272e4756a96e021 /src/client/components/CreateDeckModal.tsx
parent13a3d16ffc88845d7bc65fb0778da9aaff53b653 (diff)
downloadkioku-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.tsx53
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"}