mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2025-08-31 21:11:44 +02:00
wip
This commit is contained in:
@@ -4,6 +4,7 @@ import { AIMCQQuestion } from './AIMCQQuestion';
|
||||
import { AIOpenEndedQuestion } from './AIOpenEndedQuestion';
|
||||
import { QuizTopNavigation } from './QuizTopNavigation';
|
||||
import { getPercentage } from '../../lib/number';
|
||||
import { AIQuizResults } from './AIQuizResults';
|
||||
|
||||
export type QuestionState = {
|
||||
isSubmitted: boolean;
|
||||
@@ -25,10 +26,11 @@ type AIQuizContentProps = {
|
||||
quizSlug?: string;
|
||||
questions: QuizQuestion[];
|
||||
isLoading?: boolean;
|
||||
onNewQuiz?: () => void;
|
||||
};
|
||||
|
||||
export function AIQuizContent(props: AIQuizContentProps) {
|
||||
const { quizSlug, questions, isLoading } = props;
|
||||
const { quizSlug, questions, isLoading, onNewQuiz } = props;
|
||||
|
||||
const [activeQuestionIndex, setActiveQuestionIndex] = useState(0);
|
||||
const activeQuestion = questions[activeQuestionIndex];
|
||||
@@ -43,12 +45,10 @@ export function AIQuizContent(props: AIQuizContentProps) {
|
||||
|
||||
const handleSubmit = (status: QuestionState['status']) => {
|
||||
setQuestionStates((prev) => {
|
||||
const oldState = activeQuestionState ?? DEFAULT_QUESTION_STATE;
|
||||
|
||||
const newSelectedOptions = {
|
||||
...prev,
|
||||
[activeQuestionIndex]: {
|
||||
...oldState,
|
||||
...activeQuestionState,
|
||||
isSubmitted: true,
|
||||
status,
|
||||
},
|
||||
@@ -62,12 +62,10 @@ export function AIQuizContent(props: AIQuizContentProps) {
|
||||
|
||||
const handleSetUserAnswer = (userAnswer: string) => {
|
||||
setQuestionStates((prev) => {
|
||||
const oldState = activeQuestionState ?? DEFAULT_QUESTION_STATE;
|
||||
|
||||
const newSelectedOptions = {
|
||||
...prev,
|
||||
[activeQuestionIndex]: {
|
||||
...oldState,
|
||||
...activeQuestionState,
|
||||
userAnswer,
|
||||
},
|
||||
};
|
||||
@@ -78,12 +76,10 @@ export function AIQuizContent(props: AIQuizContentProps) {
|
||||
|
||||
const handleSetCorrectAnswer = (correctAnswer: string) => {
|
||||
setQuestionStates((prev) => {
|
||||
const oldState = activeQuestionState ?? DEFAULT_QUESTION_STATE;
|
||||
|
||||
const newSelectedOptions = {
|
||||
...prev,
|
||||
[activeQuestionIndex]: {
|
||||
...oldState,
|
||||
...activeQuestionState,
|
||||
correctAnswer,
|
||||
},
|
||||
};
|
||||
@@ -94,12 +90,10 @@ export function AIQuizContent(props: AIQuizContentProps) {
|
||||
|
||||
const handleSelectOptions = (options: number[]) => {
|
||||
setQuestionStates((prev) => {
|
||||
const oldState = activeQuestionState ?? DEFAULT_QUESTION_STATE;
|
||||
|
||||
const newSelectedOptions = {
|
||||
...prev,
|
||||
[activeQuestionIndex]: {
|
||||
...oldState,
|
||||
...activeQuestionState,
|
||||
selectedOptions: options,
|
||||
},
|
||||
};
|
||||
@@ -112,6 +106,12 @@ export function AIQuizContent(props: AIQuizContentProps) {
|
||||
setActiveQuestionIndex(activeQuestionIndex + 1);
|
||||
};
|
||||
|
||||
const handleRetry = () => {
|
||||
setActiveQuestionIndex(0);
|
||||
setQuestionStates({});
|
||||
setIsAllQuestionsSubmitted(false);
|
||||
};
|
||||
|
||||
const totalQuestions = questions?.length ?? 0;
|
||||
const progressPercentage = isLoading
|
||||
? 0
|
||||
@@ -119,15 +119,24 @@ export function AIQuizContent(props: AIQuizContentProps) {
|
||||
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-lg py-10">
|
||||
<QuizTopNavigation
|
||||
activeQuestionIndex={activeQuestionIndex}
|
||||
totalQuestions={totalQuestions}
|
||||
progressPercentage={progressPercentage}
|
||||
onPrevious={() => setActiveQuestionIndex(activeQuestionIndex - 1)}
|
||||
onNext={() => setActiveQuestionIndex(activeQuestionIndex + 1)}
|
||||
/>
|
||||
|
||||
{!isAllQuestionsSubmitted && (
|
||||
<QuizTopNavigation
|
||||
activeQuestionIndex={activeQuestionIndex}
|
||||
totalQuestions={totalQuestions}
|
||||
progressPercentage={progressPercentage}
|
||||
onPrevious={() => setActiveQuestionIndex(activeQuestionIndex - 1)}
|
||||
onNext={() => setActiveQuestionIndex(activeQuestionIndex + 1)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{isAllQuestionsSubmitted ? (
|
||||
<AIQuizResults
|
||||
questionStates={questionStates}
|
||||
totalQuestions={totalQuestions}
|
||||
onRetry={handleRetry}
|
||||
onNewQuiz={onNewQuiz ?? (() => {})}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
{activeQuestion && activeQuestion.type === 'mcq' && (
|
||||
<AIMCQQuestion
|
||||
|
89
src/components/AIQuiz/AIQuizResults.tsx
Normal file
89
src/components/AIQuiz/AIQuizResults.tsx
Normal file
@@ -0,0 +1,89 @@
|
||||
import {
|
||||
CheckCircle,
|
||||
PartyPopper,
|
||||
RefreshCcw,
|
||||
RotateCcw,
|
||||
XCircle,
|
||||
SkipForward,
|
||||
CheckCircle2Icon,
|
||||
} from 'lucide-react';
|
||||
import type { QuestionState } from './AIQuizContent';
|
||||
import { getPercentage } from '../../lib/number';
|
||||
|
||||
type AIQuizResultsProps = {
|
||||
questionStates: Record<number, QuestionState>;
|
||||
totalQuestions: number;
|
||||
onRetry: () => void;
|
||||
onNewQuiz: () => void;
|
||||
};
|
||||
|
||||
export function AIQuizResults(props: AIQuizResultsProps) {
|
||||
const { questionStates, totalQuestions, onRetry, onNewQuiz } = props;
|
||||
|
||||
const states = Object.values(questionStates);
|
||||
const correctCount = states.filter(
|
||||
(state) => state.status === 'correct',
|
||||
).length;
|
||||
|
||||
const incorrectCount = states.filter(
|
||||
(state) => state.status === 'incorrect',
|
||||
).length;
|
||||
|
||||
const skippedCount = states.filter(
|
||||
(state) => state.status === 'skipped',
|
||||
).length;
|
||||
|
||||
const accuracy = getPercentage(correctCount, totalQuestions);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center p-8 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>
|
||||
|
||||
<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="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>
|
||||
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-4">
|
||||
<button
|
||||
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
|
||||
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>
|
||||
);
|
||||
}
|
@@ -214,11 +214,6 @@ export function generateAiQuizQuestions(questionData: string): QuizQuestion[] {
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log('-'.repeat(20));
|
||||
console.log('CONTEXT:', context);
|
||||
console.log('LINE:', line);
|
||||
console.log('-'.repeat(20));
|
||||
|
||||
if (context === 'question') {
|
||||
currentQuestion.title += `\n${line}`;
|
||||
} else if (context === 'explanation') {
|
||||
|
Reference in New Issue
Block a user