1
0
mirror of https://github.com/kamranahmedse/developer-roadmap.git synced 2025-09-09 16:53:33 +02:00
This commit is contained in:
Arik Chakma
2025-07-02 21:34:26 +06:00
parent d8d6536e31
commit e3bb896df2
3 changed files with 126 additions and 42 deletions

View File

@@ -21,7 +21,7 @@ export function AIMCQQuestion(props: AIMCQQuestionProps) {
props;
const { title: questionText, options, answerExplanation } = question;
const { isSubmitted, selectedOptions = [], status } = questionState;
const { isSubmitted, selectedOptions = [] } = questionState;
const canSubmitMultipleAnswers =
options.filter((option) => option.isCorrect).length > 1;
@@ -32,9 +32,7 @@ export function AIMCQQuestion(props: AIMCQQuestionProps) {
}
if (!canSubmitMultipleAnswers) {
const newSelectedOptions = selectedOptions.includes(index)
? selectedOptions.filter((id) => id !== index)
: [...selectedOptions, index];
const newSelectedOptions = [index];
setSelectedOptions(newSelectedOptions);
return;
}

View File

@@ -27,11 +27,10 @@ type AIQuizContentProps = {
quizSlug?: string;
questions: QuizQuestion[];
isLoading?: boolean;
onNewQuiz?: () => void;
};
export function AIQuizContent(props: AIQuizContentProps) {
const { quizSlug, questions, isLoading, onNewQuiz } = props;
const { quizSlug, questions, isLoading } = props;
const [activeQuestionIndex, setActiveQuestionIndex] = useState(0);
const activeQuestion = questions[activeQuestionIndex];
@@ -140,14 +139,18 @@ export function AIQuizContent(props: AIQuizContentProps) {
/>
)}
{isAllQuestionsSubmitted ? (
{isAllQuestionsSubmitted && (
<AIQuizResults
questionStates={questionStates}
totalQuestions={totalQuestions}
onRetry={handleRetry}
onNewQuiz={onNewQuiz ?? (() => {})}
onNewQuiz={() => {
window.location.href = '/ai/quiz';
}}
/>
) : (
)}
{!isAllQuestionsSubmitted && (
<>
{activeQuestion && activeQuestion.type === 'mcq' && (
<AIMCQQuestion

View File

@@ -6,9 +6,16 @@ import {
XCircle,
SkipForward,
CheckCircle2Icon,
PlusIcon,
CheckCircleIcon,
XCircleIcon,
SkipForwardIcon,
CheckIcon,
XIcon,
} from 'lucide-react';
import type { QuestionState } from './AIQuizContent';
import { getPercentage } from '../../lib/number';
import { cn } from '../../lib/classname';
type AIQuizResultsProps = {
questionStates: Record<number, QuestionState>;
@@ -36,54 +43,130 @@ export function AIQuizResults(props: AIQuizResultsProps) {
const accuracy = getPercentage(correctCount, totalQuestions);
return (
<div className="flex flex-col items-center justify-center p-8 text-center">
<div className="mx-auto mt-10 flex max-w-sm flex-col items-center justify-center text-center">
<PartyPopper className="mb-6 h-16 w-16 text-gray-400" />
<div className="mb-2 text-4xl font-bold">
{correctCount}/{totalQuestions}
</div>
<p className="mb-8 text-lg text-gray-600">
Great job! You answered {correctCount} out of {totalQuestions} questions
correctly that's {accuracy}% accuracy!
<p className="mb-8 text-lg text-balance text-gray-600">
Great job! You answered{' '}
<span className="font-bold">{correctCount}</span> out of{' '}
<span className="font-bold">{totalQuestions}</span> questions correctly
with <span className="font-bold">{accuracy}%</span> accuracy.
</p>
<div className="mb-8 grid w-full max-w-sm grid-cols-3 gap-4">
<div className="flex flex-col items-center rounded-xl bg-green-50 p-4 text-green-700">
<CheckCircle2Icon className="mb-2 h-6 w-6" />
<div className="text-xl font-semibold">{correctCount}</div>
<div className="text-sm">Correct</div>
</div>
<div className="mb-6 grid w-full grid-cols-5 gap-2">
{states.map((state, quizIndex) => {
const { status } = state;
<div className="flex flex-col items-center rounded-xl bg-red-50 p-4 text-red-700">
<XCircle className="mb-2 h-6 w-6" />
<div className="text-xl font-semibold">{incorrectCount}</div>
<div className="text-sm">Incorrect</div>
</div>
const isCorrect = status === 'correct';
const isIncorrect = status === 'incorrect';
const isSkipped = status === 'skipped';
<div className="flex flex-col items-center rounded-xl bg-gray-50 p-4 text-gray-700">
<SkipForward className="mb-2 h-6 w-6" />
<div className="text-xl font-semibold">{skippedCount}</div>
<div className="text-sm">Skipped</div>
</div>
return (
<div
key={quizIndex}
className={cn(
'flex aspect-square flex-col items-center justify-center rounded-xl border border-gray-200 p-2',
isCorrect && 'bg-green-700 text-white',
isIncorrect && 'bg-red-700 text-white',
isSkipped && 'bg-gray-700 text-white',
)}
>
{isCorrect && <CheckIcon className="h-6 w-6" />}
{isIncorrect && <XIcon className="h-6 w-6" />}
{isSkipped && <SkipForwardIcon className="h-6 w-6" />}
</div>
);
})}
</div>
<div className="flex gap-4">
<button
<div className="mb-8 grid w-full grid-cols-3 gap-4">
<ResultCard
count={correctCount}
label="Correct"
icon={<CheckCircle2Icon className="mb-2 h-6 w-6" />}
className="bg-green-50 text-green-700"
/>
<ResultCard
count={incorrectCount}
label="Incorrect"
icon={<XCircle className="mb-2 h-6 w-6" />}
className="bg-red-50 text-red-700"
/>
<ResultCard
count={skippedCount}
label="Skipped"
icon={<SkipForward className="mb-2 h-6 w-6" />}
className="bg-gray-50 text-gray-700"
/>
</div>
<div className="flex gap-2">
<ResultAction
label="Try Again"
icon={<RotateCcw className="h-4 w-4" />}
onClick={onRetry}
className="flex items-center gap-2 rounded-lg border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50"
>
<RotateCcw className="h-4 w-4" />
Try Again
</button>
<button
className="border border-gray-300 bg-white text-gray-700"
/>
<ResultAction
label="New Quiz"
icon={<PlusIcon className="h-4 w-4" />}
onClick={onNewQuiz}
className="flex items-center gap-2 rounded-lg bg-black px-4 py-2 text-sm font-medium text-white hover:bg-gray-900"
>
<RefreshCcw className="h-4 w-4" />
New Quiz
</button>
/>
</div>
</div>
);
}
type ResultCardProps = {
count: number;
label: string;
icon: React.ReactNode;
className?: string;
};
export function ResultCard(props: ResultCardProps) {
const { count, label, icon, className } = props;
return (
<div
className={cn(
'flex flex-col items-center rounded-xl bg-gray-50 p-4 text-gray-700',
className,
)}
>
{icon}
<div className="text-xl font-semibold">{count}</div>
<div className="text-sm">{label}</div>
</div>
);
}
type ResultActionProps = {
label: string;
icon: React.ReactNode;
onClick: () => void;
className?: string;
};
export function ResultAction(props: ResultActionProps) {
const { label, icon, onClick, className } = props;
return (
<button
onClick={onClick}
className={cn(
'flex h-10 items-center gap-2 rounded-xl bg-black px-4 py-2 text-sm font-medium text-white hover:bg-gray-900',
className,
)}
>
{icon}
{label}
</button>
);
}