aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/client/components/EditNoteTypeModal.tsx
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-05-02 11:58:13 +0900
committernsfisis <nsfisis@gmail.com>2026-05-02 11:58:13 +0900
commitd47d1a014a71ae65cbbf1b384eed87c6fe078b07 (patch)
tree76c3af22e10f963104f84f3d680c5dddfc8b2be6 /src/client/components/EditNoteTypeModal.tsx
parent023d0fcfce575030ee503c5f60df8c28dba7ab07 (diff)
downloadkioku-d47d1a014a71ae65cbbf1b384eed87c6fe078b07.tar.gz
kioku-d47d1a014a71ae65cbbf1b384eed87c6fe078b07.tar.zst
kioku-d47d1a014a71ae65cbbf1b384eed87c6fe078b07.zip
feat(note-types): make note type CRUD work fully offline-firstHEADmain
CreateNoteTypeModal, DeleteNoteTypeModal, and the NoteTypeEditor (which covers field add/edit/delete/reorder) now write through the local IndexedDB repositories and fire-and-forget syncActionAtom, mirroring the deck-CRUD pattern. The dead EditNoteTypeModal — never imported — is removed. The local hasNotes / hasNoteFieldValues guards mirror the server's delete-time checks so a note type with attached notes, or a field with saved values, can't be silently soft-deleted offline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'src/client/components/EditNoteTypeModal.tsx')
-rw-r--r--src/client/components/EditNoteTypeModal.tsx226
1 files changed, 0 insertions, 226 deletions
diff --git a/src/client/components/EditNoteTypeModal.tsx b/src/client/components/EditNoteTypeModal.tsx
deleted file mode 100644
index 5916ff0..0000000
--- a/src/client/components/EditNoteTypeModal.tsx
+++ /dev/null
@@ -1,226 +0,0 @@
-import { useAtomValue } from "jotai";
-import { type FormEvent, useEffect, useState } from "react";
-import { ApiClientError, apiClient } from "../api";
-import { isOnlineAtom } from "../atoms";
-
-interface NoteType {
- id: string;
- name: string;
- frontTemplate: string;
- backTemplate: string;
- isReversible: boolean;
-}
-
-interface EditNoteTypeModalProps {
- isOpen: boolean;
- noteType: NoteType | null;
- onClose: () => void;
- onNoteTypeUpdated: () => void;
-}
-
-export function EditNoteTypeModal({
- isOpen,
- noteType,
- onClose,
- onNoteTypeUpdated,
-}: EditNoteTypeModalProps) {
- const [name, setName] = useState("");
- const [frontTemplate, setFrontTemplate] = useState("");
- const [backTemplate, setBackTemplate] = useState("");
- const [isReversible, setIsReversible] = useState(false);
- const [error, setError] = useState<string | null>(null);
- const [isSubmitting, setIsSubmitting] = useState(false);
- const isOnline = useAtomValue(isOnlineAtom);
-
- // Sync form state when noteType changes
- useEffect(() => {
- if (noteType) {
- setName(noteType.name);
- setFrontTemplate(noteType.frontTemplate);
- setBackTemplate(noteType.backTemplate);
- setIsReversible(noteType.isReversible);
- setError(null);
- }
- }, [noteType]);
-
- const handleClose = () => {
- setError(null);
- onClose();
- };
-
- const handleSubmit = async (e: FormEvent) => {
- e.preventDefault();
- if (!noteType) return;
-
- setError(null);
- setIsSubmitting(true);
-
- try {
- const res = await apiClient.rpc.api["note-types"][":id"].$put({
- param: { id: noteType.id },
- json: {
- name: name.trim(),
- frontTemplate: frontTemplate.trim(),
- backTemplate: backTemplate.trim(),
- isReversible,
- },
- });
- await apiClient.handleResponse(res);
-
- onNoteTypeUpdated();
- onClose();
- } catch (err) {
- if (err instanceof ApiClientError) {
- setError(err.message);
- } else {
- setError("Failed to update note type. Please try again.");
- }
- } finally {
- setIsSubmitting(false);
- }
- };
-
- if (!isOpen || !noteType) {
- return null;
- }
-
- return (
- <div
- role="dialog"
- aria-modal="true"
- aria-labelledby="edit-note-type-title"
- className="fixed inset-0 bg-ink/40 backdrop-blur-sm flex items-center justify-center z-50 p-4 animate-fade-in"
- onClick={(e) => {
- if (e.target === e.currentTarget) {
- handleClose();
- }
- }}
- onKeyDown={(e) => {
- if (e.key === "Escape") {
- handleClose();
- }
- }}
- >
- <div className="bg-white rounded-2xl shadow-xl w-full max-w-md animate-scale-in">
- <div className="p-6">
- <h2
- id="edit-note-type-title"
- className="font-display text-xl font-medium text-ink mb-6"
- >
- Edit Note Type
- </h2>
-
- <form onSubmit={handleSubmit} className="space-y-4">
- {error && (
- <div
- role="alert"
- className="bg-error/5 text-error text-sm px-4 py-3 rounded-lg border border-error/20"
- >
- {error}
- </div>
- )}
-
- <div>
- <label
- htmlFor="edit-note-type-name"
- className="block text-sm font-medium text-slate mb-1.5"
- >
- Name
- </label>
- <input
- id="edit-note-type-name"
- type="text"
- value={name}
- onChange={(e) => setName(e.target.value)}
- required
- maxLength={255}
- disabled={isSubmitting}
- className="w-full px-4 py-2.5 bg-ivory border border-border rounded-lg text-slate placeholder-muted transition-all duration-200 hover:border-muted focus:border-primary focus:ring-2 focus:ring-primary/10 disabled:opacity-50 disabled:cursor-not-allowed"
- />
- </div>
-
- <div>
- <label
- htmlFor="edit-front-template"
- className="block text-sm font-medium text-slate mb-1.5"
- >
- Front Template
- </label>
- <textarea
- id="edit-front-template"
- value={frontTemplate}
- onChange={(e) => setFrontTemplate(e.target.value)}
- required
- maxLength={1000}
- disabled={isSubmitting}
- rows={3}
- className="w-full px-4 py-2.5 bg-ivory border border-border rounded-lg text-slate placeholder-muted font-mono text-sm transition-all duration-200 hover:border-muted focus:border-primary focus:ring-2 focus:ring-primary/10 disabled:opacity-50 disabled:cursor-not-allowed resize-y"
- />
- <p className="text-muted text-xs mt-1">
- Use {"{{FieldName}}"} to insert field values
- </p>
- </div>
-
- <div>
- <label
- htmlFor="edit-back-template"
- className="block text-sm font-medium text-slate mb-1.5"
- >
- Back Template
- </label>
- <textarea
- id="edit-back-template"
- value={backTemplate}
- onChange={(e) => setBackTemplate(e.target.value)}
- required
- maxLength={1000}
- disabled={isSubmitting}
- rows={3}
- className="w-full px-4 py-2.5 bg-ivory border border-border rounded-lg text-slate placeholder-muted font-mono text-sm transition-all duration-200 hover:border-muted focus:border-primary focus:ring-2 focus:ring-primary/10 disabled:opacity-50 disabled:cursor-not-allowed resize-y"
- />
- </div>
-
- <div className="flex items-center gap-3">
- <input
- id="edit-is-reversible"
- type="checkbox"
- checked={isReversible}
- onChange={(e) => setIsReversible(e.target.checked)}
- disabled={isSubmitting}
- className="w-4 h-4 text-primary bg-ivory border-border rounded focus:ring-primary/20 focus:ring-2 disabled:opacity-50"
- />
- <label
- htmlFor="edit-is-reversible"
- className="text-sm font-medium text-slate"
- >
- Create reversed cards
- </label>
- </div>
- <p className="text-muted text-xs -mt-2 ml-7">
- Only affects new notes; existing cards are not modified
- </p>
-
- <div className="flex gap-3 justify-end pt-2">
- <button
- type="button"
- onClick={handleClose}
- disabled={isSubmitting}
- className="px-4 py-2 text-slate hover:bg-ivory rounded-lg transition-colors disabled:opacity-50"
- >
- Cancel
- </button>
- <button
- type="submit"
- disabled={isSubmitting || !name.trim() || !isOnline}
- title={!isOnline ? "Reconnect to save changes" : undefined}
- 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 ? "Saving..." : "Save Changes"}
- </button>
- </div>
- </form>
- </div>
- </div>
- </div>
- );
-}