mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2025-03-19 06:39:44 +01:00
Add functionality to go next and back on questions
This commit is contained in:
parent
a14d8b5f90
commit
4c615f85e5
@ -13,28 +13,19 @@ type ProgressStatButtonProps = {
|
||||
icon: ReactNode;
|
||||
label: string;
|
||||
count: number;
|
||||
onClick: () => void;
|
||||
};
|
||||
|
||||
function ProgressStatButton(props: ProgressStatButtonProps) {
|
||||
const { icon, label, count, onClick, isDisabled = false } = props;
|
||||
function ProgressStatLabel(props: ProgressStatButtonProps) {
|
||||
const { icon, label, count } = props;
|
||||
|
||||
return (
|
||||
<button
|
||||
disabled={isDisabled}
|
||||
onClick={onClick}
|
||||
className="group relative flex flex-1 items-center overflow-hidden rounded-md border border-gray-300 bg-white px-2 py-2 text-sm text-black transition-colors hover:border-black disabled:pointer-events-none disabled:opacity-50 sm:rounded-xl sm:px-4 sm:py-3 sm:text-base"
|
||||
>
|
||||
<span className="group relative flex flex-1 items-center overflow-hidden rounded-md border border-gray-300 bg-white px-2 py-2 text-sm text-black transition-colors disabled:opacity-50 sm:rounded-xl sm:px-4 sm:py-3 sm:text-base">
|
||||
{icon}
|
||||
<span className="flex flex-grow justify-between">
|
||||
<span>{label}</span>
|
||||
<span>{count}</span>
|
||||
</span>
|
||||
|
||||
<span className="absolute left-0 right-0 top-full flex h-full items-center justify-center border border-black bg-black text-white transition-all duration-200 group-hover:top-0">
|
||||
Restart Asking
|
||||
</span>
|
||||
</button>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
@ -43,12 +34,11 @@ type QuestionFinishedProps = {
|
||||
didNotKnowCount: number;
|
||||
skippedCount: number;
|
||||
totalCount: number;
|
||||
onReset: (type: QuestionProgressType | 'reset') => void;
|
||||
onReset: () => void;
|
||||
};
|
||||
|
||||
export function QuestionFinished(props: QuestionFinishedProps) {
|
||||
const { knowCount, didNotKnowCount, skippedCount, totalCount, onReset } =
|
||||
props;
|
||||
const { knowCount, didNotKnowCount, skippedCount, onReset } = props;
|
||||
|
||||
return (
|
||||
<div className="relative flex flex-grow flex-col items-center justify-center px-4 sm:px-0">
|
||||
@ -63,31 +53,25 @@ export function QuestionFinished(props: QuestionFinishedProps) {
|
||||
</p>
|
||||
|
||||
<div className="mb-5 mt-5 flex w-full flex-col gap-1.5 px-2 sm:flex-row sm:gap-3 sm:px-16">
|
||||
<ProgressStatButton
|
||||
<ProgressStatLabel
|
||||
icon={<ThumbsUp className="mr-1 h-4" />}
|
||||
label="Knew"
|
||||
count={knowCount}
|
||||
isDisabled={knowCount === 0}
|
||||
onClick={() => onReset('know')}
|
||||
/>
|
||||
<ProgressStatButton
|
||||
<ProgressStatLabel
|
||||
icon={<Sparkles className="mr-1 h-4" />}
|
||||
label="Learned"
|
||||
count={didNotKnowCount}
|
||||
isDisabled={didNotKnowCount === 0}
|
||||
onClick={() => onReset('dontKnow')}
|
||||
/>
|
||||
<ProgressStatButton
|
||||
<ProgressStatLabel
|
||||
icon={<SkipForward className="mr-1 h-4" />}
|
||||
label="Skipped"
|
||||
count={skippedCount}
|
||||
isDisabled={skippedCount === 0}
|
||||
onClick={() => onReset('skip')}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-4 mt-2 text-sm sm:mb-0">
|
||||
<button
|
||||
onClick={() => onReset('reset')}
|
||||
onClick={() => onReset()}
|
||||
className="flex items-center gap-0.5 text-sm text-red-700 hover:text-black sm:text-base"
|
||||
>
|
||||
<RefreshCcw className="mr-1 h-4" />
|
||||
|
@ -24,14 +24,14 @@ type QuestionsListProps = {
|
||||
};
|
||||
|
||||
export function QuestionsList(props: QuestionsListProps) {
|
||||
const { questions: unshuffledQuestions, groupId } = props;
|
||||
const { questions: defaultQuestions, groupId } = props;
|
||||
|
||||
const toast = useToast();
|
||||
|
||||
const [questions, setQuestions] = useState(defaultQuestions);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [showConfetti, setShowConfetti] = useState(false);
|
||||
const [questions, setQuestions] = useState<QuestionType[]>();
|
||||
const [pendingQuestions, setPendingQuestions] = useState<QuestionType[]>([]);
|
||||
const [currQuestionIndex, setCurrQuestionIndex] = useState(0);
|
||||
|
||||
const [userProgress, setUserProgress] = useState<UserQuestionProgress>();
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
@ -57,7 +57,7 @@ export function QuestionsList(props: QuestionsListProps) {
|
||||
return response;
|
||||
}
|
||||
|
||||
async function loadQuestions() {
|
||||
async function prepareProgress() {
|
||||
const userProgress = await fetchUserProgress();
|
||||
setUserProgress(userProgress);
|
||||
|
||||
@ -65,7 +65,7 @@ export function QuestionsList(props: QuestionsListProps) {
|
||||
const didNotKnowQuestions = userProgress?.dontKnow || [];
|
||||
const skipQuestions = userProgress?.skip || [];
|
||||
|
||||
const pendingQuestions = unshuffledQuestions.filter((question) => {
|
||||
const pendingQuestionIndex = questions.findIndex((question) => {
|
||||
return (
|
||||
!knownQuestions.includes(question.id) &&
|
||||
!didNotKnowQuestions.includes(question.id) &&
|
||||
@ -73,31 +73,21 @@ export function QuestionsList(props: QuestionsListProps) {
|
||||
);
|
||||
});
|
||||
|
||||
// Shuffle and set pending questions
|
||||
// setPendingQuestions(pendingQuestions.sort(() => Math.random() - 0.5));
|
||||
setPendingQuestions(pendingQuestions);
|
||||
setQuestions(unshuffledQuestions);
|
||||
|
||||
setCurrQuestionIndex(pendingQuestionIndex);
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
||||
async function resetProgress(type: QuestionProgressType | 'reset' = 'reset') {
|
||||
async function resetProgress() {
|
||||
let knownQuestions = userProgress?.know || [];
|
||||
let didNotKnowQuestions = userProgress?.dontKnow || [];
|
||||
let skipQuestions = userProgress?.skip || [];
|
||||
|
||||
if (!isLoggedIn()) {
|
||||
if (type === 'know') {
|
||||
knownQuestions = [];
|
||||
} else if (type === 'dontKnow') {
|
||||
didNotKnowQuestions = [];
|
||||
} else if (type === 'skip') {
|
||||
skipQuestions = [];
|
||||
} else if (type === 'reset') {
|
||||
knownQuestions = [];
|
||||
didNotKnowQuestions = [];
|
||||
skipQuestions = [];
|
||||
}
|
||||
setQuestions(defaultQuestions);
|
||||
|
||||
knownQuestions = [];
|
||||
didNotKnowQuestions = [];
|
||||
skipQuestions = [];
|
||||
} else {
|
||||
setIsLoading(true);
|
||||
|
||||
@ -106,7 +96,7 @@ export function QuestionsList(props: QuestionsListProps) {
|
||||
import.meta.env.PUBLIC_API_URL
|
||||
}/v1-reset-question-progress/${groupId}`,
|
||||
{
|
||||
status: type,
|
||||
status: 'reset',
|
||||
},
|
||||
);
|
||||
|
||||
@ -120,21 +110,13 @@ export function QuestionsList(props: QuestionsListProps) {
|
||||
skipQuestions = response?.skip || [];
|
||||
}
|
||||
|
||||
const pendingQuestions = unshuffledQuestions.filter((question) => {
|
||||
return (
|
||||
!knownQuestions.includes(question.id) &&
|
||||
!didNotKnowQuestions.includes(question.id) &&
|
||||
!skipQuestions.includes(question.id)
|
||||
);
|
||||
});
|
||||
|
||||
setCurrQuestionIndex(0);
|
||||
setUserProgress({
|
||||
know: knownQuestions,
|
||||
dontKnow: didNotKnowQuestions,
|
||||
skip: skipQuestions,
|
||||
});
|
||||
|
||||
setPendingQuestions(pendingQuestions.sort(() => Math.random() - 0.5));
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
||||
@ -173,30 +155,29 @@ export function QuestionsList(props: QuestionsListProps) {
|
||||
newProgress = response;
|
||||
}
|
||||
|
||||
const updatedQuestionList = pendingQuestions.filter(
|
||||
(q) => q.id !== questionId,
|
||||
);
|
||||
const nextQuestionIndex = currQuestionIndex + 1;
|
||||
|
||||
setUserProgress(newProgress);
|
||||
setPendingQuestions(updatedQuestionList);
|
||||
setIsLoading(false);
|
||||
|
||||
if (updatedQuestionList.length === 0) {
|
||||
if (!nextQuestionIndex || !questions[nextQuestionIndex]) {
|
||||
setShowConfetti(true);
|
||||
}
|
||||
|
||||
setCurrQuestionIndex(nextQuestionIndex);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
loadQuestions().then(() => null);
|
||||
}, [unshuffledQuestions]);
|
||||
prepareProgress().then(() => null);
|
||||
}, [questions]);
|
||||
|
||||
const knowCount = userProgress?.know.length || 0;
|
||||
const dontKnowCount = userProgress?.dontKnow.length || 0;
|
||||
const skipCount = userProgress?.skip.length || 0;
|
||||
const hasProgress = knowCount > 0 || dontKnowCount > 0 || skipCount > 0;
|
||||
|
||||
const currQuestion = pendingQuestions[0];
|
||||
const hasFinished = !isLoading && hasProgress && !currQuestion;
|
||||
const currQuestion = questions[currQuestionIndex];
|
||||
const hasFinished = !isLoading && hasProgress && currQuestionIndex === -1;
|
||||
|
||||
return (
|
||||
<div className="mb-0 gap-3 text-center sm:mb-40">
|
||||
@ -204,11 +185,37 @@ export function QuestionsList(props: QuestionsListProps) {
|
||||
knowCount={knowCount}
|
||||
didNotKnowCount={dontKnowCount}
|
||||
skippedCount={skipCount}
|
||||
totalCount={unshuffledQuestions?.length || questions?.length}
|
||||
totalCount={questions?.length}
|
||||
isLoading={isLoading}
|
||||
showLoginAlert={!isLoggedIn() && hasProgress}
|
||||
onResetClick={() => {
|
||||
resetProgress('reset').finally(() => null);
|
||||
resetProgress().finally(() => null);
|
||||
}}
|
||||
onNextClick={() => {
|
||||
if (
|
||||
currQuestionIndex !== -1 &&
|
||||
currQuestionIndex < questions.length - 1
|
||||
) {
|
||||
updateQuestionStatus('skip', currQuestion.id).finally(() => null);
|
||||
}
|
||||
}}
|
||||
onPrevClick={() => {
|
||||
if (currQuestionIndex > 0) {
|
||||
const prevQuestion = questions[currQuestionIndex - 1];
|
||||
// remove last question from the progress of the user
|
||||
const tempUserProgress = {
|
||||
know:
|
||||
userProgress?.know.filter((id) => id !== prevQuestion.id) || [],
|
||||
dontKnow:
|
||||
userProgress?.dontKnow.filter((id) => id !== prevQuestion.id) ||
|
||||
[],
|
||||
skip:
|
||||
userProgress?.skip.filter((id) => id !== prevQuestion.id) || [],
|
||||
};
|
||||
|
||||
setUserProgress(tempUserProgress);
|
||||
setCurrQuestionIndex(currQuestionIndex - 1);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
@ -228,12 +235,12 @@ export function QuestionsList(props: QuestionsListProps) {
|
||||
>
|
||||
{hasFinished && (
|
||||
<QuestionFinished
|
||||
totalCount={unshuffledQuestions?.length || questions?.length || 0}
|
||||
totalCount={questions?.length || 0}
|
||||
knowCount={knowCount}
|
||||
didNotKnowCount={dontKnowCount}
|
||||
skippedCount={skipCount}
|
||||
onReset={(type: QuestionProgressType | 'reset') => {
|
||||
resetProgress(type).finally(() => null);
|
||||
onReset={() => {
|
||||
resetProgress().finally(() => null);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
@ -1,4 +1,11 @@
|
||||
import { CheckCircle, RotateCcw, SkipForward, Sparkles } from 'lucide-react';
|
||||
import {
|
||||
CheckCircle,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
RotateCcw,
|
||||
SkipForward,
|
||||
Sparkles,
|
||||
} from 'lucide-react';
|
||||
import { showLoginPopup } from '../../lib/popup';
|
||||
|
||||
type QuestionsProgressProps = {
|
||||
@ -9,6 +16,8 @@ type QuestionsProgressProps = {
|
||||
totalCount?: number;
|
||||
skippedCount?: number;
|
||||
onResetClick?: () => void;
|
||||
onPrevClick?: () => void;
|
||||
onNextClick?: () => void;
|
||||
};
|
||||
|
||||
export function QuestionsProgress(props: QuestionsProgressProps) {
|
||||
@ -20,6 +29,8 @@ export function QuestionsProgress(props: QuestionsProgressProps) {
|
||||
totalCount = 0,
|
||||
skippedCount = 0,
|
||||
onResetClick = () => null,
|
||||
onPrevClick = () => null,
|
||||
onNextClick = () => null,
|
||||
} = props;
|
||||
|
||||
const totalSolved = knowCount + didNotKnowCount + skippedCount;
|
||||
@ -36,8 +47,22 @@ export function QuestionsProgress(props: QuestionsProgressProps) {
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<span className="ml-3 text-sm">
|
||||
{totalSolved} / {totalCount}
|
||||
<span className="ml-3 flex items-center text-sm">
|
||||
<button
|
||||
onClick={onPrevClick}
|
||||
className="text-zinc-400 hover:text-black"
|
||||
>
|
||||
<ChevronLeft className="h-4" strokeWidth={3} />
|
||||
</button>
|
||||
<span className="block min-w-[41px] text-center">
|
||||
<span className="tabular-nums">{totalSolved}</span> / {totalCount}
|
||||
</span>
|
||||
<button
|
||||
onClick={onNextClick}
|
||||
className="text-zinc-400 hover:text-black"
|
||||
>
|
||||
<ChevronRight className="h-4" strokeWidth={3} />
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -46,8 +71,7 @@ export function QuestionsProgress(props: QuestionsProgressProps) {
|
||||
<CheckCircle className="mr-1 h-4" />
|
||||
<span>Knew</span>
|
||||
<span className="ml-2 rounded-md bg-gray-200/80 px-1.5 font-medium text-black">
|
||||
<span className="tabular-nums">{knowCount}</span>{' '}
|
||||
Items
|
||||
<span className="tabular-nums">{knowCount}</span> Items
|
||||
</span>
|
||||
</span>
|
||||
|
||||
@ -55,8 +79,7 @@ export function QuestionsProgress(props: QuestionsProgressProps) {
|
||||
<Sparkles className="mr-1 h-4" />
|
||||
<span>Learnt</span>
|
||||
<span className="ml-2 rounded-md bg-gray-200/80 px-1.5 font-medium text-black">
|
||||
<span className="tabular-nums">{didNotKnowCount}</span>{' '}
|
||||
Items
|
||||
<span className="tabular-nums">{didNotKnowCount}</span> Items
|
||||
</span>
|
||||
</span>
|
||||
|
||||
@ -64,8 +87,7 @@ export function QuestionsProgress(props: QuestionsProgressProps) {
|
||||
<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">
|
||||
<span className="tabular-nums">{skippedCount}</span>{' '}
|
||||
Items
|
||||
<span className="tabular-nums">{skippedCount}</span> Items
|
||||
</span>
|
||||
</span>
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user