mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2025-08-26 18:44:57 +02:00
feat: add finished screen for questions
This commit is contained in:
97
src/components/Questions/QuestionFinished.tsx
Normal file
97
src/components/Questions/QuestionFinished.tsx
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
import type { ReactNode } from 'react';
|
||||||
|
import {
|
||||||
|
PartyPopper,
|
||||||
|
RefreshCcw,
|
||||||
|
SkipForward,
|
||||||
|
Sparkles,
|
||||||
|
ThumbsUp,
|
||||||
|
} from 'lucide-react';
|
||||||
|
import type { QuestionProgressType } from './QuestionsList';
|
||||||
|
|
||||||
|
type ProgressStatButtonProps = {
|
||||||
|
isDisabled?: boolean;
|
||||||
|
icon: ReactNode;
|
||||||
|
label: string;
|
||||||
|
count: number;
|
||||||
|
onClick: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
function ProgressStatButton(props: ProgressStatButtonProps) {
|
||||||
|
const { icon, label, count, onClick, isDisabled = false } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
disabled={isDisabled}
|
||||||
|
onClick={onClick}
|
||||||
|
className="group relative flex flex-1 items-center overflow-hidden rounded-xl border border-gray-300 bg-white py-3 px-4 text-black disabled:pointer-events-none disabled:opacity-50"
|
||||||
|
>
|
||||||
|
{icon}
|
||||||
|
<span className="flex flex-grow justify-between">
|
||||||
|
<span>{label}</span>
|
||||||
|
<span>{count}</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span className="absolute top-full left-0 right-0 flex h-full items-center justify-center border border-black bg-black text-white transition-all duration-200 group-hover:top-0">
|
||||||
|
Restart
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
type QuestionFinishedProps = {
|
||||||
|
knowCount: number;
|
||||||
|
didNotKnowCount: number;
|
||||||
|
skippedCount: number;
|
||||||
|
totalCount: number;
|
||||||
|
onReset: (type: QuestionProgressType | 'all') => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function QuestionFinished(props: QuestionFinishedProps) {
|
||||||
|
const { knowCount, didNotKnowCount, skippedCount, totalCount, onReset } =
|
||||||
|
props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative flex flex-grow flex-col items-center justify-center">
|
||||||
|
<PartyPopper className="mb-4 h-24 w-24 text-gray-300" />
|
||||||
|
<h1 className="text-2xl font-semibold text-gray-700">
|
||||||
|
Questions Finished
|
||||||
|
</h1>
|
||||||
|
<p className="mt-2 text-gray-500">
|
||||||
|
Click below revisit specific or all questions
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="mt-5 mb-5 flex w-full flex-col gap-3 px-2 sm:flex-row sm:px-16">
|
||||||
|
<ProgressStatButton
|
||||||
|
icon={<ThumbsUp className="mr-1 h-4" />}
|
||||||
|
label="Knew"
|
||||||
|
count={knowCount}
|
||||||
|
isDisabled={knowCount === 0}
|
||||||
|
onClick={() => onReset('know')}
|
||||||
|
/>
|
||||||
|
<ProgressStatButton
|
||||||
|
icon={<Sparkles className="mr-1 h-4" />}
|
||||||
|
label="Learned"
|
||||||
|
count={didNotKnowCount}
|
||||||
|
isDisabled={didNotKnowCount === 0}
|
||||||
|
onClick={() => onReset('dontKnow')}
|
||||||
|
/>
|
||||||
|
<ProgressStatButton
|
||||||
|
icon={<SkipForward className="mr-1 h-4" />}
|
||||||
|
label="Skipped"
|
||||||
|
count={skippedCount}
|
||||||
|
isDisabled={skippedCount === 0}
|
||||||
|
onClick={() => onReset('skip')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mt-2 text-sm">
|
||||||
|
<button
|
||||||
|
onClick={() => onReset('all')}
|
||||||
|
className="flex items-center gap-0.5 text-red-700 hover:text-black"
|
||||||
|
>
|
||||||
|
<RefreshCcw className="mr-1 h-4" />
|
||||||
|
Restart Asking
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@@ -8,6 +8,7 @@ import type { QuestionType } from '../../lib/question-group';
|
|||||||
import { Confetti } from '../Confetti';
|
import { Confetti } from '../Confetti';
|
||||||
import { httpGet, httpPut } from '../../lib/http';
|
import { httpGet, httpPut } from '../../lib/http';
|
||||||
import { useToast } from '../../hooks/use-toast';
|
import { useToast } from '../../hooks/use-toast';
|
||||||
|
import { QuestionFinished } from './QuestionFinished';
|
||||||
|
|
||||||
type UserQuestionProgress = {
|
type UserQuestionProgress = {
|
||||||
know: string[];
|
know: string[];
|
||||||
@@ -15,7 +16,7 @@ type UserQuestionProgress = {
|
|||||||
skip: string[];
|
skip: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type ProgressType = keyof UserQuestionProgress;
|
export type QuestionProgressType = keyof UserQuestionProgress;
|
||||||
|
|
||||||
type QuestionsListProps = {
|
type QuestionsListProps = {
|
||||||
groupId: string;
|
groupId: string;
|
||||||
@@ -90,7 +91,7 @@ export function QuestionsList(props: QuestionsListProps) {
|
|||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function resetProgress(type: ProgressType | 'all' = 'all') {
|
async function resetProgress(type: QuestionProgressType | 'all' = 'all') {
|
||||||
let knownQuestions = userProgress?.know || [];
|
let knownQuestions = userProgress?.know || [];
|
||||||
let didNotKnowQuestions = userProgress?.dontKnow || [];
|
let didNotKnowQuestions = userProgress?.dontKnow || [];
|
||||||
let skipQuestions = userProgress?.skip || [];
|
let skipQuestions = userProgress?.skip || [];
|
||||||
@@ -146,7 +147,7 @@ export function QuestionsList(props: QuestionsListProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function updateQuestionStatus(
|
async function updateQuestionStatus(
|
||||||
status: ProgressType,
|
status: QuestionProgressType,
|
||||||
questionId: string
|
questionId: string
|
||||||
) {
|
) {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
@@ -216,13 +217,24 @@ export function QuestionsList(props: QuestionsListProps) {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="relative mb-4 flex min-h-[400px] w-full overflow-hidden rounded-lg border border-gray-300 bg-white">
|
<div className="relative mb-4 flex min-h-[400px] w-full overflow-hidden rounded-lg border border-gray-300 bg-white">
|
||||||
{!isLoading && <QuestionCard question={currQuestion} />}
|
{!isLoading && hasProgress && !currQuestion && (
|
||||||
|
<QuestionFinished
|
||||||
|
totalCount={unshuffledQuestions?.length || questions?.length || 0}
|
||||||
|
knowCount={knowCount}
|
||||||
|
didNotKnowCount={dontKnowCount}
|
||||||
|
skippedCount={skipCount}
|
||||||
|
onReset={(type: QuestionProgressType | 'all') => {
|
||||||
|
resetProgress(type).finally(() => null);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{!isLoading && currQuestion && <QuestionCard question={currQuestion} />}
|
||||||
{isLoading && <QuestionLoader />}
|
{isLoading && <QuestionLoader />}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col gap-3 sm:flex-row">
|
<div className="flex flex-col gap-3 sm:flex-row">
|
||||||
<button
|
<button
|
||||||
disabled={isLoading}
|
disabled={isLoading || !currQuestion}
|
||||||
ref={alreadyKnowRef}
|
ref={alreadyKnowRef}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
showConfetti(alreadyKnowRef.current);
|
showConfetti(alreadyKnowRef.current);
|
||||||
@@ -241,7 +253,7 @@ export function QuestionsList(props: QuestionsListProps) {
|
|||||||
() => null
|
() => null
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
disabled={isLoading}
|
disabled={isLoading || !currQuestion}
|
||||||
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"
|
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" />
|
<Sparkles className="mr-1 h-4 text-current" />
|
||||||
@@ -251,7 +263,7 @@ export function QuestionsList(props: QuestionsListProps) {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
updateQuestionStatus('skip', currQuestion.id).finally(() => null);
|
updateQuestionStatus('skip', currQuestion.id).finally(() => null);
|
||||||
}}
|
}}
|
||||||
disabled={isLoading}
|
disabled={isLoading || !currQuestion}
|
||||||
data-next-question="skip"
|
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"
|
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"
|
||||||
>
|
>
|
||||||
|
Reference in New Issue
Block a user