From fbadeb795cf85ffcbf8ce3fb7972ef4425d784f8 Mon Sep 17 00:00:00 2001 From: "sarahosama.nabulsi" Date: Sat, 5 Apr 2025 14:49:12 +0200 Subject: [PATCH 1/2] feat: add basic create deck page (no validation) --- app/flashcards/create/page.tsx | 324 +++++++++++++++++++++++++++++++++ app/styles/globals.css | 24 +++ package-lock.json | 43 +++++ package.json | 1 + 4 files changed, 392 insertions(+) create mode 100644 app/flashcards/create/page.tsx diff --git a/app/flashcards/create/page.tsx b/app/flashcards/create/page.tsx new file mode 100644 index 0000000..2f3cbb0 --- /dev/null +++ b/app/flashcards/create/page.tsx @@ -0,0 +1,324 @@ +"use client"; + +import React, { useState } from "react"; +import { useRouter } from "next/navigation"; +import { Form, Input, Upload, Button, message, Card, Row, Col, Select, Typography } from "antd"; +import { UploadOutlined, MinusCircleOutlined, PlusOutlined } from "@ant-design/icons"; +import { motion, AnimatePresence } from "framer-motion"; + +const { Text } = Typography; + +type FlashcardFormValues = { + deckTitle: string; + flashcards: { + question: string; + answer: string; + questionImageUrl?: string; + answerImageUrl?: string; + }[]; +}; + +/** + * Example image-upload function. + * Discuss with Melih & Shak how to do image upload logic + * in backend (endpoint URLs, form data, etc.). + */ +async function handleImageUpload(_file: File): Promise { + // e.g.: + // const formData = new FormData(); + // formData.append("file", file); + // const res = await apiService.post<{ imageUrl: string }>("/flashcards/upload-image", formData); + // return res.imageUrl; + + // For now, return a placeholder for demonstration: + console.log("Simulating image upload for file:", _file); + return "https://placekitten.com/200/300"; +} + +const CreateDeckPage: React.FC = () => { + const router = useRouter(); + const [form] = Form.useForm(); + const [bulkCount, setBulkCount] = useState(1); // for the “Add X flashcards” dropdown + + /** + * Called when user submits (clicks "Save Deck"). + * gather deckTitle + flashcards array and pass them to backend. + * + * Discuss with Melih & Shak how to store this deck in backend: + * - Endpoint: e.g. POST /decks + * - Body: { deckTitle, flashcards: [...] } + */ + const onFinish = async (values: FlashcardFormValues) => { + try { + console.log("Submitted values:", values); + // e.g.: + // await apiService.post('/decks', values); + + message.success("Deck created successfully!"); + router.push("/home"); // Return to decks listing page + } catch (err) { + console.error(err); + message.error("Error creating deck."); + } + }; + + /** + * Because we use beforeUpload in , we intercept the file + * and manually handle the upload to get a URL, then store it in form state. + * + * Discuss with Melih & Shak how backend expects these uploads: + * - Auth headers? + * - Specific file field name? + */ + const handleBeforeUpload = async ( + file: File, + fieldName: [number, string] + ) => { + console.log("Uploading file:", file); + try { + const url = await handleImageUpload(file); + form.setFieldValue(["flashcards", fieldName[0], fieldName[1]], url); + message.success("Image uploaded!"); + } catch (err) { + console.error(err); + message.error("Image upload failed."); + } + return false; + }; + + return ( +
+

+ Create a New Deck +

+ +
+
+ Deck Title} + name="deckTitle" + > + + + + + {(fields, { add, remove }) => ( + <> + + {fields.map(({ key, name, ...restField }, index) => ( + + + + +
+ + Flashcard #{index + 1} + + {fields.length > 1 && ( + remove(name)} + style={{ + fontSize: 18, + cursor: "pointer", + color: "red", + }} + /> + )} +
+ + + + Topic / Question} + name={[name, "question"]} + > + + + + Question Image (Optional)} + name={[name, "questionImageUrl"]} + > + + handleBeforeUpload(file, [name, "questionImageUrl"]) + } + maxCount={1} + > + + + + + + + Answer / Memory} + name={[name, "answer"]} + > + + + + Answer Image (Optional)} + name={[name, "answerImageUrl"]} + > + + handleBeforeUpload(file, [name, "answerImageUrl"]) + } + maxCount={1} + > + + + + +
+
+
+ ))} +
+ +
+ Add + + { borderRadius: "6px", }} /> + - + { + if (!flashcards || flashcards.length < 1) { + return Promise.reject(new Error("At least one flashcard is required.")); + } + }, + }, + ]} + > {(fields, { add, remove }) => ( <> @@ -147,7 +162,7 @@ const CreateDeckPage: React.FC = () => { style={{ marginBottom: "20px", borderRadius: "8px", - backgroundColor: "#fff", + backgroundColor: "#f2f2f2", border: "2px solid #005B33", }} > @@ -175,6 +190,7 @@ const CreateDeckPage: React.FC = () => { {...restField} label={Topic / Question} name={[name, "question"]} + rules={[{ required: true, message: "A question is required." }]} > { {...restField} label={Answer / Memory} name={[name, "answer"]} + rules={[{ required: true, message: "An answer is required." }]} > { ); }; -export default CreateDeckPage; +export default CreateDeckPage; \ No newline at end of file