Add shuffle options

This commit is contained in:
Joey Yakimowich-Payne 2026-01-14 00:16:23 -07:00
commit 90fba17a1e
No known key found for this signature in database
GPG key ID: 6BFE655FA5ABD1E1

View file

@ -1,6 +1,6 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion'; import { motion, AnimatePresence } from 'framer-motion';
import { ArrowLeft, Save, Plus, Play, AlertTriangle } from 'lucide-react'; import { ArrowLeft, Save, Plus, Play, AlertTriangle, Shuffle } from 'lucide-react';
import { DndContext, closestCenter, KeyboardSensor, PointerSensor, useSensor, useSensors, DragEndEvent } from '@dnd-kit/core'; import { DndContext, closestCenter, KeyboardSensor, PointerSensor, useSensor, useSensors, DragEndEvent } from '@dnd-kit/core';
import { arrayMove, SortableContext, sortableKeyboardCoordinates, verticalListSortingStrategy } from '@dnd-kit/sortable'; import { arrayMove, SortableContext, sortableKeyboardCoordinates, verticalListSortingStrategy } from '@dnd-kit/sortable';
import { Quiz, Question } from '../types'; import { Quiz, Question } from '../types';
@ -31,6 +31,8 @@ export const QuizEditor: React.FC<QuizEditorProps> = ({
const [editingQuestion, setEditingQuestion] = useState<Question | null>(null); const [editingQuestion, setEditingQuestion] = useState<Question | null>(null);
const [showDeleteConfirm, setShowDeleteConfirm] = useState<string | null>(null); const [showDeleteConfirm, setShowDeleteConfirm] = useState<string | null>(null);
const [titleEditing, setTitleEditing] = useState(false); const [titleEditing, setTitleEditing] = useState(false);
const [shuffleQuestions, setShuffleQuestions] = useState(false);
const [shuffleAnswers, setShuffleAnswers] = useState(false);
useBodyScrollLock(!!showDeleteConfirm); useBodyScrollLock(!!showDeleteConfirm);
@ -112,6 +114,33 @@ export const QuizEditor: React.FC<QuizEditorProps> = ({
q.options.some(o => o.isCorrect) q.options.some(o => o.isCorrect)
); );
const handleStartGame = () => {
let questions = [...quiz.questions];
if (shuffleQuestions) {
questions = questions.sort(() => Math.random() - 0.5);
}
if (shuffleAnswers) {
const shapes = ['triangle', 'diamond', 'circle', 'square'] as const;
const colors = ['red', 'blue', 'yellow', 'green'] as const;
questions = questions.map(q => {
const shuffledOptions = [...q.options].sort(() => Math.random() - 0.5);
return {
...q,
options: shuffledOptions.map((opt, idx) => ({
...opt,
shape: shapes[idx % 4],
color: colors[idx % 4]
}))
};
});
}
onStartGame({ ...quiz, questions });
};
return ( return (
<div className="min-h-screen bg-gray-100 text-gray-900 p-4 md:p-8 flex flex-col items-center"> <div className="min-h-screen bg-gray-100 text-gray-900 p-4 md:p-8 flex flex-col items-center">
<div className="max-w-4xl w-full bg-white rounded-[2rem] shadow-xl overflow-hidden border-4 border-white"> <div className="max-w-4xl w-full bg-white rounded-[2rem] shadow-xl overflow-hidden border-4 border-white">
@ -212,9 +241,55 @@ export const QuizEditor: React.FC<QuizEditorProps> = ({
)} )}
</div> </div>
<div className="p-6 bg-gray-50 border-t-2 border-gray-100"> <div className="p-6 bg-gray-50 border-t-2 border-gray-100 space-y-4">
<div className="space-y-2">
<label className="flex items-center justify-between p-4 bg-white rounded-xl border-2 border-gray-200 cursor-pointer hover:border-theme-primary transition group">
<div className="flex items-center gap-3">
<div className={`p-2 rounded-lg transition ${shuffleQuestions ? 'bg-theme-primary text-white' : 'bg-gray-100 text-gray-500 group-hover:bg-gray-200'}`}>
<Shuffle size={20} />
</div>
<div>
<p className="font-bold text-gray-900">Shuffle Questions</p>
<p className="text-sm text-gray-500">Randomize question order when starting</p>
</div>
</div>
<div className="relative">
<input
type="checkbox"
checked={shuffleQuestions}
onChange={(e) => setShuffleQuestions(e.target.checked)}
className="sr-only peer"
/>
<div className="w-11 h-6 bg-gray-200 rounded-full peer peer-checked:bg-theme-primary transition-colors"></div>
<div className="absolute left-0.5 top-0.5 w-5 h-5 bg-white rounded-full shadow transition-transform peer-checked:translate-x-5"></div>
</div>
</label>
<label className="flex items-center justify-between p-4 bg-white rounded-xl border-2 border-gray-200 cursor-pointer hover:border-theme-primary transition group">
<div className="flex items-center gap-3">
<div className={`p-2 rounded-lg transition ${shuffleAnswers ? 'bg-theme-primary text-white' : 'bg-gray-100 text-gray-500 group-hover:bg-gray-200'}`}>
<Shuffle size={20} />
</div>
<div>
<p className="font-bold text-gray-900">Shuffle Answers</p>
<p className="text-sm text-gray-500">Randomize answer positions for each question</p>
</div>
</div>
<div className="relative">
<input
type="checkbox"
checked={shuffleAnswers}
onChange={(e) => setShuffleAnswers(e.target.checked)}
className="sr-only peer"
/>
<div className="w-11 h-6 bg-gray-200 rounded-full peer peer-checked:bg-theme-primary transition-colors"></div>
<div className="absolute left-0.5 top-0.5 w-5 h-5 bg-white rounded-full shadow transition-transform peer-checked:translate-x-5"></div>
</div>
</label>
</div>
<button <button
onClick={() => onStartGame(quiz)} onClick={handleStartGame}
disabled={!canStartGame} disabled={!canStartGame}
className="w-full bg-green-500 text-white py-4 rounded-2xl text-xl font-black shadow-[0_6px_0_#16a34a] active:shadow-none active:translate-y-[6px] transition-all hover:bg-green-600 flex items-center justify-center gap-3 disabled:opacity-50 disabled:cursor-not-allowed disabled:active:translate-y-0 disabled:active:shadow-[0_6px_0_#16a34a]" className="w-full bg-green-500 text-white py-4 rounded-2xl text-xl font-black shadow-[0_6px_0_#16a34a] active:shadow-none active:translate-y-[6px] transition-all hover:bg-green-600 flex items-center justify-center gap-3 disabled:opacity-50 disabled:cursor-not-allowed disabled:active:translate-y-0 disabled:active:shadow-[0_6px_0_#16a34a]"
> >