"use client"; import { LightButton, PrimaryButton } from "@/design-system/base/button"; import { Input } from "@/design-system/base/input"; import { Select } from "@/design-system/base/select"; import { Textarea } from "@/design-system/base/textarea"; import { Modal } from "@/design-system/overlay/modal"; import { VStack, HStack } from "@/design-system/layout/stack"; import { Plus, Trash2 } from "lucide-react"; import { useState } from "react"; import { useTranslations } from "next-intl"; import { actionCreateCard } from "@/modules/card/card-action"; import type { CardType, CardMeaning } from "@/modules/card/card-action-dto"; import { toast } from "sonner"; const QUERY_LANGUAGE_LABELS = { english: "english", chinese: "chinese", japanese: "japanese", korean: "korean", } as const; const QUERY_LANGUAGES = [ { value: "en", label: "english" as const }, { value: "zh", label: "chinese" as const }, { value: "ja", label: "japanese" as const }, { value: "ko", label: "korean" as const }, ] as const; interface AddCardModalProps { isOpen: boolean; onClose: () => void; deckId: number; onAdded: () => void; } export function AddCardModal({ isOpen, onClose, deckId, onAdded, }: AddCardModalProps) { const t = useTranslations("deck_id"); const [cardType, setCardType] = useState("WORD"); const [word, setWord] = useState(""); const [ipa, setIpa] = useState(""); const [queryLang, setQueryLang] = useState("en"); const [customQueryLang, setCustomQueryLang] = useState(""); const [meanings, setMeanings] = useState([ { partOfSpeech: null, definition: "", example: null } ]); const [isSubmitting, setIsSubmitting] = useState(false); const showIpa = cardType === "WORD" || cardType === "PHRASE"; const addMeaning = () => { setMeanings([...meanings, { partOfSpeech: null, definition: "", example: null }]); }; const removeMeaning = (index: number) => { if (meanings.length > 1) { setMeanings(meanings.filter((_, i) => i !== index)); } }; const updateMeaning = ( index: number, field: "partOfSpeech" | "definition" | "example", value: string ) => { const updated = [...meanings]; updated[index] = { ...updated[index], [field]: value || null }; setMeanings(updated); }; const resetForm = () => { setCardType("WORD"); setWord(""); setIpa(""); setQueryLang("en"); setCustomQueryLang(""); setMeanings([{ partOfSpeech: null, definition: "", example: null }]); }; const handleAdd = async () => { if (!word.trim()) { toast.error(t("wordRequired")); return; } const validMeanings = meanings.filter(m => m.definition?.trim()); if (validMeanings.length === 0) { toast.error(t("definitionRequired")); return; } setIsSubmitting(true); const effectiveQueryLang = customQueryLang.trim() || queryLang; try { const cardResult = await actionCreateCard({ deckId, word: word.trim(), ipa: showIpa && ipa.trim() ? ipa.trim() : null, queryLang: effectiveQueryLang, cardType, meanings: validMeanings.map(m => ({ partOfSpeech: cardType === "SENTENCE" ? null : (m.partOfSpeech?.trim() || null), definition: m.definition!.trim(), example: m.example?.trim() || null, })), }); if (!cardResult.success) { throw new Error(cardResult.message || "Failed to create card"); } resetForm(); onAdded(); onClose(); toast.success(t("cardAdded") || "Card added successfully"); } catch (error) { toast.error(error instanceof Error ? error.message : "Unknown error"); } finally { setIsSubmitting(false); } }; const handleClose = () => { resetForm(); onClose(); }; return ( {t("addNewCard")}
{QUERY_LANGUAGES.map((lang) => ( { setQueryLang(lang.value); setCustomQueryLang(""); }} size="sm" > {t(lang.label)} ))} setCustomQueryLang(e.target.value)} placeholder={t("enterLanguageName")} className="w-auto min-w-[100px] flex-1" size="sm" />
setWord(e.target.value)} className="w-full" placeholder={cardType === "SENTENCE" ? t("sentencePlaceholder") : t("wordPlaceholder")} />
{showIpa && (
setIpa(e.target.value)} className="w-full" placeholder={t("ipaPlaceholder")} />
)}
{meanings.map((meaning, index) => (
{cardType !== "SENTENCE" && (
updateMeaning(index, "partOfSpeech", e.target.value)} placeholder={t("partOfSpeech")} className="w-full" />
)}
updateMeaning(index, "definition", e.target.value)} placeholder={t("definition")} className="w-full" />
{meanings.length > 1 && ( )}