mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2025-08-27 11:09:53 +02:00
Show user progress
This commit is contained in:
@@ -12,6 +12,7 @@ import { useToast } from '../../hooks/use-toast';
|
||||
type UserQuestionProgress = {
|
||||
know: string[];
|
||||
didNotKnow: string[];
|
||||
skipped: string[];
|
||||
};
|
||||
|
||||
type QuestionsListProps = {
|
||||
@@ -20,13 +21,11 @@ type QuestionsListProps = {
|
||||
};
|
||||
|
||||
export function QuestionsList(props: QuestionsListProps) {
|
||||
const { questions: defaultQuestions, groupId } = props;
|
||||
const { questions: unshuffledQuestions, groupId } = props;
|
||||
|
||||
const toast = useToast();
|
||||
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [isUpdatingStatus, setIsUpdatingStatus] = useState(false);
|
||||
|
||||
const [confettiEl, setConfettiEl] = useState<HTMLElement | null>(null);
|
||||
|
||||
const [questions, setQuestions] = useState<QuestionType[]>();
|
||||
@@ -72,33 +71,37 @@ export function QuestionsList(props: QuestionsListProps) {
|
||||
|
||||
const knownQuestions = userProgress?.know || [];
|
||||
const didNotKnowQuestions = userProgress?.didNotKnow || [];
|
||||
const skippedQuestions = userProgress?.skipped || [];
|
||||
|
||||
const pendingQuestions = defaultQuestions.filter((question) => {
|
||||
const pendingQuestions = unshuffledQuestions.filter((question) => {
|
||||
return (
|
||||
!knownQuestions.includes(question.id) &&
|
||||
!didNotKnowQuestions.includes(question.id)
|
||||
!didNotKnowQuestions.includes(question.id) &&
|
||||
!skippedQuestions.includes(question.id)
|
||||
);
|
||||
});
|
||||
|
||||
// Shuffle and set pending questions
|
||||
setPendingQuestions(pendingQuestions.sort(() => Math.random() - 0.5));
|
||||
setQuestions(defaultQuestions);
|
||||
setQuestions(unshuffledQuestions);
|
||||
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
||||
async function updateQuestionStatus(
|
||||
status: 'know' | 'dontKnow',
|
||||
status: 'know' | 'dontKnow' | 'skip',
|
||||
questionId: string
|
||||
) {
|
||||
setIsUpdatingStatus(true);
|
||||
let newProgress = userProgress || { know: [], didNotKnow: [] };
|
||||
setIsLoading(true);
|
||||
let newProgress = userProgress || { know: [], didNotKnow: [], skipped: [] };
|
||||
|
||||
if (!isLoggedIn()) {
|
||||
if (status === 'know') {
|
||||
newProgress.know.push(questionId);
|
||||
} else {
|
||||
} else if (status == 'dontKnow') {
|
||||
newProgress.didNotKnow.push(questionId);
|
||||
} else if (status == 'skip') {
|
||||
newProgress.skipped.push(questionId);
|
||||
}
|
||||
} else {
|
||||
const { response, error } = await httpPut<UserQuestionProgress>(
|
||||
@@ -120,16 +123,17 @@ export function QuestionsList(props: QuestionsListProps) {
|
||||
|
||||
setUserProgress(newProgress);
|
||||
setPendingQuestions(pendingQuestions.filter((q) => q.id !== questionId));
|
||||
setIsUpdatingStatus(false);
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
loadQuestions().then(() => null);
|
||||
}, [defaultQuestions]);
|
||||
}, [unshuffledQuestions]);
|
||||
|
||||
const knownCount = userProgress?.know.length || 0;
|
||||
const didNotKnowCount = userProgress?.didNotKnow.length || 0;
|
||||
const hasProgress = knownCount > 0 || didNotKnowCount > 0;
|
||||
const skippedCount = userProgress?.skipped.length || 0;
|
||||
const hasProgress = knownCount > 0 || didNotKnowCount > 0 || skippedCount > 0;
|
||||
|
||||
const currQuestion = pendingQuestions[0];
|
||||
|
||||
@@ -143,6 +147,10 @@ export function QuestionsList(props: QuestionsListProps) {
|
||||
/>
|
||||
|
||||
<QuestionsProgress
|
||||
knowCount={knownCount}
|
||||
didNotKnowCount={didNotKnowCount}
|
||||
skippedCount={skippedCount}
|
||||
totalCount={unshuffledQuestions?.length || questions?.length}
|
||||
isLoading={isLoading}
|
||||
showLoginAlert={!isLoggedIn() && hasProgress}
|
||||
/>
|
||||
@@ -154,7 +162,7 @@ export function QuestionsList(props: QuestionsListProps) {
|
||||
|
||||
<div className="flex flex-col gap-3 sm:flex-row">
|
||||
<button
|
||||
disabled={isLoading || isUpdatingStatus}
|
||||
disabled={isLoading}
|
||||
ref={alreadyKnowRef}
|
||||
onClick={(e) => {
|
||||
showConfetti(alreadyKnowRef.current);
|
||||
@@ -173,14 +181,17 @@ export function QuestionsList(props: QuestionsListProps) {
|
||||
() => null
|
||||
);
|
||||
}}
|
||||
disabled={isLoading || isUpdatingStatus}
|
||||
disabled={isLoading}
|
||||
className="flex flex-1 items-center rounded-xl border border-gray-300 bg-white py-3 px-4 text-black transition-colors hover:border-black hover:bg-black hover:text-white disabled:pointer-events-none disabled:opacity-50"
|
||||
>
|
||||
<Sparkles className="mr-1 h-4 text-current" />
|
||||
Didn't Know that
|
||||
</button>
|
||||
<button
|
||||
disabled={isLoading || isUpdatingStatus}
|
||||
onClick={() => {
|
||||
updateQuestionStatus('skip', currQuestion.id).finally(() => null);
|
||||
}}
|
||||
disabled={isLoading}
|
||||
data-next-question="skip"
|
||||
className="flex flex-1 items-center rounded-xl border border-red-600 p-3 text-red-600 hover:bg-red-600 hover:text-white disabled:pointer-events-none disabled:opacity-50"
|
||||
>
|
||||
|
@@ -1,43 +1,72 @@
|
||||
import { CheckCircle, RotateCcw, Sparkles } from 'lucide-react';
|
||||
import { CheckCircle, RotateCcw, SkipForward, Sparkles } from 'lucide-react';
|
||||
import { showLoginPopup } from '../../lib/popup';
|
||||
|
||||
type QuestionsProgressProps = {
|
||||
isLoading?: boolean;
|
||||
showLoginAlert?: boolean;
|
||||
knowCount?: number;
|
||||
didNotKnowCount?: number;
|
||||
totalCount?: number;
|
||||
skippedCount?: number;
|
||||
};
|
||||
|
||||
export function QuestionsProgress(props: QuestionsProgressProps) {
|
||||
const { showLoginAlert, isLoading = false } = props;
|
||||
const {
|
||||
showLoginAlert,
|
||||
isLoading = false,
|
||||
knowCount = 0,
|
||||
didNotKnowCount = 0,
|
||||
totalCount = 0,
|
||||
skippedCount = 0,
|
||||
} = props;
|
||||
|
||||
const totalSolved = knowCount + didNotKnowCount + skippedCount;
|
||||
const donePercentage = (totalSolved / totalCount) * 100;
|
||||
|
||||
return (
|
||||
<div className="mb-5 overflow-hidden rounded-lg border border-gray-300 bg-white p-6">
|
||||
<div className="mb-3 flex items-center text-gray-600">
|
||||
<div className="relative w-full flex-1 rounded-xl bg-gray-200 p-1">
|
||||
<div className="absolute bottom-0 left-0 top-0 w-[30%] rounded-xl bg-slate-800"></div>
|
||||
<div
|
||||
className="absolute bottom-0 left-0 top-0 rounded-xl bg-slate-800"
|
||||
style={{
|
||||
width: `${donePercentage}%`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<span className="ml-3 text-sm">5 / 100</span>
|
||||
<span className="ml-3 text-sm">
|
||||
{totalSolved} / {totalCount}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="relative -left-1 flex flex-col gap-2 text-sm text-black sm:flex-row sm:gap-3">
|
||||
<span className="flex items-center">
|
||||
<CheckCircle className="mr-1 h-4" />
|
||||
<span>Already knew</span>
|
||||
<span>Knew</span>
|
||||
<span className="ml-2 rounded-md bg-gray-200/80 px-1.5 font-medium text-black">
|
||||
44 Questions
|
||||
{knowCount} Questions
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<span className="flex items-center">
|
||||
<Sparkles className="mr-1 h-4" />
|
||||
<span>Didn't Know</span>
|
||||
<span>Learnt</span>
|
||||
<span className="ml-2 rounded-md bg-gray-200/80 px-1.5 font-medium text-black">
|
||||
20 Questions
|
||||
{didNotKnowCount} Questions
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<span className="flex items-center">
|
||||
<SkipForward className="mr-1 h-4" />
|
||||
<span>Skipped</span>
|
||||
<span className="ml-2 rounded-md bg-gray-200/80 px-1.5 font-medium text-black">
|
||||
{skippedCount} Questions
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<button className="flex items-center text-red-600 hover:text-red-900">
|
||||
<RotateCcw className="mr-1 h-4" />
|
||||
Reset Progress
|
||||
Reset
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
Reference in New Issue
Block a user