| import React, { useState } from 'react';
|
| import { X, BookOpen, Layers, ArrowRight, ArrowLeft, RotateCw, Loader2, Check } from 'lucide-react';
|
| import { generateFlashcards } from '../api';
|
|
|
| const FlashcardModal = ({ isOpen, onClose, notebookId }) => {
|
| const [status, setStatus] = useState('setup');
|
| const [difficulty, setDifficulty] = useState('medium');
|
| const [numCards, setNumCards] = useState(10);
|
| const [cards, setCards] = useState([]);
|
| const [currentIndex, setCurrentIndex] = useState(0);
|
| const [isFlipped, setIsFlipped] = useState(false);
|
|
|
| if (!isOpen) return null;
|
|
|
| const handleGenerate = async () => {
|
| setStatus('loading');
|
| try {
|
| const data = await generateFlashcards(notebookId, numCards, difficulty);
|
| setCards(data.flashcards || []);
|
| setStatus('studying');
|
| setCurrentIndex(0);
|
| setIsFlipped(false);
|
| } catch (error) {
|
| console.error(error);
|
|
|
| setStatus('setup');
|
| }
|
| };
|
|
|
| const handleNext = () => {
|
| if (currentIndex < cards.length - 1) {
|
| setIsFlipped(false);
|
| setTimeout(() => setCurrentIndex(prev => prev + 1), 150);
|
| }
|
| };
|
|
|
| const handlePrev = () => {
|
| if (currentIndex > 0) {
|
| setIsFlipped(false);
|
| setTimeout(() => setCurrentIndex(prev => prev - 1), 150);
|
| }
|
| };
|
|
|
| const handleFlip = () => setIsFlipped(!isFlipped);
|
|
|
|
|
| const progress = cards.length > 0 ? ((currentIndex + 1) / cards.length) * 100 : 0;
|
|
|
| return (
|
| <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm p-4">
|
| <div className="bg-white rounded-2xl shadow-2xl w-full max-w-3xl flex flex-col h-[600px] animate-in fade-in zoom-in duration-200">
|
| {/* Header */}
|
| <div className="p-4 border-b border-slate-100 flex items-center justify-between">
|
| <div className="flex items-center gap-3">
|
| <div className="w-10 h-10 rounded-full bg-cyan-100 flex items-center justify-center text-cyan-600">
|
| <Layers size={20} fill="currentColor" />
|
| </div>
|
| <div>
|
| <h2 className="text-lg font-bold text-slate-900">Flashcards</h2>
|
| <p className="text-sm text-slate-500">Master concepts through active recall</p>
|
| </div>
|
| </div>
|
| <button onClick={onClose} className="p-2 hover:bg-slate-100 rounded-full text-slate-400 transition-colors">
|
| <X size={20} />
|
| </button>
|
| </div>
|
|
|
| {/* Content */}
|
| <div className="flex-1 bg-slate-50 p-8 flex flex-col overflow-hidden relative">
|
|
|
| {status === 'setup' && (
|
| <div className="flex flex-col items-center justify-center h-full space-y-8 animate-in fade-in slide-in-from-bottom-4">
|
| <div className="text-center">
|
| <h3 className="text-2xl font-bold text-slate-800 mb-2">Configure Your Session</h3>
|
| <p className="text-slate-500">Customize how you want to review this notebook.</p>
|
| </div>
|
|
|
| <div className="grid grid-cols-2 gap-8 w-full max-w-lg">
|
| {/* Difficulty */}
|
| <div className="space-y-3">
|
| <label className="text-sm font-bold text-slate-700 uppercase tracking-wide">Difficulty Level</label>
|
| <div className="flex flex-col gap-2">
|
| {['basic', 'medium', 'advanced'].map(level => (
|
| <button
|
| key={level}
|
| onClick={() => setDifficulty(level)}
|
| className={`px-4 py-3 rounded-lg border-2 font-medium capitalize text-left transition-all ${difficulty === level
|
| ? 'border-cyan-500 bg-cyan-50 text-cyan-800 shadow-sm'
|
| : 'border-slate-200 hover:border-cyan-200 bg-white text-slate-600'
|
| }`}
|
| >
|
| {level}
|
| </button>
|
| ))}
|
| </div>
|
| </div>
|
|
|
| {/* Card Count */}
|
| <div className="space-y-3">
|
| <label className="text-sm font-bold text-slate-700 uppercase tracking-wide">Number of Cards</label>
|
| <div className="flex flex-col gap-2">
|
| {[5, 10, 15].map(count => (
|
| <button
|
| key={count}
|
| onClick={() => setNumCards(count)}
|
| className={`px-4 py-3 rounded-lg border-2 font-medium text-left transition-all ${numCards === count
|
| ? 'border-cyan-500 bg-cyan-50 text-cyan-800 shadow-sm'
|
| : 'border-slate-200 hover:border-cyan-200 bg-white text-slate-600'
|
| }`}
|
| >
|
| {count} Cards
|
| </button>
|
| ))}
|
| </div>
|
| </div>
|
| </div>
|
|
|
| <button
|
| onClick={handleGenerate}
|
| className="px-10 py-4 bg-cyan-600 hover:bg-cyan-700 text-white font-bold rounded-xl shadow-lg shadow-cyan-200 flex items-center gap-2 hover:scale-105 transition-transform"
|
| >
|
| <BookOpen size={20} /> Generate Flashcards
|
| </button>
|
| </div>
|
| )}
|
|
|
| {status === 'loading' && (
|
| <div className="flex flex-col items-center justify-center h-full text-slate-500">
|
| <Loader2 size={48} className="animate-spin text-cyan-500 mb-6" />
|
| <h3 className="text-xl font-medium text-slate-800">Reviewing Documents...</h3>
|
| <p className="mt-2">Identifying core concepts and crafting questions.</p>
|
| </div>
|
| )}
|
|
|
| {status === 'studying' && cards.length > 0 && (
|
| <div className="flex flex-col h-full max-w-2xl mx-auto w-full">
|
| {/* Progress Bar */}
|
| <div className="flex items-center justify-between mb-2 text-sm font-medium text-slate-500">
|
| <span>Card {currentIndex + 1} of {cards.length}</span>
|
| <span>{difficulty}</span>
|
| </div>
|
| <div className="h-2 bg-slate-200 rounded-full mb-8 overflow-hidden">
|
| <div
|
| className="h-full bg-cyan-500 transition-all duration-300 ease-out"
|
| style={{ width: `${progress}%` }}
|
| />
|
| </div>
|
|
|
| {/* Card Container - Perspective */}
|
| <div className="flex-1 relative perspective-1000 cursor-pointer" onClick={handleFlip}>
|
| <div className={`relative w-full h-full transition-transform duration-500 transform-style-3d ${isFlipped ? 'rotate-y-180' : ''}`}>
|
| {/* Front */}
|
| <div className="absolute inset-0 bg-white rounded-2xl shadow-xl flex flex-col items-center justify-center p-8 text-center backface-hidden border-2 border-slate-100 group hover:border-cyan-200 transition-colors">
|
| <span className="text-xs font-bold text-cyan-600 mb-4 bg-cyan-50 px-3 py-1 rounded-full uppercase tracking-wider">Concept</span>
|
| <h3 className="text-3xl font-bold text-slate-800 leading-tight">
|
| {cards[currentIndex].front}
|
| </h3>
|
| <p className="mt-8 text-slate-400 text-sm font-medium flex items-center gap-2">
|
| <RotateCw size={14} /> Click to flip
|
| </p>
|
| </div>
|
|
|
| {/* Back */}
|
| <div className="absolute inset-0 bg-gradient-to-br from-cyan-50 to-blue-50 rounded-2xl shadow-xl flex flex-col items-center justify-center p-8 text-center backface-hidden rotate-y-180 border-2 border-cyan-100">
|
| <span className="text-xs font-bold text-slate-500 mb-4 px-3 py-1 rounded-full uppercase tracking-wider">Explanation</span>
|
| <p className="text-xl text-slate-800 leading-relaxed font-medium">
|
| {cards[currentIndex].back}
|
| </p>
|
| <p className="mt-8 text-cyan-600 text-sm font-medium flex items-center gap-2">
|
| <Check size={14} /> Swiped
|
| </p>
|
| </div>
|
| </div>
|
| </div>
|
|
|
| {/* Nav */}
|
| <div className="mt-8 flex justify-between items-center px-4">
|
| <button
|
| onClick={(e) => { e.stopPropagation(); handlePrev(); }}
|
| disabled={currentIndex === 0}
|
| className="p-4 rounded-full bg-white text-slate-700 shadow-md border border-slate-100 disabled:opacity-50 hover:bg-slate-50 transition-colors"
|
| >
|
| <ArrowLeft size={24} />
|
| </button>
|
|
|
| <span className="text-sm font-bold text-slate-400 uppercase tracking-widest text-[10px]">
|
| Use Space to Flip
|
| </span>
|
|
|
| <button
|
| onClick={(e) => { e.stopPropagation(); handleNext(); }}
|
| disabled={currentIndex === cards.length - 1}
|
| className="p-4 rounded-full bg-cyan-600 text-white shadow-lg shadow-cyan-200 disabled:opacity-50 disabled:bg-slate-300 disabled:shadow-none hover:bg-cyan-700 transition-colors"
|
| >
|
| <ArrowRight size={24} />
|
| </button>
|
| </div>
|
| </div>
|
| )}
|
| </div>
|
| </div>
|
|
|
| {/* Inline Styles for 3D flip if Tailwind plugins missing */}
|
| <style>{`
|
| .perspective-1000 { perspective: 1000px; }
|
| .transform-style-3d { transform-style: preserve-3d; }
|
| .backface-hidden { backface-visibility: hidden; }
|
| .rotate-y-180 { transform: rotateY(180deg); }
|
| `}</style>
|
| </div>
|
| );
|
| };
|
|
|
| export default FlashcardModal;
|
|
|