diff --git a/client/src/pages/Flashcards.jsx b/client/src/pages/Flashcards.jsx index 4a067ca9..b46e8ced 100644 --- a/client/src/pages/Flashcards.jsx +++ b/client/src/pages/Flashcards.jsx @@ -1,10 +1,372 @@ -import { useState, useEffect } from "react"; -import Modal from "../components/Modal"; +import React, { useState, useEffect } from "react"; +// Import icons from lucide-react, which is available +import { Edit, Trash2, X } from "lucide-react"; +// Import the global theme hook (this will work in your local project) import { useTheme } from "../context/ThemeContext"; -import { fetchFlashcards, addFlashcard } from "../utils/api"; -export default function Flashcards() { - const { theme } = useTheme(); +// --- API Functions (from ../utils/api) --- +// We place all API logic directly in this file. +const API_URL = "http://localhost:5050/api/flashcards"; + +// CREATE +const addFlashcard = async (flashcard) => { + const res = await fetch(API_URL, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(flashcard), + }); + if (!res.ok) { + const err = await res.json().catch(() => ({})); // Try to parse error + throw new Error(err.message || "Failed to add flashcard"); + } + return await res.json(); +}; + +// READ (All) +const fetchFlashcards = async () => { + const res = await fetch(API_URL); + if (!res.ok) { + const err = await res.json().catch(() => ({})); + throw new Error(err.message || "Failed to fetch flashcards"); + } + return await res.json(); +}; + +// UPDATE +const updateFlashcard = async (id, flashcard) => { + const res = await fetch(`${API_URL}/${id}`, { + method: "PATCH", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(flashcard), + }); + if (!res.ok) { + const err = await res.json().catch(() => ({})); + throw new Error(err.message || "Failed to update flashcard"); + } + return await res.json(); +}; + +// DELETE +const deleteFlashcard = async (id) => { + const res = await fetch(`${API_URL}/${id}`, { + method: "DELETE", + }); + if (!res.ok) { + const err = await res.json().catch(() => ({})); + throw new Error(err.message || "Failed to delete flashcard"); + } + return await res.json(); +}; +// --- End API Functions --- + +// --- Base Modal Component (from ../components/Modal) --- +// A generic, self-contained Modal component. +function Modal({ open, onClose, title, children }) { + if (!open) return null; + + return ( +
+
e.stopPropagation()} // Prevent click from closing modal + className="relative m-4 w-full max-w-lg rounded-lg bg-white p-6 shadow-2xl dark:bg-gray-800" + > + {/* Header */} +
+

+ {title} +

+ +
+ {/* Body */} +
{children}
+
+
+ ); +} +// --- End Modal Component --- + +// --- FlashcardModal Component (from ../components/FlashcardModal) --- +function FlashcardModal({ + open, + onClose, + mode = "add", // 'add' or 'edit' + cardToEdit, // The card data to pre-fill the form + onCardAdded, // Callback for when a new card is created + onCardUpdated, // Callback for when a card is updated + topics = [], // New prop to receive the topics list +}) { + const [form, setForm] = useState({ term: "", definition: "", topic: "" }); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + const [topicChoice, setTopicChoice] = useState("select"); // State for dropdown + + useEffect(() => { + if (open) { + if (mode === "edit" && cardToEdit) { + const cardTopic = cardToEdit.topic || ""; + // Check if the card's topic is in the predefined list + const isCustomTopic = !topics.includes(cardTopic) && cardTopic !== ""; + + setForm({ + term: cardToEdit.term || "", + definition: cardToEdit.definition || "", + topic: cardTopic, + }); + // Set dropdown state accordingly + setTopicChoice(isCustomTopic ? "custom" : "select"); + } else { + // Reset for 'add' mode + setForm({ term: "", definition: "", topic: "" }); + setTopicChoice("select"); + } + setError(null); + } + }, [open, mode, cardToEdit, topics]); // Added 'topics' to dependency array + + const handleSubmit = async (e) => { + e.preventDefault(); + if (isLoading) return; + setIsLoading(true); + setError(null); + + try { + const cardData = { + term: form.term.trim(), + definition: form.definition.trim(), + topic: + form.topic.trim().charAt(0).toUpperCase() + + form.topic.trim().slice(1), + }; + + if (!cardData.term || !cardData.definition || !cardData.topic) { + setError("All fields are required."); + setIsLoading(false); + return; + } + + if (mode === "edit") { + const updatedCard = await updateFlashcard(cardToEdit._id, cardData); + if (onCardUpdated) onCardUpdated(updatedCard); + } else { + const newCard = await addFlashcard(cardData); + if (onCardAdded) onCardAdded(newCard); + } + onClose(); + } catch (err) { + console.error( + mode === "edit" + ? "Error updating flashcard:" + : "Error adding flashcard:", + err + ); + setError(err.message || "An unexpected error occurred."); + } finally { + setIsLoading(false); + } + }; + + const title = mode === "edit" ? "Edit Flashcard" : "Add Flashcard"; + const buttonText = mode === "edit" ? "Save Changes" : "Add Card"; + + return ( + +
+
+ + setForm({ ...form, term: e.target.value })} + required + className="w-full border p-2 rounded-md shadow-sm dark:bg-gray-700 dark:border-gray-600" + /> +
+
+ +