1
0
mirror of https://github.com/kamranahmedse/developer-roadmap.git synced 2025-09-09 00:30:40 +02:00

feat: remove question progress

This commit is contained in:
Arik Chakma
2025-06-23 23:33:28 +06:00
parent 4038681fb5
commit 831b1a6f5d
2 changed files with 26 additions and 142 deletions

View File

@@ -1,12 +1,9 @@
import { useEffect, useRef, useState } from 'react'; import { useRef, useState } from 'react';
import { QuestionsProgress } from './QuestionsProgress'; import { QuestionsProgress } from './QuestionsProgress';
import { CheckCircle, SkipForward, Sparkles } from 'lucide-react'; import { CheckCircle, SkipForward, Sparkles } from 'lucide-react';
import { QuestionCard } from './QuestionCard'; import { QuestionCard } from './QuestionCard';
import { QuestionLoader } from './QuestionLoader';
import { isLoggedIn } from '../../lib/jwt'; import { isLoggedIn } from '../../lib/jwt';
import type { QuestionType } from '../../lib/question-group'; import type { QuestionType } from '../../lib/question-group';
import { httpGet, httpPut } from '../../lib/http';
import { useToast } from '../../hooks/use-toast';
import { QuestionFinished } from './QuestionFinished'; import { QuestionFinished } from './QuestionFinished';
import { Confetti } from '../Confetti'; import { Confetti } from '../Confetti';
@@ -24,142 +21,42 @@ type QuestionsListProps = {
}; };
export function QuestionsList(props: QuestionsListProps) { export function QuestionsList(props: QuestionsListProps) {
const { questions: defaultQuestions, groupId } = props; const { questions } = props;
const toast = useToast();
const [questions, setQuestions] = useState(defaultQuestions);
const [isLoading, setIsLoading] = useState(true);
const [showConfetti, setShowConfetti] = useState(false); const [showConfetti, setShowConfetti] = useState(false);
const [currQuestionIndex, setCurrQuestionIndex] = useState(0); const [currQuestionIndex, setCurrQuestionIndex] = useState(0);
const [userProgress, setUserProgress] = useState<UserQuestionProgress>(); const [userProgress, setUserProgress] = useState<UserQuestionProgress>({
know: [],
dontKnow: [],
skip: [],
});
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
async function fetchUserProgress(): Promise<
UserQuestionProgress | undefined
> {
if (!isLoggedIn()) {
return;
}
const { response, error } = await httpGet<UserQuestionProgress>(
`${
import.meta.env.PUBLIC_API_URL
}/v1-get-user-question-progress/${groupId}`,
);
if (error) {
toast.error(error.message || 'Error fetching user progress');
return;
}
return response;
}
async function prepareProgress() {
const userProgress = await fetchUserProgress();
setUserProgress(userProgress);
const knownQuestions = userProgress?.know || [];
const didNotKnowQuestions = userProgress?.dontKnow || [];
const skipQuestions = userProgress?.skip || [];
const pendingQuestionIndex = questions.findIndex((question) => {
return (
!knownQuestions.includes(question.id) &&
!didNotKnowQuestions.includes(question.id) &&
!skipQuestions.includes(question.id)
);
});
setCurrQuestionIndex(pendingQuestionIndex);
setIsLoading(false);
}
async function resetProgress() { async function resetProgress() {
let knownQuestions = userProgress?.know || [];
let didNotKnowQuestions = userProgress?.dontKnow || [];
let skipQuestions = userProgress?.skip || [];
if (!isLoggedIn()) {
setQuestions(defaultQuestions);
knownQuestions = [];
didNotKnowQuestions = [];
skipQuestions = [];
} else {
setIsLoading(true);
const { response, error } = await httpPut<UserQuestionProgress>(
`${
import.meta.env.PUBLIC_API_URL
}/v1-reset-question-progress/${groupId}`,
{
status: 'reset',
},
);
if (error) {
toast.error(error.message || 'Error resetting progress');
return;
}
knownQuestions = response?.know || [];
didNotKnowQuestions = response?.dontKnow || [];
skipQuestions = response?.skip || [];
}
setCurrQuestionIndex(0); setCurrQuestionIndex(0);
setUserProgress({ setUserProgress({
know: knownQuestions, know: [],
dontKnow: didNotKnowQuestions, dontKnow: [],
skip: skipQuestions, skip: [],
}); });
setIsLoading(false);
} }
async function updateQuestionStatus( function updateQuestionStatus(
status: QuestionProgressType, status: QuestionProgressType,
questionId: string, questionId: string,
) { ) {
setIsLoading(true);
let newProgress = userProgress || { know: [], dontKnow: [], skip: [] }; let newProgress = userProgress || { know: [], dontKnow: [], skip: [] };
if (status === 'know') {
if (!isLoggedIn()) { newProgress.know.push(questionId);
if (status === 'know') { } else if (status == 'dontKnow') {
newProgress.know.push(questionId); newProgress.dontKnow.push(questionId);
} else if (status == 'dontKnow') { } else if (status == 'skip') {
newProgress.dontKnow.push(questionId); newProgress.skip.push(questionId);
} else if (status == 'skip') {
newProgress.skip.push(questionId);
}
} else {
const { response, error } = await httpPut<UserQuestionProgress>(
`${
import.meta.env.PUBLIC_API_URL
}/v1-update-question-status/${groupId}`,
{
status,
questionId,
questionGroupId: groupId,
},
);
if (error || !response) {
toast.error(error?.message || 'Error marking question status');
return;
}
newProgress = response;
} }
const nextQuestionIndex = currQuestionIndex + 1; const nextQuestionIndex = currQuestionIndex + 1;
setUserProgress(newProgress); setUserProgress(newProgress);
setIsLoading(false);
if (!nextQuestionIndex || !questions[nextQuestionIndex]) { if (!nextQuestionIndex || !questions[nextQuestionIndex]) {
setShowConfetti(true); setShowConfetti(true);
} }
@@ -167,17 +64,13 @@ export function QuestionsList(props: QuestionsListProps) {
setCurrQuestionIndex(nextQuestionIndex); setCurrQuestionIndex(nextQuestionIndex);
} }
useEffect(() => {
prepareProgress().then(() => null);
}, [questions]);
const knowCount = userProgress?.know.length || 0; const knowCount = userProgress?.know.length || 0;
const dontKnowCount = userProgress?.dontKnow.length || 0; const dontKnowCount = userProgress?.dontKnow.length || 0;
const skipCount = userProgress?.skip.length || 0; const skipCount = userProgress?.skip.length || 0;
const hasProgress = knowCount > 0 || dontKnowCount > 0 || skipCount > 0; const hasProgress = knowCount > 0 || dontKnowCount > 0 || skipCount > 0;
const currQuestion = questions[currQuestionIndex]; const currQuestion = questions[currQuestionIndex];
const hasFinished = !isLoading && hasProgress && currQuestionIndex === -1; const hasFinished = hasProgress && currQuestionIndex === -1;
return ( return (
<div className="mb-0 gap-3 text-center sm:mb-40"> <div className="mb-0 gap-3 text-center sm:mb-40">
@@ -186,7 +79,6 @@ export function QuestionsList(props: QuestionsListProps) {
didNotKnowCount={dontKnowCount} didNotKnowCount={dontKnowCount}
skippedCount={skipCount} skippedCount={skipCount}
totalCount={questions?.length} totalCount={questions?.length}
isLoading={isLoading}
showLoginAlert={!isLoggedIn() && hasProgress} showLoginAlert={!isLoggedIn() && hasProgress}
onResetClick={() => { onResetClick={() => {
resetProgress().finally(() => null); resetProgress().finally(() => null);
@@ -196,7 +88,7 @@ export function QuestionsList(props: QuestionsListProps) {
currQuestionIndex !== -1 && currQuestionIndex !== -1 &&
currQuestionIndex < questions.length - 1 currQuestionIndex < questions.length - 1
) { ) {
updateQuestionStatus('skip', currQuestion.id).finally(() => null); updateQuestionStatus('skip', currQuestion.id);
} }
}} }}
onPrevClick={() => { onPrevClick={() => {
@@ -244,8 +136,7 @@ export function QuestionsList(props: QuestionsListProps) {
}} }}
/> />
)} )}
{!isLoading && currQuestion && <QuestionCard question={currQuestion} />} {currQuestion && <QuestionCard question={currQuestion} />}
{isLoading && <QuestionLoader />}
</div> </div>
<div <div
@@ -254,11 +145,11 @@ export function QuestionsList(props: QuestionsListProps) {
}`} }`}
> >
<button <button
disabled={isLoading || !currQuestion} disabled={!currQuestion}
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
updateQuestionStatus('know', currQuestion.id).finally(() => null); updateQuestionStatus('know', currQuestion.id);
}} }}
className="flex flex-1 items-center rounded-md border border-gray-300 bg-white px-2 py-2 text-sm text-black transition-colors hover:border-black hover:bg-black hover:text-white disabled:pointer-events-none disabled:opacity-50 sm:rounded-lg sm:px-4 sm:py-3 sm:text-base" className="flex flex-1 items-center rounded-md border border-gray-300 bg-white px-2 py-2 text-sm text-black transition-colors hover:border-black hover:bg-black hover:text-white disabled:pointer-events-none disabled:opacity-50 sm:rounded-lg sm:px-4 sm:py-3 sm:text-base"
> >
@@ -267,11 +158,9 @@ export function QuestionsList(props: QuestionsListProps) {
</button> </button>
<button <button
onClick={() => { onClick={() => {
updateQuestionStatus('dontKnow', currQuestion.id).finally( updateQuestionStatus('dontKnow', currQuestion.id);
() => null,
);
}} }}
disabled={isLoading || !currQuestion} disabled={!currQuestion}
className="flex flex-1 items-center rounded-md border border-gray-300 bg-white px-2 py-2 text-sm text-black transition-colors hover:border-black hover:bg-black hover:text-white disabled:pointer-events-none disabled:opacity-50 sm:rounded-lg sm:px-4 sm:py-3 sm:text-base" className="flex flex-1 items-center rounded-md border border-gray-300 bg-white px-2 py-2 text-sm text-black transition-colors hover:border-black hover:bg-black hover:text-white disabled:pointer-events-none disabled:opacity-50 sm:rounded-lg sm:px-4 sm:py-3 sm:text-base"
> >
<Sparkles className="mr-1 h-4 text-current" /> <Sparkles className="mr-1 h-4 text-current" />
@@ -279,9 +168,9 @@ export function QuestionsList(props: QuestionsListProps) {
</button> </button>
<button <button
onClick={() => { onClick={() => {
updateQuestionStatus('skip', currQuestion.id).finally(() => null); updateQuestionStatus('skip', currQuestion.id);
}} }}
disabled={isLoading || !currQuestion} disabled={!currQuestion}
data-next-question="skip" data-next-question="skip"
className="flex flex-1 items-center rounded-md border border-red-600 px-2 py-2 text-sm text-red-600 hover:bg-red-600 hover:text-white disabled:pointer-events-none disabled:opacity-50 sm:rounded-lg sm:px-4 sm:py-3 sm:text-base" className="flex flex-1 items-center rounded-md border border-red-600 px-2 py-2 text-sm text-red-600 hover:bg-red-600 hover:text-white disabled:pointer-events-none disabled:opacity-50 sm:rounded-lg sm:px-4 sm:py-3 sm:text-base"
> >

View File

@@ -1,9 +1,6 @@
--- ---
import GridItem from '../../components/GridItem.astro';
import SimplePageHeader from '../../components/SimplePageHeader.astro';
import BaseLayout from '../../layouts/BaseLayout.astro'; import BaseLayout from '../../layouts/BaseLayout.astro';
import Footer from '../../components/Footer.astro'; import Footer from '../../components/Footer.astro';
import AstroIcon from '../../components/AstroIcon.astro';
import { QuestionsList } from '../../components/Questions/QuestionsList'; import { QuestionsList } from '../../components/Questions/QuestionsList';
import { import {
@@ -11,8 +8,6 @@ import {
type QuestionGroupType, type QuestionGroupType,
} from '../../lib/question-group'; } from '../../lib/question-group';
import QuestionGuide from '../../components/Questions/QuestionGuide.astro'; import QuestionGuide from '../../components/Questions/QuestionGuide.astro';
import { markdownToHtml } from '../../lib/markdown';
import MarkdownFile from '../../components/MarkdownFile.astro';
export const prerender = true; export const prerender = true;