kaboot/components/QuizCreator.tsx

211 lines
9.3 KiB
TypeScript

import React, { useState } from 'react';
import { useAuth } from 'react-oidc-context';
import { Quiz, Question, AnswerOption } from '../types';
import { v4 as uuidv4 } from 'uuid';
import { Plus, Save, Trash2, CheckCircle, Circle, X, BookOpen } from 'lucide-react';
import { COLORS, SHAPES } from '../constants';
interface QuizCreatorProps {
onFinalize: (quiz: Quiz, saveToLibrary: boolean) => void;
onCancel: () => void;
}
export const QuizCreator: React.FC<QuizCreatorProps> = ({ onFinalize, onCancel }) => {
const auth = useAuth();
const [title, setTitle] = useState('');
const [questions, setQuestions] = useState<Question[]>([]);
const [qText, setQText] = useState('');
const [options, setOptions] = useState<string[]>(['', '', '', '']);
const [reasons, setReasons] = useState<string[]>(['', '', '', '']);
const [correctIdx, setCorrectIdx] = useState<number>(0);
const [saveToLibrary, setSaveToLibrary] = useState(false);
const handleAddQuestion = () => {
if (!qText.trim() || options.some(o => !o.trim())) {
alert("Please fill in the question and all 4 options.");
return;
}
const shapes = ['triangle', 'diamond', 'circle', 'square'] as const;
const colors = ['red', 'blue', 'yellow', 'green'] as const;
const newOptions: AnswerOption[] = options.map((text, idx) => ({
text,
isCorrect: idx === correctIdx,
shape: shapes[idx],
color: colors[idx],
reason: reasons[idx].trim() || undefined
}));
const newQuestion: Question = {
id: uuidv4(),
text: qText,
options: newOptions,
timeLimit: 20
};
setQuestions([...questions, newQuestion]);
setQText('');
setOptions(['', '', '', '']);
setReasons(['', '', '', '']);
setCorrectIdx(0);
};
const handleRemoveQuestion = (id: string) => {
setQuestions(questions.filter(q => q.id !== id));
};
const handleFinalize = () => {
if (!title.trim() || questions.length === 0) return;
onFinalize({ title, questions }, saveToLibrary);
};
return (
<div className="h-screen bg-gray-100 text-gray-900 p-4 md:p-8 flex flex-col items-center overflow-hidden">
<div className="max-w-4xl w-full bg-white rounded-[2rem] shadow-xl overflow-hidden border-4 border-white flex-1 min-h-0 flex flex-col">
<div className="bg-theme-primary p-8 text-white flex justify-between items-center relative overflow-hidden">
<div className="relative z-10">
<h2 className="text-4xl font-black font-display">Create Quiz</h2>
<p className="opacity-80 font-bold">Build your masterpiece</p>
</div>
<button
onClick={onCancel}
className="bg-white/20 p-3 rounded-full hover:bg-white/30 transition relative z-10"
>
<X size={24} />
</button>
{/* Decorative Circles */}
<div className="absolute -right-10 -top-10 w-40 h-40 bg-white/10 rounded-full"></div>
<div className="absolute right-20 bottom-[-50px] w-24 h-24 bg-white/10 rounded-full"></div>
</div>
<div className="p-6 md:p-8 space-y-6 md:space-y-8 flex-1 min-h-0 overflow-y-auto">
<div>
<label className="block text-sm font-black uppercase tracking-wider text-gray-500 mb-2 ml-2">Quiz Title</label>
<input
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
className="w-full p-4 border-4 border-gray-200 rounded-2xl text-2xl font-bold focus:border-theme-primary outline-none transition-colors"
placeholder="e.g., The Ultimate Trivia"
/>
</div>
<div className="space-y-4">
{questions.map((q, idx) => (
<div key={q.id} className="border-4 border-gray-100 p-4 rounded-2xl bg-gray-50 flex justify-between items-center group hover:border-gray-200 transition">
<div className="flex items-center gap-4">
<span className="bg-theme-primary text-white w-10 h-10 flex items-center justify-center rounded-full font-black">
{idx + 1}
</span>
<span className="font-bold text-lg">{q.text}</span>
</div>
<button onClick={() => handleRemoveQuestion(q.id)} className="text-gray-400 hover:text-red-500 p-2 rounded-xl hover:bg-red-50 transition">
<Trash2 size={24} />
</button>
</div>
))}
</div>
<div className="bg-blue-50/50 p-6 md:p-8 rounded-[2rem] border-4 border-blue-100">
<h3 className="text-xl font-black mb-6 flex items-center gap-2 text-blue-900">
<Plus size={24} /> New Question
</h3>
<div className="mb-6">
<input
type="text"
value={qText}
onChange={(e) => setQText(e.target.value)}
className="w-full p-4 border-4 border-white shadow-sm rounded-2xl font-bold text-lg focus:border-blue-400 outline-none"
placeholder="What is the question?"
/>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{options.map((opt, idx) => {
const isSelected = correctIdx === idx;
const borderColor = isSelected ? 'border-green-500' : 'border-white';
const bgClass = COLORS[['red','blue','yellow','green'][idx] as any];
return (
<div key={idx} className={`flex flex-col gap-2 p-3 rounded-2xl border-4 ${borderColor} bg-white shadow-sm transition-all`}>
<div className="flex items-center gap-3">
<button
onClick={() => setCorrectIdx(idx)}
className={`p-1 rounded-full ${isSelected ? 'text-green-500' : 'text-gray-200 hover:text-gray-400'}`}
>
{isSelected ? <CheckCircle size={32} fill="currentColor" className="text-white" /> : <Circle size={32} />}
</button>
<div className={`w-4 h-8 rounded-full ${bgClass}`}></div>
<input
type="text"
value={opt}
onChange={(e) => {
const newOpts = [...options];
newOpts[idx] = e.target.value;
setOptions(newOpts);
}}
className="w-full p-2 outline-none font-bold text-gray-700 bg-transparent placeholder:font-normal"
placeholder={`Option ${idx + 1}`}
/>
</div>
<div className="pl-12">
<input
type="text"
value={reasons[idx]}
onChange={(e) => {
const newReasons = [...reasons];
newReasons[idx] = e.target.value;
setReasons(newReasons);
}}
className="w-full p-2 text-sm outline-none text-gray-500 bg-gray-50 rounded-lg placeholder:text-gray-400"
placeholder={isSelected ? "Why is this correct? (optional)" : "Why is this wrong? (optional)"}
/>
</div>
</div>
)
})}
</div>
<button
onClick={handleAddQuestion}
className="mt-8 w-full bg-blue-600 text-white py-4 rounded-2xl font-black text-lg hover:bg-blue-700 shadow-[0_6px_0_#1e40af] active:shadow-none active:translate-y-[6px] transition-all"
>
Add Question
</button>
</div>
</div>
<div className="p-6 bg-gray-50 border-t-2 border-gray-100 flex justify-between items-center">
{auth.isAuthenticated ? (
<label className="flex items-center gap-3 cursor-pointer select-none group">
<input
type="checkbox"
checked={saveToLibrary}
onChange={(e) => setSaveToLibrary(e.target.checked)}
className="sr-only peer"
/>
<div className="w-6 h-6 border-2 border-gray-300 rounded-lg flex items-center justify-center peer-checked:bg-theme-primary peer-checked:border-theme-primary transition-all group-hover:border-gray-400">
{saveToLibrary && <CheckCircle size={16} className="text-white" />}
</div>
<span className="text-gray-600 font-bold flex items-center gap-2">
<BookOpen size={18} /> Save to my library
</span>
</label>
) : (
<div />
)}
<button
onClick={handleFinalize}
className="flex items-center gap-2 bg-green-500 text-white px-10 py-4 rounded-2xl text-xl font-black hover:bg-green-600 shadow-[0_6px_0_#15803d] active:shadow-none active:translate-y-[6px] transition-all"
>
<Save size={24} /> Finish & Play
</button>
</div>
</div>
</div>
);
};