mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2025-08-19 07:31:24 +02:00
feat: question page confetti
This commit is contained in:
@@ -32,10 +32,12 @@
|
|||||||
"astro-compress": "^2.0.8",
|
"astro-compress": "^2.0.8",
|
||||||
"jose": "^4.14.4",
|
"jose": "^4.14.4",
|
||||||
"js-cookie": "^3.0.5",
|
"js-cookie": "^3.0.5",
|
||||||
|
"lucide-react": "^0.274.0",
|
||||||
"nanostores": "^0.9.2",
|
"nanostores": "^0.9.2",
|
||||||
"node-html-parser": "^6.1.5",
|
"node-html-parser": "^6.1.5",
|
||||||
"npm-check-updates": "^16.10.12",
|
"npm-check-updates": "^16.10.12",
|
||||||
"react": "^18.0.0",
|
"react": "^18.0.0",
|
||||||
|
"react-confetti": "^6.1.0",
|
||||||
"react-dom": "^18.0.0",
|
"react-dom": "^18.0.0",
|
||||||
"rehype-external-links": "^2.1.0",
|
"rehype-external-links": "^2.1.0",
|
||||||
"roadmap-renderer": "^1.0.6",
|
"roadmap-renderer": "^1.0.6",
|
||||||
|
40
pnpm-lock.yaml
generated
40
pnpm-lock.yaml
generated
@@ -38,6 +38,9 @@ dependencies:
|
|||||||
js-cookie:
|
js-cookie:
|
||||||
specifier: ^3.0.5
|
specifier: ^3.0.5
|
||||||
version: 3.0.5
|
version: 3.0.5
|
||||||
|
lucide-react:
|
||||||
|
specifier: ^0.274.0
|
||||||
|
version: 0.274.0(react@18.0.0)
|
||||||
nanostores:
|
nanostores:
|
||||||
specifier: ^0.9.2
|
specifier: ^0.9.2
|
||||||
version: 0.9.2
|
version: 0.9.2
|
||||||
@@ -50,6 +53,9 @@ dependencies:
|
|||||||
react:
|
react:
|
||||||
specifier: ^18.0.0
|
specifier: ^18.0.0
|
||||||
version: 18.0.0
|
version: 18.0.0
|
||||||
|
react-confetti:
|
||||||
|
specifier: ^6.1.0
|
||||||
|
version: 6.1.0(react@18.0.0)
|
||||||
react-dom:
|
react-dom:
|
||||||
specifier: ^18.0.0
|
specifier: ^18.0.0
|
||||||
version: 18.0.0(react@18.0.0)
|
version: 18.0.0(react@18.0.0)
|
||||||
@@ -1519,7 +1525,7 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
browserslist: 4.21.10
|
browserslist: 4.21.10
|
||||||
caniuse-lite: 1.0.30001525
|
caniuse-lite: 1.0.30001525
|
||||||
fraction.js: 4.3.4
|
fraction.js: 4.3.6
|
||||||
normalize-range: 0.1.2
|
normalize-range: 0.1.2
|
||||||
picocolors: 1.0.0
|
picocolors: 1.0.0
|
||||||
postcss: 8.4.29
|
postcss: 8.4.29
|
||||||
@@ -1611,7 +1617,7 @@ packages:
|
|||||||
hasBin: true
|
hasBin: true
|
||||||
dependencies:
|
dependencies:
|
||||||
caniuse-lite: 1.0.30001525
|
caniuse-lite: 1.0.30001525
|
||||||
electron-to-chromium: 1.4.507
|
electron-to-chromium: 1.4.508
|
||||||
node-releases: 2.0.13
|
node-releases: 2.0.13
|
||||||
update-browserslist-db: 1.0.11(browserslist@4.21.10)
|
update-browserslist-db: 1.0.11(browserslist@4.21.10)
|
||||||
dev: false
|
dev: false
|
||||||
@@ -2124,8 +2130,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
|
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/electron-to-chromium@1.4.507:
|
/electron-to-chromium@1.4.508:
|
||||||
resolution: {integrity: sha512-brvPFnO1lu3UYBpBht2qWw9qqhdG4htTjT90/9oOJmxQ77VvTxL9+ghErFqQzgj7n8268ONAmlebqjBR/S+qgA==}
|
resolution: {integrity: sha512-FFa8QKjQK/A5QuFr2167myhMesGrhlOBD+3cYNxO9/S4XzHEXesyTD/1/xF644gC8buFPz3ca6G1LOQD0tZrrg==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/email-addresses@5.0.0:
|
/email-addresses@5.0.0:
|
||||||
@@ -2426,8 +2432,8 @@ packages:
|
|||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/fraction.js@4.3.4:
|
/fraction.js@4.3.6:
|
||||||
resolution: {integrity: sha512-pwiTgt0Q7t+GHZA4yaLjObx4vXmmdcS0iSJ19o8d/goUGgItX9UZWKWNnLHehxviD8wU2IWRsnR8cD5+yOJP2Q==}
|
resolution: {integrity: sha512-n2aZ9tNfYDwaHhvFTkhFErqOMIb8uyzSQ+vGJBjZyanAKZVbGUQ1sngfk9FdkBw7G26O7AgNjLcecLffD1c7eg==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/fs-constants@1.0.0:
|
/fs-constants@1.0.0:
|
||||||
@@ -3239,6 +3245,14 @@ packages:
|
|||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/lucide-react@0.274.0(react@18.0.0):
|
||||||
|
resolution: {integrity: sha512-qiWcojRXEwDiSimMX1+arnxha+ROJzZjJaVvCC0rsG6a9pUPjZePXSq7em4ZKMp0NDm1hyzPNkM7UaWC3LU2AA==}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.5.1 || ^17.0.0 || ^18.0.0
|
||||||
|
dependencies:
|
||||||
|
react: 18.0.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/magic-string@0.30.3:
|
/magic-string@0.30.3:
|
||||||
resolution: {integrity: sha512-B7xGbll2fG/VjP+SWg4sX3JynwIU0mjoTc6MPpKNuIvftk6u6vqhDnk1R80b8C2GBR6ywqy+1DcKBrevBg+bmw==}
|
resolution: {integrity: sha512-B7xGbll2fG/VjP+SWg4sX3JynwIU0mjoTc6MPpKNuIvftk6u6vqhDnk1R80b8C2GBR6ywqy+1DcKBrevBg+bmw==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -4607,6 +4621,16 @@ packages:
|
|||||||
strip-json-comments: 2.0.1
|
strip-json-comments: 2.0.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/react-confetti@6.1.0(react@18.0.0):
|
||||||
|
resolution: {integrity: sha512-7Ypx4vz0+g8ECVxr88W9zhcQpbeujJAVqL14ZnXJ3I23mOI9/oBVTQ3dkJhUmB0D6XOtCZEM6N0Gm9PMngkORw==}
|
||||||
|
engines: {node: '>=10.18'}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.3.0 || ^17.0.1 || ^18.0.0
|
||||||
|
dependencies:
|
||||||
|
react: 18.0.0
|
||||||
|
tween-functions: 1.2.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/react-dom@18.0.0(react@18.0.0):
|
/react-dom@18.0.0(react@18.0.0):
|
||||||
resolution: {integrity: sha512-XqX7uzmFo0pUceWFCt7Gff6IyIMzFUn7QMZrbrQfGxtaxXZIcGQzoNpRLE3fQLnS4XzLLPMZX2T9TRcSrasicw==}
|
resolution: {integrity: sha512-XqX7uzmFo0pUceWFCt7Gff6IyIMzFUn7QMZrbrQfGxtaxXZIcGQzoNpRLE3fQLnS4XzLLPMZX2T9TRcSrasicw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -5443,6 +5467,10 @@ packages:
|
|||||||
safe-buffer: 5.2.1
|
safe-buffer: 5.2.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/tween-functions@1.2.0:
|
||||||
|
resolution: {integrity: sha512-PZBtLYcCLtEcjL14Fzb1gSxPBeL7nWvGhO5ZFPGqziCcr8uvHp0NDmdjBchp6KHL+tExcg0m3NISmKxhU394dA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/type-fest@0.13.1:
|
/type-fest@0.13.1:
|
||||||
resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==}
|
resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
83
src/components/Questions/QuestionsList.tsx
Normal file
83
src/components/Questions/QuestionsList.tsx
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import { QuestionsProgress } from './QuestionsProgress';
|
||||||
|
import { CheckCircle, SkipForward, Sparkles } from 'lucide-react';
|
||||||
|
import { useRef, useState } from 'react';
|
||||||
|
import ReactConfetti from 'react-confetti';
|
||||||
|
|
||||||
|
export function QuestionsList() {
|
||||||
|
const [confettiPos, setConfettiPos] = useState<
|
||||||
|
undefined | { x: number; y: number; w: number; h: number }
|
||||||
|
>(undefined);
|
||||||
|
|
||||||
|
const alreadyKnowRef = useRef<HTMLButtonElement>(null);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mb-40 gap-3 text-center">
|
||||||
|
<QuestionsProgress />
|
||||||
|
|
||||||
|
{confettiPos && (
|
||||||
|
<ReactConfetti
|
||||||
|
numberOfPieces={20}
|
||||||
|
recycle={false}
|
||||||
|
onConfettiComplete={() => {
|
||||||
|
setConfettiPos(undefined);
|
||||||
|
}}
|
||||||
|
initialVelocityX={2}
|
||||||
|
initialVelocityY={8}
|
||||||
|
tweenDuration={25}
|
||||||
|
confettiSource={{
|
||||||
|
x: confettiPos.x,
|
||||||
|
y: confettiPos.y,
|
||||||
|
w: confettiPos.w,
|
||||||
|
h: confettiPos.h,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="relative mb-4 h-[400px] w-full overflow-hidden rounded-lg border border-gray-300 bg-white">
|
||||||
|
<div className="flex h-full w-full items-center justify-center">
|
||||||
|
<p className="animate-pulse text-2xl text-black duration-100">
|
||||||
|
Please wait ..
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-3 sm:flex-row">
|
||||||
|
<button
|
||||||
|
ref={alreadyKnowRef}
|
||||||
|
onClick={(e) => {
|
||||||
|
const alreadyKnowRect =
|
||||||
|
alreadyKnowRef.current?.getBoundingClientRect();
|
||||||
|
const buttonX = alreadyKnowRect?.x || 0;
|
||||||
|
const buttonY = alreadyKnowRect?.y || 0;
|
||||||
|
|
||||||
|
// set confetti position, keeping in mind the scroll values
|
||||||
|
setConfettiPos({
|
||||||
|
x: buttonX,
|
||||||
|
y: buttonY + window.scrollY,
|
||||||
|
w: alreadyKnowRect?.width || 0,
|
||||||
|
h: alreadyKnowRect?.height || 0,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
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"
|
||||||
|
>
|
||||||
|
<CheckCircle className="mr-1 h-4 text-current" />
|
||||||
|
Already Know that
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
data-next-question="dont-know"
|
||||||
|
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"
|
||||||
|
>
|
||||||
|
<Sparkles className="mr-1 h-4 text-current" />
|
||||||
|
Didn't Know that
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
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"
|
||||||
|
>
|
||||||
|
<SkipForward className="mr-1 h-4" />
|
||||||
|
Skip Question
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
37
src/components/Questions/QuestionsProgress.tsx
Normal file
37
src/components/Questions/QuestionsProgress.tsx
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import {Check, CheckCircle, Lightbulb, PartyPopper, RotateCcw, Sparkles} from 'lucide-react';
|
||||||
|
|
||||||
|
export function QuestionsProgress() {
|
||||||
|
return (
|
||||||
|
<div className="mb-5 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>
|
||||||
|
<span className="ml-3 text-sm">5 / 100</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 className="ml-2 rounded-md bg-gray-200/80 px-1.5 font-medium text-black">
|
||||||
|
44 Questions
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span className="flex items-center">
|
||||||
|
<Sparkles className="mr-1 h-4" />
|
||||||
|
<span>Didn't Know</span>
|
||||||
|
<span className="ml-2 rounded-md bg-gray-200/80 px-1.5 font-medium text-black">
|
||||||
|
20 Questions
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<button className="flex items-center text-red-600 hover:text-red-900">
|
||||||
|
<RotateCcw className="mr-1 h-4" />
|
||||||
|
Reset Progress
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@@ -4,6 +4,7 @@ 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 AstroIcon from "../../components/AstroIcon.astro";
|
||||||
|
import { QuestionsList } from '../../components/Questions/QuestionsList';
|
||||||
|
|
||||||
export async function getStaticPaths() {
|
export async function getStaticPaths() {
|
||||||
return [
|
return [
|
||||||
@@ -34,74 +35,7 @@ export async function getStaticPaths() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class='gap-3 text-center mb-40'>
|
<QuestionsList client:load />
|
||||||
<!-- Progress bar -->
|
|
||||||
<div class='mb-5 rounded-lg border border-gray-300 p-6 bg-white'>
|
|
||||||
<div class='mb-3 flex items-center text-gray-600'>
|
|
||||||
<div class='relative w-full flex-1 rounded-xl bg-gray-200 p-1'>
|
|
||||||
<div
|
|
||||||
class='absolute bottom-0 left-0 top-0 w-[30%] rounded-xl bg-slate-800'
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<span class='ml-3 text-sm'>5 / 100</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class='relative -left-1 flex flex-col gap-2 text-sm text-black sm:flex-row sm:gap-3'
|
|
||||||
>
|
|
||||||
<span class='flex items-center'>
|
|
||||||
<AstroIcon icon='check-current' class='h-3 text-gray-800 mr-1.5' />
|
|
||||||
<span>Already knew</span>
|
|
||||||
<span class='bg-gray-200/80 px-1.5 rounded-md ml-2 text-black font-medium'>44 Questions</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span class='flex items-center'>
|
|
||||||
<AstroIcon icon='question' class='h-4 mr-1.5' />
|
|
||||||
<span>Didn't Know</span>
|
|
||||||
<span class='bg-gray-200/80 px-1.5 rounded-md ml-2 text-black font-medium'>20 Questions</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<button class='flex items-center text-red-600 hover:text-red-900'>
|
|
||||||
<AstroIcon icon='reset' class='h-3 sm:h-4' />
|
|
||||||
<span class='ml-0.5 sm:ml-1'>Reset Progress</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class='relative mb-4 h-[400px] w-full overflow-hidden rounded-lg bg-white border border-gray-300'>
|
|
||||||
<div
|
|
||||||
class='flex h-full w-full items-center justify-center'
|
|
||||||
>
|
|
||||||
<p class='animate-pulse text-2xl text-black duration-100'>
|
|
||||||
Please wait ..
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class='flex flex-col gap-3 sm:flex-row'>
|
|
||||||
<button
|
|
||||||
class='flex flex-1 items-center rounded-xl bg-white border border-gray-300 py-3 px-4 text-black transition-colors hover:bg-black hover:border-black hover:text-white'
|
|
||||||
>
|
|
||||||
<AstroIcon icon='check-current' class='mr-2 h-4 text-current' />
|
|
||||||
Already Know that
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
data-next-question='dont-know'
|
|
||||||
class='flex flex-1 items-center rounded-xl bg-white border border-gray-300 py-3 px-4 text-black transition-colors hover:bg-black hover:border-black hover:text-white'
|
|
||||||
>
|
|
||||||
<AstroIcon icon='bulb' class='mr-2 h-5 text-current' />
|
|
||||||
Didn't Know that
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
data-next-question='skip'
|
|
||||||
class='flex flex-1 items-center rounded-xl border border-red-600 p-3 text-red-600 hover:bg-red-600 hover:text-white'
|
|
||||||
>
|
|
||||||
<AstroIcon icon='skip' class='mr-2 h-4' />
|
|
||||||
Skip Question
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user