From 27c39631349d27765799593aa8ec446975cec115 Mon Sep 17 00:00:00 2001 From: KIRTI13115 <192kirti@gmail.com> Date: Sun, 28 Sep 2025 23:06:37 +0530 Subject: [PATCH 1/3] Please merge #315 Users can add flashcards --- .gitignore | 3 +- app/api/flashcards/route.ts | 36 ++ app/flashcards/add/page.tsx | 9 + app/flashcards/page.tsx | 578 ++++++++++++++++-- app/theory-cheatsheets/page.tsx | 470 ++++---------- components/AddFlashcard.tsx | 126 ++++ components/NavbarSheet.tsx | 155 +---- data/flashcards.json | 170 ++++++ data/flashcards.ts | 146 +---- package-lock.json | 404 +++++++++++- package.json | 2 + prisma/dev.db | Bin 0 -> 28672 bytes .../20250928134336_init/migration.sql | 8 + prisma/migrations/migration_lock.toml | 3 + prisma/schema.prisma | 25 + 15 files changed, 1460 insertions(+), 675 deletions(-) create mode 100644 app/api/flashcards/route.ts create mode 100644 app/flashcards/add/page.tsx create mode 100644 components/AddFlashcard.tsx create mode 100644 data/flashcards.json create mode 100644 prisma/dev.db create mode 100644 prisma/migrations/20250928134336_init/migration.sql create mode 100644 prisma/migrations/migration_lock.toml create mode 100644 prisma/schema.prisma diff --git a/.gitignore b/.gitignore index 6ffc7b2..6b201ba 100644 --- a/.gitignore +++ b/.gitignore @@ -39,4 +39,5 @@ yarn-error.log* # typescript *.tsbuildinfo -next-env.d.ts \ No newline at end of file +next-env.d.ts +/generated/prisma diff --git a/app/api/flashcards/route.ts b/app/api/flashcards/route.ts new file mode 100644 index 0000000..c883bc2 --- /dev/null +++ b/app/api/flashcards/route.ts @@ -0,0 +1,36 @@ +import { NextResponse } from "next/server"; +import { PrismaClient } from "@prisma/client"; + +const prisma = new PrismaClient(); + +export async function GET() { + try { + const flashcards = await prisma.flashcard.findMany({ + orderBy: { id: "asc" }, + }); + return NextResponse.json(flashcards); + } catch (error: any) { + return NextResponse.json({ error: error.message }, { status: 500 }); + } +} + +export async function POST(req: Request) { + try { + const body = await req.json(); + + const newFlashcard = await prisma.flashcard.create({ + data: { + term: body.term, + explanation: body.explanation, + difficulty: body.difficulty, + category: body.category, + }, + }); + + return NextResponse.json(newFlashcard, { status: 201 }); + } catch (error: any) { + console.error("❌ Prisma error:", error); + return NextResponse.json({ error: error.message }, { status: 500 }); + } +} + diff --git a/app/flashcards/add/page.tsx b/app/flashcards/add/page.tsx new file mode 100644 index 0000000..8f1fb0b --- /dev/null +++ b/app/flashcards/add/page.tsx @@ -0,0 +1,9 @@ +import AddFlashcard from "@/components/AddFlashcard"; + +export default function AddFlashcardPage() { + return ( +
+ +
+ ); +} diff --git a/app/flashcards/page.tsx b/app/flashcards/page.tsx index 348ae09..0d09db4 100644 --- a/app/flashcards/page.tsx +++ b/app/flashcards/page.tsx @@ -1,44 +1,542 @@ -import { Metadata } from "next"; -import FlashcardsPageClient from "./FlashcardsPageClient"; - -export const metadata: Metadata = { - title: "DSA Flashcards - Interactive Learning for Data Structures & Algorithms", - description: "Master Data Structures and Algorithms concepts with interactive flashcards. Practice core DSA topics, track your progress, and reinforce your learning with spaced repetition. Perfect for coding interview preparation.", - keywords: [ - "DSA flashcards", - "data structures flashcards", - "algorithms flashcards", - "interactive DSA learning", - "coding interview flashcards", - "DSA concepts practice", - "spaced repetition learning", - "algorithm flashcards", - "programming concepts", - "computer science flashcards" - ], - openGraph: { - title: "DSA Flashcards - Interactive Learning for Data Structures & Algorithms | DSAMate v2", - description: "Master Data Structures and Algorithms concepts with interactive flashcards. Practice core DSA topics and track your progress.", - images: [ - { - url: "/og-image.svg", - width: 1200, - height: 630, - alt: "DSA Flashcards - Interactive Learning | DSAMate v2", - }, - ], - }, - twitter: { - card: "summary_large_image", - title: "DSA Flashcards - Interactive Learning for Data Structures & Algorithms", - description: "Master Data Structures and Algorithms concepts with interactive flashcards. Practice core DSA topics and track your progress.", - images: ["/og-image.svg"], - }, - alternates: { - canonical: "/flashcards", - }, +"use client"; + +import { useState, useEffect } from "react"; +import { motion, AnimatePresence } from "framer-motion"; +import Navbar from "@/components/Navbar"; +import FlashcardComponent from "@/components/FlashcardComponent"; +import ReportIssueButton from "@/components/ReportIssueButton"; +import { ChevronLeft, ChevronRight, RotateCcw, BookOpen, Filter, Trophy } from "lucide-react"; + +// new state hooks for dynamic flashcards + + + +type Flashcard = { + id: number; + term: string; + explanation: string; + difficulty: string; + category: string; }; + + export default function FlashcardsPage() { - return ; + const [flashcards, setFlashcards] = useState([]); +const [categories, setCategories] = useState(["All"]); +const [difficulties, setDifficulties] = useState(["All"]); +const [loading, setLoading] = useState(true); + + const [currentIndex, setCurrentIndex] = useState(0); + const [isFlipped, setIsFlipped] = useState(false); + const [categoryFilter, setCategoryFilter] = useState("All"); + const [difficultyFilter, setDifficultyFilter] = useState("All"); + const [reviewedCards, setReviewedCards] = useState>(new Set()); + const [showFilters, setShowFilters] = useState(false); + + useEffect(() => { + let mounted = true; + + async function fetchFlashcards() { + setLoading(true); + try { + const res = await fetch("/api/flashcards", { cache: "no-store" }); + if (!res.ok) throw new Error(`Failed to fetch flashcards (${res.status})`); + + const json: unknown = await res.json(); + + if (!Array.isArray(json)) { + throw new Error("Invalid response format: expected an array"); + } + + + + // Coerce/normalize items into Flashcard[] safely + const cards: Flashcard[] = (json as unknown[]).map((item) => { + const obj: any = item ?? {}; + return { + id: Number(obj.id ?? NaN), + term: String(obj.term ?? ""), + explanation: String(obj.explanation ?? ""), + difficulty: String(obj.difficulty ?? "Basic"), + category: String(obj.category ?? "Uncategorized"), + }; + }); + + // filter out totally invalid rows (e.g., missing id or term) + const validCards = cards.filter((c) => !Number.isNaN(c.id) && c.term.length > 0); + + if (!mounted) return; + + setFlashcards(validCards); + setCategories(["All", ...Array.from(new Set(validCards.map((c) => c.category)))]); + setDifficulties(["All", ...Array.from(new Set(validCards.map((c) => c.difficulty)))]); + } catch (err) { + console.error("Error loading flashcards:", err); + // setFlashcards([]) or show an error state + } finally { + if (mounted) setLoading(false); + } + } + + fetchFlashcards(); + return () => { + mounted = false; + }; +}, []); + + + +const fadeInUp = { + hidden: { opacity: 0, y: 20 }, + visible: (i: number) => ({ + opacity: 1, + y: 0, + transition: { + delay: i * 0.1, + duration: 0.6, + }, + }), +}; + +const containerVariants = { + hidden: { opacity: 0 }, + visible: { + opacity: 1, + transition: { + staggerChildren: 0.1, + }, + }, +}; + + // Load progress from localStorage + useEffect(() => { + const savedIndex = localStorage.getItem('flashcard_current_index'); + const savedReviewed = localStorage.getItem('flashcard_reviewed'); + const savedCategoryFilter = localStorage.getItem('flashcard_category_filter'); + const savedDifficultyFilter = localStorage.getItem('flashcard_difficulty_filter'); + + if (savedIndex) setCurrentIndex(parseInt(savedIndex)); + if (savedReviewed) setReviewedCards(new Set(JSON.parse(savedReviewed))); + if (savedCategoryFilter) setCategoryFilter(savedCategoryFilter); + if (savedDifficultyFilter) setDifficultyFilter(savedDifficultyFilter); + }, []); + + + // Filter flashcards + const filteredCards = flashcards.filter(card => { + const categoryMatch = categoryFilter === "All" || card.category === categoryFilter; + const difficultyMatch = difficultyFilter === "All" || card.difficulty === difficultyFilter; + return categoryMatch && difficultyMatch; + }); + + // Reset index and flip state when filtered cards change + useEffect(() => { + if (currentIndex >= filteredCards.length && filteredCards.length > 0) { + setCurrentIndex(0); + setIsFlipped(false); + } + }, [filteredCards.length, currentIndex]); + + // Reset current index when filters change to prevent out-of-bounds + useEffect(() => { + setCurrentIndex(0); + setIsFlipped(false); + }, [categoryFilter, difficultyFilter]); + + // Save progress to localStorage + useEffect(() => { + localStorage.setItem('flashcard_current_index', currentIndex.toString()); + localStorage.setItem('flashcard_reviewed', JSON.stringify([...reviewedCards])); + localStorage.setItem('flashcard_category_filter', categoryFilter); + localStorage.setItem('flashcard_difficulty_filter', difficultyFilter); + }, [currentIndex, reviewedCards, categoryFilter, difficultyFilter]); + + const currentCard = filteredCards[currentIndex]; + const reviewedCount = filteredCards.filter(card => reviewedCards.has(card.id)).length; + const progress = filteredCards.length > 0 ? (reviewedCount / filteredCards.length) * 100 : 0; + + // Ensure currentCard exists to prevent undefined errors + if (!currentCard && filteredCards.length > 0) { + return null; // Prevent rendering while state is updating + } + + const handleNext = () => { + if (filteredCards.length > 0) { + setCurrentIndex((prev) => (prev + 1) % filteredCards.length); + setIsFlipped(false); + } + }; + + const handlePrevious = () => { + if (filteredCards.length > 0) { + setCurrentIndex((prev) => (prev - 1 + filteredCards.length) % filteredCards.length); + setIsFlipped(false); + } + }; + + const handleFlip = () => { + setIsFlipped(!isFlipped); + // Mark card as reviewed when flipped for the first time + if (!isFlipped && currentCard && !reviewedCards.has(currentCard.id)) { + setReviewedCards(prev => new Set([...prev, currentCard.id])); + } + }; + + const resetProgress = () => { + setReviewedCards(new Set()); + setCurrentIndex(0); + setIsFlipped(false); + }; + + const resetFilters = () => { + setCategoryFilter("All"); + setDifficultyFilter("All"); + }; + + if (loading) { + return ( +
+
+
+ ); +} + + if (filteredCards.length === 0) { + return ( +
+ + +
+ +
+ 📚 +
+

No flashcards found

+

+ Try adjusting your filters to see more flashcards. +

+ +
+
+
+ ); + } + + return ( +
+ + +
+ {/* Hero Section */} + + {/* Background decoration */} +
+ +
+ {/* Header */} + +
+
+ 🧠 +
+

+ DSA Flashcards +

+
+

+ Master core Data Structures & Algorithms concepts with interactive flashcards +

+
+ + {/* Stats and Controls */} + + {/* Progress Stats */} +
+
+
{currentIndex + 1}
+
Current
+
+
+
{reviewedCount}
+
Reviewed
+
+
+
{filteredCards.length}
+
Total
+
+
+
{Math.round(progress)}%
+
Progress
+
+
+ + {/* Control Buttons */} +
+ setShowFilters(!showFilters)} + className={`flex items-center gap-2 px-4 py-3 rounded-xl font-medium transition-all ${ + showFilters + ? 'bg-blue-600 text-white shadow-lg' + : 'bg-white/70 dark:bg-white/5 border border-gray-200/50 dark:border-white/10 text-gray-700 dark:text-gray-300 hover:bg-white/80 dark:hover:bg-white/10' + }`} + > + + Filters + + + + + Reset + +
+
+ + {/* Filters */} + + {showFilters && ( + +
+
+ {/* Category Filter */} +
+ +
+ {categories.map((category) => ( + + ))} +
+
+ + {/* Difficulty Filter */} +
+ +
+ {difficulties.map((difficulty) => ( + + ))} +
+
+
+
+
+ )} +
+ + {/* Elegant Progress Design */} + +
+ {/* Minimalist Progress Header */} +
+ +
+ {Math.round(progress)}% +
+
+
+
+ {reviewedCount}/{filteredCards.length} +
+
+ Mastered +
+
+
+
+ + {/* Sophisticated Progress Bar */} +
+ {/* Background track */} +
+ {/* Progress fill with gradient */} + + {/* Shimmer effect */} + + +
+ + {/* Progress milestones */} +
+ {[0, 25, 50, 75, 100].map((milestone) => ( + = milestone + ? 'bg-gradient-to-r from-blue-500 to-purple-500 border-white shadow-lg scale-110' + : 'bg-white dark:bg-gray-800 border-gray-300 dark:border-gray-600' + }`} + animate={{ + scale: progress >= milestone ? 1.1 : 1, + boxShadow: progress >= milestone + ? "0 0 20px rgba(59, 130, 246, 0.5)" + : "0 0 0px rgba(0, 0, 0, 0)" + }} + transition={{ duration: 0.3, delay: milestone * 0.02 }} + > + {progress >= milestone && ( + + ✓ + + )} + + ))} +
+
+ + {/* Elegant completion message */} + + {progress === 100 && ( + +
+
🎉
+
+ All concepts mastered! +
+
+
+ )} +
+
+
+ + {/* Flashcard */} + + + + + + + + + {/* Navigation Controls */} + + + + Previous + + +
+ {reviewedCards.has(currentCard.id) && ( + + + Reviewed + + )} +
+ + + Next + + +
+
+
+
+
+ ); } diff --git a/app/theory-cheatsheets/page.tsx b/app/theory-cheatsheets/page.tsx index f1d8b1c..ef3917d 100644 --- a/app/theory-cheatsheets/page.tsx +++ b/app/theory-cheatsheets/page.tsx @@ -11,20 +11,31 @@ import { Sheet, Sprout, Zap, + ChevronLeft, + ChevronRight, + RotateCcw, + Trophy, } from "lucide-react"; import React, { useState, useEffect } from "react"; import { useRouter } from "next/navigation"; import { Input } from "@/components/ui/input"; import ConceptCard from "@/components/ui/conceptCard"; -import Navbar from "@/components/ui/Navbar-interview"; -// Flashcards imports (migrated from former /flashcards page) import Navbar2 from "@/components/Navbar"; import FlashcardComponent from "@/components/FlashcardComponent"; import { motion, AnimatePresence } from "framer-motion"; -import { flashcards, categories, difficulties, type Flashcard } from "@/data/flashcards"; -import { ChevronLeft, ChevronRight, RotateCcw, Trophy } from "lucide-react"; -// ...removed framer-motion imports and animation configs... +// Flashcard type (from DB) +export type Flashcard = { + id: number; + term: string; + explanation: string; + difficulty: string; + category: string; +}; + +// ==================== +// Core cheatsheet data +// ==================== const coreConcept = [ { title: "OOPS", @@ -33,22 +44,8 @@ const coreConcept = [ icon: , level: "easy", questions: [ - { - questionTitle: "OOPS belongs to which subject?", - optionA: "Computer Science", - optionB: "Geology", - optionC: "Math", - optionD: "Zoology", - level: "hard", - }, - { - questionTitle: "Which of these is not a principle of OOPS?", - optionA: "Inheritance", - optionB: "Polymorphism", - optionC: "Encapsulation", - optionD: "Photosynthesis", - level: "medium", - }, + { questionTitle: "OOPS belongs to which subject?", optionA: "Computer Science", optionB: "Geology", optionC: "Math", optionD: "Zoology", level: "hard" }, + { questionTitle: "Which of these is not a principle of OOPS?", optionA: "Inheritance", optionB: "Polymorphism", optionC: "Encapsulation", optionD: "Photosynthesis", level: "medium" }, ], topic: ["classes", "objects", "inheritance"], }, @@ -59,22 +56,8 @@ const coreConcept = [ icon: , level: "medium", questions: [ - { - questionTitle: "Which of the following is a DBMS software?", - optionA: "MySQL", - optionB: "Photoshop", - optionC: "MS Word", - optionD: "Excel", - level: "easy", - }, - { - questionTitle: "What is normalization in DBMS?", - optionA: "Organizing data to reduce redundancy", - optionB: "Adding more tables", - optionC: "Deleting duplicate records", - optionD: "Converting text to uppercase", - level: "medium", - }, + { questionTitle: "Which of the following is a DBMS software?", optionA: "MySQL", optionB: "Photoshop", optionC: "MS Word", optionD: "Excel", level: "easy" }, + { questionTitle: "What is normalization in DBMS?", optionA: "Organizing data to reduce redundancy", optionB: "Adding more tables", optionC: "Deleting duplicate records", optionD: "Converting text to uppercase", level: "medium" }, ], topic: ["SQL", "normalization", "transactions"], }, @@ -85,22 +68,8 @@ const coreConcept = [ icon: , level: "hard", questions: [ - { - questionTitle: "Which of the following is not an OS?", - optionA: "Linux", - optionB: "Windows", - optionC: "MS Excel", - optionD: "MacOS", - level: "easy", - }, - { - questionTitle: "What is the purpose of a process scheduler?", - optionA: "Manage memory allocation", - optionB: "Select process from ready queue", - optionC: "Format hard disk", - optionD: "Connect to network", - level: "hard", - }, + { questionTitle: "Which of the following is not an OS?", optionA: "Linux", optionB: "Windows", optionC: "MS Excel", optionD: "MacOS", level: "easy" }, + { questionTitle: "What is the purpose of a process scheduler?", optionA: "Manage memory allocation", optionB: "Select process from ready queue", optionC: "Format hard disk", optionD: "Connect to network", level: "hard" }, ], topic: ["processing", "memory", "scheduling"], }, @@ -111,44 +80,29 @@ const coreConcept = [ icon: , level: "easy", questions: [ - { - questionTitle: "Which of these is a networking device?", - optionA: "Router", - optionB: "Compiler", - optionC: "Keyboard", - optionD: "Printer", - level: "easy", - }, - { - questionTitle: "Which protocol is used for secure web browsing?", - optionA: "HTTP", - optionB: "FTP", - optionC: "HTTPS", - optionD: "SMTP", - level: "medium", - }, + { questionTitle: "Which of these is a networking device?", optionA: "Router", optionB: "Compiler", optionC: "Keyboard", optionD: "Printer", level: "easy" }, + { questionTitle: "Which protocol is used for secure web browsing?", optionA: "HTTP", optionB: "FTP", optionC: "HTTPS", optionD: "SMTP", level: "medium" }, ], topic: ["protocols", "OSI model", "topologies"], }, ]; -const totalQuestions = coreConcept.reduce((count, concept) => { - return count + concept.questions.length; -}, 0); - +const totalQuestions = coreConcept.reduce((count, concept) => count + concept.questions.length, 0); const rounded = Math.floor(totalQuestions / 50) * 50; -const displayQuestions = - totalQuestions % 50 === 0 - ? `${rounded} Questions` - : `${rounded}+ Questions`; +const displayQuestions = totalQuestions % 50 === 0 ? `${rounded} Questions` : `${rounded}+ Questions`; -const page = () => { +const Page = () => { const router = useRouter(); - const [activeTab, setActiveTab] = useState<'cheatsheets' | 'flashcards'>('cheatsheets'); + const [activeTab, setActiveTab] = useState<"cheatsheets" | "flashcards">("cheatsheets"); const [searchTerm, setSearchTerm] = useState(""); const [selectedSubject, setSelectedSubject] = useState(null); - // Flashcard states (ported) + // ==================== + // Flashcard state + // ==================== + const [flashcards, setFlashcards] = useState([]); + const [categories, setCategories] = useState(["All"]); + const [difficulties, setDifficulties] = useState(["All"]); const [currentIndex, setCurrentIndex] = useState(0); const [isFlipped, setIsFlipped] = useState(false); const [categoryFilter, setCategoryFilter] = useState("All"); @@ -156,12 +110,35 @@ const page = () => { const [reviewedCards, setReviewedCards] = useState>(new Set()); const [showFilters, setShowFilters] = useState(false); - // Persist flashcard progress + // ==================== + // Fetch flashcards from API + // ==================== + useEffect(() => { + async function fetchFlashcards() { + try { + const res = await fetch("/api/flashcards", { cache: "no-store" }); + const data: Flashcard[] = await res.json(); + setFlashcards(data); + + const validCards = data.filter((c) => c.category && c.difficulty); + setCategories(["All", ...Array.from(new Set(validCards.map((c) => c.category)))]); + setDifficulties(["All", ...Array.from(new Set(validCards.map((c) => c.difficulty)))]); + } catch (err) { + console.error("Error fetching flashcards:", err); + } + } + fetchFlashcards(); + }, []); + + // ==================== + // Persist flashcard progress in localStorage + // ==================== useEffect(() => { - const savedIndex = localStorage.getItem('flashcard_current_index'); - const savedReviewed = localStorage.getItem('flashcard_reviewed'); - const savedCategoryFilter = localStorage.getItem('flashcard_category_filter'); - const savedDifficultyFilter = localStorage.getItem('flashcard_difficulty_filter'); + const savedIndex = localStorage.getItem("flashcard_current_index"); + const savedReviewed = localStorage.getItem("flashcard_reviewed"); + const savedCategoryFilter = localStorage.getItem("flashcard_category_filter"); + const savedDifficultyFilter = localStorage.getItem("flashcard_difficulty_filter"); + if (savedIndex) setCurrentIndex(parseInt(savedIndex)); if (savedReviewed) setReviewedCards(new Set(JSON.parse(savedReviewed))); if (savedCategoryFilter) setCategoryFilter(savedCategoryFilter); @@ -169,13 +146,16 @@ const page = () => { }, []); useEffect(() => { - localStorage.setItem('flashcard_current_index', currentIndex.toString()); - localStorage.setItem('flashcard_reviewed', JSON.stringify([...reviewedCards])); - localStorage.setItem('flashcard_category_filter', categoryFilter); - localStorage.setItem('flashcard_difficulty_filter', difficultyFilter); + localStorage.setItem("flashcard_current_index", currentIndex.toString()); + localStorage.setItem("flashcard_reviewed", JSON.stringify([...reviewedCards])); + localStorage.setItem("flashcard_category_filter", categoryFilter); + localStorage.setItem("flashcard_difficulty_filter", difficultyFilter); }, [currentIndex, reviewedCards, categoryFilter, difficultyFilter]); - const filteredCards = flashcards.filter(card => { + // ==================== + // Flashcard navigation + // ==================== + const filteredCards = flashcards.filter((card) => { const categoryMatch = categoryFilter === "All" || card.category === categoryFilter; const difficultyMatch = difficultyFilter === "All" || card.difficulty === difficultyFilter; return categoryMatch && difficultyMatch; @@ -188,13 +168,8 @@ const page = () => { } }, [filteredCards.length, currentIndex]); - useEffect(() => { - setCurrentIndex(0); - setIsFlipped(false); - }, [categoryFilter, difficultyFilter]); - const currentCard = filteredCards[currentIndex]; - const reviewedCount = filteredCards.filter(card => reviewedCards.has(card.id)).length; + const reviewedCount = filteredCards.filter((card) => reviewedCards.has(card.id)).length; const progress = filteredCards.length > 0 ? (reviewedCount / filteredCards.length) * 100 : 0; const handleNext = () => { @@ -203,45 +178,51 @@ const page = () => { setIsFlipped(false); } }; + const handlePrevious = () => { if (filteredCards.length > 0) { setCurrentIndex((prev) => (prev - 1 + filteredCards.length) % filteredCards.length); setIsFlipped(false); } }; + const handleFlip = () => { setIsFlipped(!isFlipped); if (!isFlipped && currentCard && !reviewedCards.has(currentCard.id)) { - setReviewedCards(prev => new Set([...prev, currentCard.id])); + setReviewedCards((prev) => new Set([...prev, currentCard.id])); } }; + const resetProgress = () => { setReviewedCards(new Set()); setCurrentIndex(0); setIsFlipped(false); }; + const resetFilters = () => { setCategoryFilter("All"); setDifficultyFilter("All"); }; - // Remove duplicacy in cheatsheets by filtering unique titles + // ==================== + // Cheatsheet filtering + // ==================== const filteredSubjects = coreConcept - .filter((con, idx, arr) => arr.findIndex(c => c.title === con.title) === idx) + .filter((con, idx, arr) => arr.findIndex((c) => c.title === con.title) === idx) .filter((con) => { const matchesSearch = con.title.toLowerCase().includes(searchTerm.toLowerCase()) || - con.topic.some((tag) => - tag.toLowerCase().includes(searchTerm.toLowerCase()) - ); + con.topic.some((tag) => tag.toLowerCase().includes(searchTerm.toLowerCase())); const matchesSubject = !selectedSubject || con.title === selectedSubject; return matchesSearch && matchesSubject; }); + // ==================== + // UI + // ==================== return (
- {/* } pageTitle="Theory Cheatsheets & Flashcards" onBack="/" page1="Interview-Experiences" page1Link="/interview-experiences" /> */} {/* Hero Section */}
@@ -263,7 +244,6 @@ const page = () => { {displayQuestions}
-
Track Progress @@ -276,307 +256,78 @@ const page = () => { {/* Tabs */}
- -
- {activeTab === 'cheatsheets' && ( + {/* Cheatsheets */} + {activeTab === "cheatsheets" && ( <> - {/* Filters (Cheatsheets) */}
- setSearchTerm(e.target.value)} - /> -
-
- -
- - {coreConcept.slice(0, 3).map((sub) => ( - - ))} -
+ setSearchTerm(e.target.value)} />
- {/* Concept Cards */}
{filteredSubjects.map((sub) => ( - + ))}
)} - {activeTab === 'flashcards' && ( - + {/* Flashcards */} + {activeTab === "flashcards" && ( + {/* Control Buttons */}
- - -
- {/* Stats and Controls */} -
- {/* Progress Stats */} -
-
-
{currentIndex + 1}
-
Current
- -
-
-
{reviewedCount}
-
Reviewed
-
-
-
{filteredCards.length}
-
Total
-
-
-
{Math.round(progress)}%
-
Progress
-
-
- -
- - {/* Filters */} - {showFilters && ( -
-
-
- {/* Category Filter */} -
- -
- {categories.map((category) => ( - - ))} -
-
- - {/* Difficulty Filter */} -
- -
- {difficulties.map((difficulty) => ( - - ))} -
-
-
-
-
- )} - {/* End Filters Section */} - - {/* Elegant Progress Design */} -
-
- {/* Minimalist Progress Header */} -
-
-
- {Math.round(progress)}% -
-
-
-
- {reviewedCount}/{filteredCards.length} -
-
- Mastered -
-
-
-
- - {/* Sophisticated Progress Bar */} -
- {/* Background track */} -
- {/* Progress fill with gradient */} -
- {/* Shimmer effect */} - {/* Removed shimmer effect */} -
-
- - {/* Progress milestones */} -
- {[0, 25, 50, 75, 100].map((milestone) => ( -
= milestone - ? 'bg-gradient-to-r from-blue-500 to-purple-500 border-white shadow-lg scale-110' - : 'bg-white dark:bg-gray-800 border-gray-300 dark:border-gray-600' - }`} - > - {progress >= milestone && ( -
- ✓ -
- )} -
- ))} -
-
- - {/* Elegant completion message */} - {progress === 100 && ( -
-
-
🎉
-
- All concepts mastered! -
-
-
- )} -
-
- - - {/* Flashcard section: Only show flashcards, not theory cheatsheets */} + {/* Flashcard display */}
{filteredCards.length > 0 && currentCard ? ( - - + + ) : ( - +
⚠️
-

- No Flashcard Available -

-

- Filtered Cards: {filteredCards.length}, Current Index: {currentIndex} -

-

- Please check your filters or flashcard data source. -

+

No Flashcard Available

+

Please check your filters or add new flashcards.

)}
- {/* Navigation Controls */} + {/* Navigation */}
- -
{currentCard && reviewedCards.has(currentCard.id) && ( @@ -585,22 +336,15 @@ const page = () => { )}
- -
)} - - {/* ...existing code... */}
); }; -export default page; +export default Page; diff --git a/components/AddFlashcard.tsx b/components/AddFlashcard.tsx new file mode 100644 index 0000000..9bcf8fc --- /dev/null +++ b/components/AddFlashcard.tsx @@ -0,0 +1,126 @@ +"use client"; + +import React, { useState } from "react"; +import { useRouter } from "next/navigation"; + +export default function AddFlashcard() { + const router = useRouter(); + const [formData, setFormData] = useState({ + term: "", + explanation: "", + difficulty: "Basic", + category: "", + }); + + const [loading, setLoading] = useState(false); + + const handleChange = ( + e: React.ChangeEvent + ) => { + setFormData({ ...formData, [e.target.name]: e.target.value }); + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setLoading(true); + + try { + const res = await fetch("/api/flashcards", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(formData), + }); + + if (!res.ok) { + throw new Error("Failed to add flashcard"); + } + + // Wait for DB confirmation + const newCard = await res.json(); + alert(`Flashcard "${newCard.term}" added!`); + + // Redirect only after DB success + router.push("/flashcards"); + } catch (err) { + console.error(err); + alert("Error adding flashcard."); + } finally { + setLoading(false); + } +}; + + + + return ( +
+

Add a New Flashcard

+
+ {/* Term */} +
+ + +
+ + {/* Explanation */} +
+ +