mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2025-03-15 12:49:43 +01:00
Change confetti to show on completion of quiz
This commit is contained in:
parent
437d879af3
commit
1ed54bad90
@ -9,12 +9,13 @@ type ConfettiPosition = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type ConfettiProps = {
|
type ConfettiProps = {
|
||||||
element: HTMLElement | null;
|
pieces?: number;
|
||||||
onDone: () => void;
|
element?: HTMLElement | null;
|
||||||
|
onDone?: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function Confetti(props: ConfettiProps) {
|
export function Confetti(props: ConfettiProps) {
|
||||||
const { element, onDone } = props;
|
const { element = document.body, onDone = () => null, pieces = 40 } = props;
|
||||||
|
|
||||||
const [confettiPos, setConfettiPos] = useState<
|
const [confettiPos, setConfettiPos] = useState<
|
||||||
undefined | ConfettiPosition
|
undefined | ConfettiPosition
|
||||||
@ -48,7 +49,7 @@ export function Confetti(props: ConfettiProps) {
|
|||||||
return (
|
return (
|
||||||
<ReactConfetti
|
<ReactConfetti
|
||||||
height={document.body.scrollHeight}
|
height={document.body.scrollHeight}
|
||||||
numberOfPieces={40}
|
numberOfPieces={pieces}
|
||||||
recycle={false}
|
recycle={false}
|
||||||
onConfettiComplete={(confettiInstance) => {
|
onConfettiComplete={(confettiInstance) => {
|
||||||
setConfettiPos(undefined);
|
setConfettiPos(undefined);
|
||||||
@ -56,7 +57,7 @@ export function Confetti(props: ConfettiProps) {
|
|||||||
}}
|
}}
|
||||||
initialVelocityX={4}
|
initialVelocityX={4}
|
||||||
initialVelocityY={8}
|
initialVelocityY={8}
|
||||||
tweenDuration={25}
|
tweenDuration={10}
|
||||||
confettiSource={{
|
confettiSource={{
|
||||||
x: confettiPos.x,
|
x: confettiPos.x,
|
||||||
y: confettiPos.y,
|
y: confettiPos.y,
|
||||||
|
@ -5,10 +5,10 @@ import { QuestionCard } from './QuestionCard';
|
|||||||
import { QuestionLoader } from './QuestionLoader';
|
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 { 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';
|
import { QuestionFinished } from './QuestionFinished';
|
||||||
|
import { Confetti } from '../Confetti';
|
||||||
|
|
||||||
type UserQuestionProgress = {
|
type UserQuestionProgress = {
|
||||||
know: string[];
|
know: string[];
|
||||||
@ -29,25 +29,12 @@ export function QuestionsList(props: QuestionsListProps) {
|
|||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [confettiEl, setConfettiEl] = useState<HTMLElement | null>(null);
|
const [showConfetti, setShowConfetti] = useState(false);
|
||||||
|
|
||||||
const [questions, setQuestions] = useState<QuestionType[]>();
|
const [questions, setQuestions] = useState<QuestionType[]>();
|
||||||
const [pendingQuestions, setPendingQuestions] = useState<QuestionType[]>([]);
|
const [pendingQuestions, setPendingQuestions] = useState<QuestionType[]>([]);
|
||||||
|
|
||||||
const [userProgress, setUserProgress] = useState<UserQuestionProgress>();
|
const [userProgress, setUserProgress] = useState<UserQuestionProgress>();
|
||||||
const alreadyKnowRef = useRef<HTMLButtonElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const didNotKnowRef = useRef<HTMLButtonElement>(null);
|
|
||||||
|
|
||||||
function showConfetti(el: HTMLElement | null) {
|
|
||||||
// If confetti is already showing, remove that first
|
|
||||||
if (confettiEl) {
|
|
||||||
setConfettiEl(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
window.setTimeout(() => {
|
|
||||||
setConfettiEl(el);
|
|
||||||
}, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchUserProgress(): Promise<
|
async function fetchUserProgress(): Promise<
|
||||||
UserQuestionProgress | undefined
|
UserQuestionProgress | undefined
|
||||||
@ -57,7 +44,9 @@ export function QuestionsList(props: QuestionsListProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { response, error } = await httpGet<UserQuestionProgress>(
|
const { response, error } = await httpGet<UserQuestionProgress>(
|
||||||
`/v1-get-user-question-progress/${groupId}`
|
`${
|
||||||
|
import.meta.env.PUBLIC_API_URL
|
||||||
|
}/v1-get-user-question-progress/${groupId}`
|
||||||
);
|
);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
@ -112,7 +101,9 @@ export function QuestionsList(props: QuestionsListProps) {
|
|||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
const { response, error } = await httpPut<UserQuestionProgress>(
|
const { response, error } = await httpPut<UserQuestionProgress>(
|
||||||
`/v1-reset-question-progress/${groupId}`,
|
`${
|
||||||
|
import.meta.env.PUBLIC_API_URL
|
||||||
|
}/v1-reset-question-progress/${groupId}`,
|
||||||
{
|
{
|
||||||
type,
|
type,
|
||||||
}
|
}
|
||||||
@ -163,7 +154,9 @@ export function QuestionsList(props: QuestionsListProps) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const { response, error } = await httpPut<UserQuestionProgress>(
|
const { response, error } = await httpPut<UserQuestionProgress>(
|
||||||
`/v1-update-question-status/${groupId}`,
|
`${
|
||||||
|
import.meta.env.PUBLIC_API_URL
|
||||||
|
}/v1-update-question-status/${groupId}`,
|
||||||
{
|
{
|
||||||
status,
|
status,
|
||||||
questionId,
|
questionId,
|
||||||
@ -179,9 +172,17 @@ export function QuestionsList(props: QuestionsListProps) {
|
|||||||
newProgress = response;
|
newProgress = response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const updatedQuestionList = pendingQuestions.filter(
|
||||||
|
(q) => q.id !== questionId
|
||||||
|
);
|
||||||
|
|
||||||
setUserProgress(newProgress);
|
setUserProgress(newProgress);
|
||||||
setPendingQuestions(pendingQuestions.filter((q) => q.id !== questionId));
|
setPendingQuestions(updatedQuestionList);
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
|
|
||||||
|
if (updatedQuestionList.length === 0) {
|
||||||
|
setShowConfetti(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -194,16 +195,10 @@ export function QuestionsList(props: QuestionsListProps) {
|
|||||||
const hasProgress = knowCount > 0 || dontKnowCount > 0 || skipCount > 0;
|
const hasProgress = knowCount > 0 || dontKnowCount > 0 || skipCount > 0;
|
||||||
|
|
||||||
const currQuestion = pendingQuestions[0];
|
const currQuestion = pendingQuestions[0];
|
||||||
|
const hasFinished = !isLoading && hasProgress && !currQuestion;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mb-40 gap-3 text-center">
|
<div className="mb-40 gap-3 text-center">
|
||||||
<Confetti
|
|
||||||
element={confettiEl}
|
|
||||||
onDone={() => {
|
|
||||||
setConfettiEl(null);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<QuestionsProgress
|
<QuestionsProgress
|
||||||
knowCount={knowCount}
|
knowCount={knowCount}
|
||||||
didNotKnowCount={dontKnowCount}
|
didNotKnowCount={dontKnowCount}
|
||||||
@ -216,8 +211,21 @@ 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">
|
{showConfetti && containerRef.current && (
|
||||||
{!isLoading && hasProgress && !currQuestion && (
|
<Confetti
|
||||||
|
pieces={100}
|
||||||
|
element={containerRef.current}
|
||||||
|
onDone={() => {
|
||||||
|
setShowConfetti(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div
|
||||||
|
ref={containerRef}
|
||||||
|
className="relative mb-4 flex min-h-[400px] w-full overflow-hidden rounded-lg border border-gray-300 bg-white"
|
||||||
|
>
|
||||||
|
{hasFinished && (
|
||||||
<QuestionFinished
|
<QuestionFinished
|
||||||
totalCount={unshuffledQuestions?.length || questions?.length || 0}
|
totalCount={unshuffledQuestions?.length || questions?.length || 0}
|
||||||
knowCount={knowCount}
|
knowCount={knowCount}
|
||||||
@ -232,12 +240,14 @@ export function QuestionsList(props: QuestionsListProps) {
|
|||||||
{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 ${
|
||||||
|
hasFinished ? 'invisible' : 'visible'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
disabled={isLoading || !currQuestion}
|
disabled={isLoading || !currQuestion}
|
||||||
ref={alreadyKnowRef}
|
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
showConfetti(alreadyKnowRef.current);
|
|
||||||
updateQuestionStatus('know', currQuestion.id).finally(() => null);
|
updateQuestionStatus('know', currQuestion.id).finally(() => null);
|
||||||
}}
|
}}
|
||||||
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"
|
||||||
@ -246,9 +256,7 @@ export function QuestionsList(props: QuestionsListProps) {
|
|||||||
Already Know that
|
Already Know that
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
ref={didNotKnowRef}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
showConfetti(didNotKnowRef.current);
|
|
||||||
updateQuestionStatus('dontKnow', currQuestion.id).finally(
|
updateQuestionStatus('dontKnow', currQuestion.id).finally(
|
||||||
() => null
|
() => null
|
||||||
);
|
);
|
||||||
|
@ -48,7 +48,7 @@ const { frontmatter } = questionGroup;
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<QuestionsList questions={questionGroup.questions} client:load />
|
<QuestionsList groupId={questionGroup.id} questions={questionGroup.questions} client:load />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user