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:
@@ -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"
|
||||||
>
|
>
|
||||||
|
@@ -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;
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user