1
0
mirror of https://github.com/kamranahmedse/developer-roadmap.git synced 2025-09-02 22:02:39 +02:00
This commit is contained in:
Arik Chakma
2025-07-03 00:20:26 +06:00
parent 70d0f7c82e
commit c370bafc53
6 changed files with 57 additions and 33 deletions

View File

@@ -23,6 +23,8 @@ const DEFAULT_QUESTION_STATE: QuestionState = {
status: 'pending',
};
type QuizStatus = 'answering' | 'submitted' | 'reviewing';
type AIQuizContentProps = {
quizSlug?: string;
questions: QuizQuestion[];
@@ -38,7 +40,7 @@ export function AIQuizContent(props: AIQuizContentProps) {
const [questionStates, setQuestionStates] = useState<
Record<number, QuestionState>
>({});
const [isAllQuestionsSubmitted, setIsAllQuestionsSubmitted] = useState(false);
const [quizStatus, setQuizStatus] = useState<QuizStatus>('answering');
const activeQuestionState =
questionStates[activeQuestionIndex] ?? DEFAULT_QUESTION_STATE;
@@ -59,7 +61,9 @@ export function AIQuizContent(props: AIQuizContentProps) {
return newSelectedOptions;
});
setIsAllQuestionsSubmitted(activeQuestionIndex === questions.length - 1);
setQuizStatus(
activeQuestionIndex === questions.length - 1 ? 'submitted' : 'answering',
);
};
const handleSetUserAnswer = (userAnswer: string) => {
@@ -112,34 +116,54 @@ export function AIQuizContent(props: AIQuizContentProps) {
});
};
const handleNext = () => {
setActiveQuestionIndex(activeQuestionIndex + 1);
};
const handleRetry = () => {
setActiveQuestionIndex(0);
setQuestionStates({});
setIsAllQuestionsSubmitted(false);
setQuizStatus('answering');
};
const hasNextQuestion = activeQuestionIndex < questions.length - 1;
const hasPreviousQuestion = activeQuestionIndex > 0;
const totalQuestions = questions?.length ?? 0;
const isAllQuestionsSubmitted =
Object.values(questionStates).filter((state) => state.status !== 'pending')
.length === totalQuestions;
const progressPercentage = isLoading
? 0
: getPercentage(activeQuestionIndex + 1, totalQuestions);
const shouldShowQuestions =
quizStatus === 'answering' || quizStatus === 'reviewing';
const handleNextQuestion = () => {
if (!hasNextQuestion) {
setQuizStatus(isAllQuestionsSubmitted ? 'submitted' : 'reviewing');
return;
}
setActiveQuestionIndex(activeQuestionIndex + 1);
};
return (
<div className="mx-auto w-full max-w-lg py-10">
{!isAllQuestionsSubmitted && (
{shouldShowQuestions && (
<QuizTopNavigation
activeQuestionIndex={activeQuestionIndex}
totalQuestions={totalQuestions}
progressPercentage={progressPercentage}
onPrevious={() => setActiveQuestionIndex(activeQuestionIndex - 1)}
onNext={() => setActiveQuestionIndex(activeQuestionIndex + 1)}
onPrevious={() => {
if (!hasPreviousQuestion) {
return;
}
setActiveQuestionIndex(activeQuestionIndex - 1);
}}
onNext={handleNextQuestion}
/>
)}
{isAllQuestionsSubmitted && (
{quizStatus === 'submitted' && (
<AIQuizResults
questionStates={questionStates}
totalQuestions={totalQuestions}
@@ -147,10 +171,14 @@ export function AIQuizContent(props: AIQuizContentProps) {
onNewQuiz={() => {
window.location.href = '/ai/quiz';
}}
onReview={(questionIndex) => {
setActiveQuestionIndex(questionIndex);
setQuizStatus('reviewing');
}}
/>
)}
{!isAllQuestionsSubmitted && (
{shouldShowQuestions && (
<>
{activeQuestion && activeQuestion.type === 'mcq' && (
<AIMCQQuestion
@@ -158,7 +186,7 @@ export function AIQuizContent(props: AIQuizContentProps) {
questionState={activeQuestionState}
setSelectedOptions={handleSelectOptions}
onSubmit={handleSubmit}
onNext={handleNext}
onNext={handleNextQuestion}
/>
)}
@@ -168,7 +196,7 @@ export function AIQuizContent(props: AIQuizContentProps) {
question={activeQuestion}
questionState={activeQuestionState}
onSubmit={handleSubmit}
onNext={handleNext}
onNext={handleNextQuestion}
setUserAnswer={handleSetUserAnswer}
setCorrectAnswer={handleSetCorrectAnswer}
/>

View File

@@ -218,9 +218,7 @@ export function AIQuizGenerator() {
format={selectedFormatTitle || selectedFormat}
questionAnswerChatMessages={questionAnswerChatMessages}
setQuestionAnswerChatMessages={setQuestionAnswerChatMessages}
onGenerateNow={() => {
handleSubmit();
}}
from="quiz"
/>
)}

View File

@@ -22,10 +22,12 @@ type AIQuizResultsProps = {
totalQuestions: number;
onRetry: () => void;
onNewQuiz: () => void;
onReview?: (questionIndex: number) => void;
};
export function AIQuizResults(props: AIQuizResultsProps) {
const { questionStates, totalQuestions, onRetry, onNewQuiz } = props;
const { questionStates, totalQuestions, onRetry, onNewQuiz, onReview } =
props;
const states = Object.values(questionStates);
const correctCount = states.filter(
@@ -66,10 +68,11 @@ export function AIQuizResults(props: AIQuizResultsProps) {
const isSkipped = status === 'skipped';
return (
<div
<button
key={quizIndex}
onClick={() => onReview?.(quizIndex)}
className={cn(
'flex aspect-square flex-col items-center justify-center rounded-xl border border-gray-200 p-2',
'flex aspect-square flex-col items-center justify-center rounded-xl border border-gray-200 p-2 hover:opacity-80',
isCorrect && 'bg-green-700 text-white',
isIncorrect && 'bg-red-700 text-white',
isSkipped && 'bg-gray-700 text-white',
@@ -78,7 +81,7 @@ export function AIQuizResults(props: AIQuizResultsProps) {
{isCorrect && <CheckIcon className="h-6 w-6" />}
{isIncorrect && <XIcon className="h-6 w-6" />}
{isSkipped && <SkipForwardIcon className="h-6 w-6" />}
</div>
</button>
);
})}
</div>

View File

@@ -227,9 +227,6 @@ export function ContentGenerator() {
format={selectedFormat}
questionAnswerChatMessages={questionAnswerChatMessages}
setQuestionAnswerChatMessages={setQuestionAnswerChatMessages}
onGenerateNow={() => {
handleSubmit();
}}
/>
)}

View File

@@ -27,9 +27,11 @@ type QuestionAnswerChatProps = {
setQuestionAnswerChatMessages: (
messages: QuestionAnswerChatMessage[],
) => void;
onGenerateNow: () => void;
defaultQuestions?: AIQuestionSuggestionsResponse['questions'];
type?: 'create' | 'update';
from?: 'content' | 'quiz';
className?: string;
};
@@ -40,9 +42,9 @@ export function QuestionAnswerChat(props: QuestionAnswerChatProps) {
defaultQuestions,
questionAnswerChatMessages,
setQuestionAnswerChatMessages,
onGenerateNow,
type = 'create',
className = '',
from = 'content',
} = props;
const [activeMessageIndex, setActiveMessageIndex] = useState(
@@ -58,7 +60,7 @@ export function QuestionAnswerChat(props: QuestionAnswerChatProps) {
data: aiQuestionSuggestions,
isLoading: isLoadingAiQuestionSuggestions,
} = useQuery(
aiQuestionSuggestionsOptions({ term, format }, defaultQuestions),
aiQuestionSuggestionsOptions({ term, format, from }, defaultQuestions),
queryClient,
);
@@ -113,11 +115,6 @@ export function QuestionAnswerChat(props: QuestionAnswerChatProps) {
scrollToBottom();
};
const canGenerateNow =
// user can generate after answering 5 questions -> 5 * 2 messages (user and assistant)
!isLoadingAiQuestionSuggestions && questionAnswerChatMessages.length >= 10;
const canReset = questionAnswerChatMessages.length >= 2;
const handleReset = () => {
setQuestionAnswerChatMessages([]);
setActiveMessageIndex(0);

View File

@@ -4,6 +4,7 @@ import { httpGet } from '../lib/query-http';
type AIQuestionSuggestionsQuery = {
term: string;
format: string;
from?: 'content' | 'quiz';
};
export type AIQuestionSuggestionsResponse = {
@@ -31,7 +32,7 @@ export function aiQuestionSuggestionsOptions(
query,
);
},
enabled: !!query.term && !!query.format,
enabled: !!query.term && !!query.format && !!query.from,
refetchOnMount: false,
});
}