diff --git a/package-lock.json b/package-lock.json index 569a3fd..2b8497a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -55,6 +55,7 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -264,31 +265,6 @@ "node": ">=6.9.0" } }, - "node_modules/@emnapi/core": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", - "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.1", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz", - "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@emnapi/wasi-threads": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", @@ -296,7 +272,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { "tslib": "^2.4.0" } @@ -882,6 +857,7 @@ "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -928,6 +904,7 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1036,6 +1013,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -1238,6 +1216,7 @@ "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -2185,6 +2164,7 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -2246,6 +2226,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -2255,6 +2236,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -2517,6 +2499,7 @@ "integrity": "sha512-B9ifbFudT1TFhfltfaIPgjo9Z3mDynBTJSUYxTjOQruf/zHH+ezCQKcoqO+h7a9Pw9Nm/OtlXAiGT1axBgwqrQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", @@ -2641,6 +2624,7 @@ "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "dev": true, "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/src/App.css b/src/App.css index f937ae0..fcbd40f 100644 --- a/src/App.css +++ b/src/App.css @@ -1307,4 +1307,12 @@ a { .progress-side-panel { order: 2; } + + .field-error { + color: #e04561; + font-size: 13px; + margin-top: 4px; + margin-bottom: 4px; + font-weight: 600; + } } \ No newline at end of file diff --git a/src/pages/GenerateQuizPage.jsx b/src/pages/GenerateQuizPage.jsx index 35b79a6..0256129 100644 --- a/src/pages/GenerateQuizPage.jsx +++ b/src/pages/GenerateQuizPage.jsx @@ -1,86 +1,87 @@ -//DUMMY version to test frontend design and screens import { useState } from "react"; import { useNavigate } from "react-router-dom"; function GenerateQuizPage() { const navigate = useNavigate(); + const [topic, setTopic] = useState(""); - const [uploadedFile, setUploadedFile] = useState(null); - const [uploadedContent, setUploadedContent] = useState(""); const [numQuestions, setNumQuestions] = useState(10); const [difficulty, setDifficulty] = useState("medium"); const [loading, setLoading] = useState(false); const [error, setError] = useState(""); + const [errors, setErrors] = useState({}); - const handleFileUpload = async (e) => { - const file = e.target.files[0]; - if (!file) return; + const API_URL = "http://localhost:5000/generate"; - setUploadedFile(file); - setError(""); + const validateForm = () => { + const newErrors = {}; - try { - const fileContent = await file.text(); - setUploadedContent(fileContent); - } catch (err) { - setError("Failed to read file. Please try another document."); - setUploadedFile(null); - setUploadedContent(""); + if (!topic.trim()) { + newErrors.topic = "Topic or study material is required."; + } else if (topic.trim().length < 3) { + newErrors.topic = "Please enter at least 3 characters."; + } else if (topic.trim().length > 200) { + newErrors.topic = "Please keep the topic under 200 characters."; + } + + if (!numQuestions || numQuestions < 1 || numQuestions > 20) { + newErrors.numQuestions = "Number of questions must be between 1 and 20."; } - }; - const handleRemoveFile = () => { - setUploadedFile(null); - setUploadedContent(""); + if (!difficulty) { + newErrors.difficulty = "Please select a difficulty level."; + } + + return newErrors; }; const handleGenerate = async (e) => { e.preventDefault(); - if (!topic.trim() && !uploadedContent.trim()) { - setError("Please enter a topic or upload a document."); + const validationErrors = validateForm(); + if (Object.keys(validationErrors).length > 0) { + setErrors(validationErrors); return; } try { setLoading(true); setError(""); + setErrors({}); - // Use uploaded content if available, otherwise use topic - const inputData = uploadedContent.trim() ? { text: uploadedContent } : { topic }; - - // Call backend API to generate quiz - const response = await fetch("http://localhost:5000/api/quiz/generate", { + const response = await fetch(API_URL, { method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(inputData), + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + topic: topic.trim(), + }), }); + const data = await response.json(); + if (!response.ok) { - const errorData = await response.json(); - throw new Error(errorData.details || "Failed to generate quiz"); + throw new Error(data.error || "Failed to generate quiz."); } - const data = await response.json(); - const questions = data.questions; - - // Format questions for the quiz page (convert options array to object with A, B, C, D) - const formattedQuestions = questions.map((q) => ({ - question: q.question, - options: [q.options.A, q.options.B, q.options.C, q.options.D], - correctAnswer: q.answer, - })); - - setTimeout(() => { - navigate("/quiz", { - state: { - topic: uploadedFile ? uploadedFile.name : topic, - questions: formattedQuestions, - }, - }); - }, 600); + const questions = data.questions || []; + + if (!Array.isArray(questions) || questions.length === 0) { + throw new Error("No quiz questions were returned from the server."); + } + + navigate("/quiz", { + state: { + topic, + quizId: data.quizId, + questions, + numQuestions, + difficulty, + }, + }); } catch (err) { - setError(err.message || "Failed to generate quiz. Please try again."); + setError(err.message || "Failed to generate quiz."); } finally { setLoading(false); } @@ -131,48 +132,34 @@ function GenerateQuizPage() { type="text" placeholder="e.g., Data Structures" value={topic} - onChange={(e) => setTopic(e.target.value)} + onChange={(e) => { + setTopic(e.target.value); + setErrors((prev) => ({ ...prev, topic: "" })); + }} /> + {errors.topic &&

{errors.topic}

} -
- - - {uploadedFile && ( - - )} -
+
Upload
+ {errors.difficulty && ( +

{errors.difficulty}

+ )}
@@ -182,19 +169,31 @@ function GenerateQuizPage() { ))}
+ {errors.numQuestions && ( +

{errors.numQuestions}

+ )}
{error &&

{error}

} - @@ -218,15 +217,24 @@ function GenerateQuizPage() {

Instant Synthesis

-

Our AI engine transforms complex raw material into teachable concepts.

+

+ Our AI engine transforms complex raw material into teachable + concepts. +

Adaptive Feedback

-

Receive custom explanations for every answer to strengthen learning.

+

+ Receive custom explanations for every answer to strengthen + learning. +

Smart Repository

-

Every quiz is archived and categorized for future review and analysis.

+

+ Every quiz is archived and categorized for future review and + analysis. +

@@ -247,186 +255,4 @@ function GenerateQuizPage() { ); } -export default GenerateQuizPage; - -// REAL VERSION AFTER AI GENERATION IS IMPLEMENTED -// import { useState } from "react"; -// import { useNavigate } from "react-router-dom"; -// import { generateQuiz } from "../services/api"; - -// function GenerateQuizPage() { -// const navigate = useNavigate(); -// const [topic, setTopic] = useState(""); -// const [numQuestions, setNumQuestions] = useState(10); -// const [difficulty, setDifficulty] = useState("medium"); -// const [loading, setLoading] = useState(false); -// const [error, setError] = useState(""); - -// const handleGenerate = async (e) => { -// e.preventDefault(); - -// if (!topic.trim()) { -// setError("Please enter a topic or paste study material."); -// return; -// } - -// try { -// setLoading(true); -// setError(""); - -// const data = await generateQuiz({ -// topic, -// numQuestions, -// difficulty, -// }); - -// navigate("/quiz", { -// state: { -// topic, -// questions: data.questions || [], -// }, -// }); -// } catch (err) { -// setError(err.message || "Failed to generate quiz."); -// } finally { -// setLoading(false); -// } -// }; - -// return ( -//
-//
-//
Quizey
- -// - -//
Profile
-//
- -//
-//
-//
AI Powered Learning
-//

-// Craft your -//
-// intellectual -//
-// path. -//

-//

-// Input any topic and let AI architect a customized assessment designed -// for deep comprehension and long-term retention. -//

- -//
-//
-//
-//
-// 2.8m+ quizzes generated today -//
-//
- -//
-//
-// -// setTopic(e.target.value)} -// /> - -// -//
Upload
- -//
-//
-// -// -//
- -//
-// -//
-// {[5, 10, 15, 20].map((num) => ( -// -// ))} -//
-//
-//
- -// {error &&

{error}

} - -// - -//
-//
-// QUESTION PREVIEW -// LIVE PREVIEW -//
-//
-//
-//
-//
-//
-//
-//
- -//
-//

Why learn with Quizey?

-//
- -//
-//
-//

Instant Synthesis

-//

Our AI engine transforms complex raw material into teachable concepts.

-//
-//
-//

Adaptive Feedback

-//

Receive custom explanations for every answer to strengthen learning.

-//
-//
-//

Smart Repository

-//

Every quiz is archived and categorized for future review and analysis.

-//
-//
-//
- -// -//
-// ); -// } - -// export default GenerateQuizPage; \ No newline at end of file +export default GenerateQuizPage; \ No newline at end of file diff --git a/src/pages/LoginPage.jsx b/src/pages/LoginPage.jsx index a8b63b8..6c5d512 100644 --- a/src/pages/LoginPage.jsx +++ b/src/pages/LoginPage.jsx @@ -8,15 +8,47 @@ function LoginPage() { password: "", }); + const [errors, setErrors] = useState({}); + + const validateForm = () => { + const newErrors = {}; + + if (!formData.email.trim()) { + newErrors.email = "Email is required."; + } else if (!/\S+@\S+\.\S+/.test(formData.email)) { + newErrors.email = "Please enter a valid email address."; + } + + if (!formData.password.trim()) { + newErrors.password = "Password is required."; + } else if (formData.password.length < 6) { + newErrors.password = "Password must be at least 6 characters."; + } + + return newErrors; + }; + const handleChange = (e) => { setFormData((prev) => ({ ...prev, [e.target.name]: e.target.value, })); + + setErrors((prev) => ({ + ...prev, + [e.target.name]: "", + })); }; const handleSubmit = (e) => { e.preventDefault(); + + const validationErrors = validateForm(); + if (Object.keys(validationErrors).length > 0) { + setErrors(validationErrors); + return; + } + navigate("/landing"); }; @@ -38,6 +70,7 @@ function LoginPage() { value={formData.email} onChange={handleChange} /> + {errors.email &&

{errors.email}

} + {errors.password &&

{errors.password}

}