diff --git a/app/dashboard/leaderboard/page.tsx b/app/dashboard/leaderboard/page.tsx index 6e83730..ba22a15 100644 --- a/app/dashboard/leaderboard/page.tsx +++ b/app/dashboard/leaderboard/page.tsx @@ -1,4 +1,5 @@ import Leaderboard from '@/ui/Leaderboard'; +import { cookies } from 'next/headers'; export const metadata = { title: 'Tableau des scores - Peer-at Code', @@ -6,5 +7,6 @@ export const metadata = { }; export default async function Page() { - return ; + const token = cookies().get('token')?.value; + return ; } diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx index 29bd481..5d39a7a 100644 --- a/app/dashboard/page.tsx +++ b/app/dashboard/page.tsx @@ -1,10 +1,12 @@ -import Card from '@/ui/Card'; +'use client'; -export const metadata = { - title: 'Dashboard - Peer-at Code' -}; +import { useMe } from '@/lib/hooks/use-players'; +import Card from '@/ui/Card'; +import cookies from 'js-cookie'; export default function Page() { + const token = cookies.get('token'); + const { data: me, isLoading } = useMe({ token: token! }); return (
@@ -14,9 +16,24 @@ export default function Page() {

Ceci est la page d'accueil du dashboard

- - - + + +
diff --git a/app/dashboard/puzzles/[id]/page.tsx b/app/dashboard/puzzles/[id]/page.tsx index 68664a6..07709ab 100644 --- a/app/dashboard/puzzles/[id]/page.tsx +++ b/app/dashboard/puzzles/[id]/page.tsx @@ -1,3 +1,4 @@ +import { cookies } from 'next/headers'; import { getPuzzle } from '@/lib/puzzles'; import Puzzle from '@/ui/Puzzle'; import type { Metadata } from 'next'; @@ -5,16 +6,26 @@ import { notFound } from 'next/navigation'; export async function generateMetadata({ params }: { params: { id: number } }): Promise { const { id } = params; + const token = cookies().get('token')?.value; - const puzzle = await getPuzzle(id); + if (!token) { + notFound(); + } + + const puzzle = await getPuzzle({ token, id }); return { title: `${puzzle.name} - Peer-at Code` }; } export default async function Page({ params }: { params: { id: number } }) { const { id } = params; + const token = cookies().get('token')?.value; - const puzzle = await getPuzzle(id); + if (!token) { + notFound(); + } + + const puzzle = await getPuzzle({ token, id }); if (!puzzle) { notFound(); diff --git a/app/dashboard/puzzles/page.tsx b/app/dashboard/puzzles/page.tsx index 29f9ba8..29aea34 100644 --- a/app/dashboard/puzzles/page.tsx +++ b/app/dashboard/puzzles/page.tsx @@ -1,3 +1,5 @@ +import { cookies } from 'next/headers'; + import Puzzles from '@/ui/Puzzles'; export const metadata = { @@ -5,9 +7,12 @@ export const metadata = { }; export default async function Page() { + const cookieStore = cookies(); + const token = cookieStore.get('token')?.value; + return (
- +
); } diff --git a/app/page.tsx b/app/page.tsx index 342a155..e67bdaf 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -3,7 +3,6 @@ import Console from '@/ui/Console'; import Image from 'next/image'; export default function Page() { - // TODO: Fix this (image) return (
diff --git a/lib/fetcher.ts b/lib/fetcher.ts new file mode 100644 index 0000000..1803a9c --- /dev/null +++ b/lib/fetcher.ts @@ -0,0 +1,12 @@ +import axios from 'axios'; + +const fetcher = axios.create({ + baseURL: process.env.NEXT_PUBLIC_API_URL, + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json' + }, + insecureHTTPParser: true +}); + +export default fetcher; diff --git a/lib/hooks/use-leaderboard.ts b/lib/hooks/use-leaderboard.ts index fb11515..b2c684f 100644 --- a/lib/hooks/use-leaderboard.ts +++ b/lib/hooks/use-leaderboard.ts @@ -1,6 +1,6 @@ import useSWR from 'swr'; import { getScores } from '../leaderboard'; -export function useLeaderboard() { - return useSWR('leaderboard', () => getScores()); +export function useLeaderboard({ token }: { token: string }) { + return useSWR('leaderboard', () => getScores({ token })); } diff --git a/lib/hooks/use-players.ts b/lib/hooks/use-players.ts new file mode 100644 index 0000000..22bcd73 --- /dev/null +++ b/lib/hooks/use-players.ts @@ -0,0 +1,12 @@ +import useSWR from 'swr'; +import { getPlayer } from '../players'; + +export function useMe({ token }: { token: string }) { + return useSWR('me', () => getPlayer({ token }), { + revalidateOnReconnect: false + }); +} + +export function usePlayer({ token, username }: { token: string; username: string }) { + return useSWR(`players/${username}`, () => getPlayer({ token, username })); +} diff --git a/lib/hooks/use-puzzles.ts b/lib/hooks/use-puzzles.ts index 88a77e0..9a4ab81 100644 --- a/lib/hooks/use-puzzles.ts +++ b/lib/hooks/use-puzzles.ts @@ -2,14 +2,14 @@ import useSWR from 'swr'; import { getChapters, getPuzzle, getPuzzles } from '../puzzles'; -export function useChapters() { - return useSWR('chapters', () => getChapters()); +export function useChapters({ token }: { token: string }) { + return useSWR('chapters', () => getChapters({ token })); } -export function usePuzzles() { - return useSWR('puzzles', () => getPuzzles()); +export function usePuzzles({ token }: { token: string }) { + return useSWR('puzzles', () => getPuzzles({ token })); } -export function usePuzzle(id: number) { - return useSWR(`puzzles/${id}`, () => getPuzzle(id)); +export function usePuzzle({ token, id }: { token: string; id: number }) { + return useSWR(`puzzles/${id}`, () => getPuzzle({ token, id })); } diff --git a/lib/leaderboard.ts b/lib/leaderboard.ts index 0f3d387..54d79f7 100644 --- a/lib/leaderboard.ts +++ b/lib/leaderboard.ts @@ -1,8 +1,10 @@ -import axios from 'axios'; +import fetcher from './fetcher'; -export const getScores = async (): Promise => { - const { data, status } = await axios.get(`${process.env.NEXT_PUBLIC_API_URL}/leaderboard`, { - insecureHTTPParser: true +export const getScores = async ({ token }: { token: string }): Promise => { + const { data, status } = await fetcher.get(`/leaderboard`, { + headers: { + Authorization: `Bearer ${token}` + } }); const scores = data; @@ -24,4 +26,5 @@ export type Score = { completions: number; pseudo: string; group: string; + avatar: string; }; diff --git a/lib/players.ts b/lib/players.ts new file mode 100644 index 0000000..afdc3be --- /dev/null +++ b/lib/players.ts @@ -0,0 +1,41 @@ +import fetcher from './fetcher'; + +export const getPlayer = async ({ + token, + username = '' +}: { + token: string; + username?: string; +}): Promise => { + const { data, status } = await fetcher.get(`/player/${username}`, { + headers: { + Authorization: `Bearer ${token}` + } + }); + + const player = data; + + if (status !== 200) { + throw new Error('Failed to fetch player'); + } + + if (!player) { + return null; + } + + return player as Player; +}; + +export type Player = { + email: string; + firstnames: string; + lastname: string; + description: string; + avatar: string; + group: string; + score: number; + tries: number; + completions: number; + pseudo: string; + badges: any[]; +}; diff --git a/lib/puzzles.ts b/lib/puzzles.ts index 5b374d7..1187721 100644 --- a/lib/puzzles.ts +++ b/lib/puzzles.ts @@ -1,8 +1,10 @@ -import axios from 'axios'; +import fetcher from './fetcher'; -export const getChapters = async (): Promise => { - const { data, status } = await axios.get(`${process.env.NEXT_PUBLIC_API_URL}/chapters`, { - insecureHTTPParser: true +export const getChapters = async ({ token }: { token: string }): Promise => { + const { data, status } = await fetcher.get(`/chapters`, { + headers: { + Authorization: `Bearer ${token}` + } }); let chapters = data; @@ -20,36 +22,44 @@ export const getChapters = async (): Promise => { return chapters as Chapter[]; }; -export const getPuzzlesByChapter = async (chapitre: number): Promise => { - const { data, status } = await axios.get( - `${process.env.NEXT_PUBLIC_API_URL}/chapter/${chapitre}`, - { insecureHTTPParser: true } - ); +export const getChapter = async ({ + token, + id +}: { + token: string; + id: number; +}): Promise => { + const { data, status } = await fetcher.get(`/chapter/${id}`, { + headers: { + Authorization: `Bearer ${token}` + } + }); - const { puzzles, name, id } = data; + const chapter = data; if (status !== 200) { throw new Error('Failed to fetch puzzles'); } - if (!puzzles) { + if (!chapter) { return null; } - return { - name, - id, - puzzles - }; + return chapter as Chapter; }; -export const getPuzzles = async (): Promise<{ chapters: Chapter[]; puzzles: Puzzle[] }> => { - const chapters = await getChapters(); - let puzzles: Puzzle[] = []; +export const getPuzzles = async ({ + token +}: { + token: string; +}): Promise<{ chapters: Chapter[]; puzzles: Puzzle[] }> => { + const chapters = await getChapters({ token }); + const puzzles: Puzzle[] = []; for (const chapter of chapters) { - const puzzlesByChapter = await getPuzzlesByChapter(chapter.id); - puzzles = [...puzzles, ...puzzlesByChapter!.puzzles]; + const puzzlesByChapter = await getChapter({ token, id: chapter.id }); + if (!puzzlesByChapter?.puzzles) continue; + puzzles.push(...puzzlesByChapter!.puzzles); } return { @@ -58,9 +68,11 @@ export const getPuzzles = async (): Promise<{ chapters: Chapter[]; puzzles: Puzz }; }; -export const getPuzzle = async (id: number): Promise => { - const { data, status } = await axios.get(`${process.env.NEXT_PUBLIC_API_URL}/puzzle/${id}`, { - insecureHTTPParser: true +export const getPuzzle = async ({ token, id }: { token: string; id: number }): Promise => { + const { data, status } = await fetcher.get(`/puzzle/${id}`, { + headers: { + Authorization: `Bearer ${token}` + } }); const puzzle = data; @@ -77,8 +89,8 @@ export const getPuzzle = async (id: number): Promise => { }; export type Puzzle = { - name: string; id: number; + name: string; content: string; }; diff --git a/package.json b/package.json index 595d716..2b806c1 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ }, "homepage": "https://github.com/Peer-at-Code/peer-at-code#readme", "dependencies": { + "@radix-ui/react-popover": "^1.0.5", "axios": "^1.3.4", "boring-avatars": "^1.7.0", "clsx": "^1.2.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d623388..94a4003 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,6 +1,7 @@ lockfileVersion: 5.4 specifiers: + '@radix-ui/react-popover': ^1.0.5 '@tailwindcss/forms': ^0.5.3 '@types/js-cookie': ^3.0.3 '@types/node': 18.11.18 @@ -33,6 +34,7 @@ specifiers: zod: ^3.20.2 dependencies: + '@radix-ui/react-popover': 1.0.5_5ndqzdd6t4rivxsukjv3i3ak2q axios: 1.3.4 boring-avatars: 1.7.0 clsx: 1.2.1 @@ -73,7 +75,6 @@ packages: engines: {node: '>=6.9.0'} dependencies: regenerator-runtime: 0.13.11 - dev: true /@eslint/eslintrc/1.4.1: resolution: {integrity: sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==} @@ -92,6 +93,30 @@ packages: - supports-color dev: true + /@floating-ui/core/0.7.3: + resolution: {integrity: sha512-buc8BXHmG9l82+OQXOFU3Kr2XQx9ys01U/Q9HMIrZ300iLc8HLMgh7dcCqgYzAzf4BkoQvDcXf5Y+CuEZ5JBYg==} + dev: false + + /@floating-ui/dom/0.5.4: + resolution: {integrity: sha512-419BMceRLq0RrmTSDxn8hf9R3VCJv2K9PUfugh5JyEFmdjzDo+e8U5EdR8nzKq8Yj1htzLm3b6eQEEam3/rrtg==} + dependencies: + '@floating-ui/core': 0.7.3 + dev: false + + /@floating-ui/react-dom/0.7.2_5ndqzdd6t4rivxsukjv3i3ak2q: + resolution: {integrity: sha512-1T0sJcpHgX/u4I1OzIEhlcrvkUN8ln39nz7fMoE/2HDHrPiMFoOGR7++GYyfUmIQHkkrTinaeQsO3XWubjSvGg==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + dependencies: + '@floating-ui/dom': 0.5.4 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + use-isomorphic-layout-effect: 1.1.2_3stiutgnnbnfnf3uowm5cip22i + transitivePeerDependencies: + - '@types/react' + dev: false + /@humanwhocodes/config-array/0.11.8: resolution: {integrity: sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==} engines: {node: '>=10.10.0'} @@ -272,6 +297,253 @@ packages: tslib: 2.5.0 dev: true + /@radix-ui/primitive/1.0.0: + resolution: {integrity: sha512-3e7rn8FDMin4CgeL7Z/49smCA3rFYY3Ha2rUQ7HRWFadS5iCRw08ZgVT1LaNTCNqgvrUiyczLflrVrF0SRQtNA==} + dependencies: + '@babel/runtime': 7.21.0 + dev: false + + /@radix-ui/react-arrow/1.0.2_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-fqYwhhI9IarZ0ll2cUSfKuXHlJK0qE4AfnRrPBbRwEH/4mGQn04/QFGomLi8TXWIdv9WJk//KgGm+aDxVIr1wA==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.21.0 + '@radix-ui/react-primitive': 1.0.2_biqbaboplfbrettd7655fr4n2y + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + dev: false + + /@radix-ui/react-compose-refs/1.0.0_react@18.2.0: + resolution: {integrity: sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.21.0 + react: 18.2.0 + dev: false + + /@radix-ui/react-context/1.0.0_react@18.2.0: + resolution: {integrity: sha512-1pVM9RfOQ+n/N5PJK33kRSKsr1glNxomxONs5c49MliinBY6Yw2Q995qfBUUo0/Mbg05B/sGA0gkgPI7kmSHBg==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.21.0 + react: 18.2.0 + dev: false + + /@radix-ui/react-dismissable-layer/1.0.3_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-nXZOvFjOuHS1ovumntGV7NNoLaEp9JEvTht3MBjP44NSW5hUKj/8OnfN3+8WmB+CEhN44XaGhpHoSsUIEl5P7Q==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.21.0 + '@radix-ui/primitive': 1.0.0 + '@radix-ui/react-compose-refs': 1.0.0_react@18.2.0 + '@radix-ui/react-primitive': 1.0.2_biqbaboplfbrettd7655fr4n2y + '@radix-ui/react-use-callback-ref': 1.0.0_react@18.2.0 + '@radix-ui/react-use-escape-keydown': 1.0.2_react@18.2.0 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + dev: false + + /@radix-ui/react-focus-guards/1.0.0_react@18.2.0: + resolution: {integrity: sha512-UagjDk4ijOAnGu4WMUPj9ahi7/zJJqNZ9ZAiGPp7waUWJO0O1aWXi/udPphI0IUjvrhBsZJGSN66dR2dsueLWQ==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.21.0 + react: 18.2.0 + dev: false + + /@radix-ui/react-focus-scope/1.0.2_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-spwXlNTfeIprt+kaEWE/qYuYT3ZAqJiAGjN/JgdvgVDTu8yc+HuX+WOWXrKliKnLnwck0F6JDkqIERncnih+4A==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.21.0 + '@radix-ui/react-compose-refs': 1.0.0_react@18.2.0 + '@radix-ui/react-primitive': 1.0.2_biqbaboplfbrettd7655fr4n2y + '@radix-ui/react-use-callback-ref': 1.0.0_react@18.2.0 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + dev: false + + /@radix-ui/react-id/1.0.0_react@18.2.0: + resolution: {integrity: sha512-Q6iAB/U7Tq3NTolBBQbHTgclPmGWE3OlktGGqrClPozSw4vkQ1DfQAOtzgRPecKsMdJINE05iaoDUG8tRzCBjw==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.21.0 + '@radix-ui/react-use-layout-effect': 1.0.0_react@18.2.0 + react: 18.2.0 + dev: false + + /@radix-ui/react-popover/1.0.5_5ndqzdd6t4rivxsukjv3i3ak2q: + resolution: {integrity: sha512-GRHZ8yD12MrN2NLobHPE8Rb5uHTxd9x372DE9PPNnBjpczAQHcZ5ne0KXG4xpf+RDdXSzdLv9ym6mYJCDTaUZg==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.21.0 + '@radix-ui/primitive': 1.0.0 + '@radix-ui/react-compose-refs': 1.0.0_react@18.2.0 + '@radix-ui/react-context': 1.0.0_react@18.2.0 + '@radix-ui/react-dismissable-layer': 1.0.3_biqbaboplfbrettd7655fr4n2y + '@radix-ui/react-focus-guards': 1.0.0_react@18.2.0 + '@radix-ui/react-focus-scope': 1.0.2_biqbaboplfbrettd7655fr4n2y + '@radix-ui/react-id': 1.0.0_react@18.2.0 + '@radix-ui/react-popper': 1.1.1_5ndqzdd6t4rivxsukjv3i3ak2q + '@radix-ui/react-portal': 1.0.2_biqbaboplfbrettd7655fr4n2y + '@radix-ui/react-presence': 1.0.0_biqbaboplfbrettd7655fr4n2y + '@radix-ui/react-primitive': 1.0.2_biqbaboplfbrettd7655fr4n2y + '@radix-ui/react-slot': 1.0.1_react@18.2.0 + '@radix-ui/react-use-controllable-state': 1.0.0_react@18.2.0 + aria-hidden: 1.2.3 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + react-remove-scroll: 2.5.5_3stiutgnnbnfnf3uowm5cip22i + transitivePeerDependencies: + - '@types/react' + dev: false + + /@radix-ui/react-popper/1.1.1_5ndqzdd6t4rivxsukjv3i3ak2q: + resolution: {integrity: sha512-keYDcdMPNMjSC8zTsZ8wezUMiWM9Yj14wtF3s0PTIs9srnEPC9Kt2Gny1T3T81mmSeyDjZxsD9N5WCwNNb712w==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.21.0 + '@floating-ui/react-dom': 0.7.2_5ndqzdd6t4rivxsukjv3i3ak2q + '@radix-ui/react-arrow': 1.0.2_biqbaboplfbrettd7655fr4n2y + '@radix-ui/react-compose-refs': 1.0.0_react@18.2.0 + '@radix-ui/react-context': 1.0.0_react@18.2.0 + '@radix-ui/react-primitive': 1.0.2_biqbaboplfbrettd7655fr4n2y + '@radix-ui/react-use-callback-ref': 1.0.0_react@18.2.0 + '@radix-ui/react-use-layout-effect': 1.0.0_react@18.2.0 + '@radix-ui/react-use-rect': 1.0.0_react@18.2.0 + '@radix-ui/react-use-size': 1.0.0_react@18.2.0 + '@radix-ui/rect': 1.0.0 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + transitivePeerDependencies: + - '@types/react' + dev: false + + /@radix-ui/react-portal/1.0.2_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-swu32idoCW7KA2VEiUZGBSu9nB6qwGdV6k6HYhUoOo3M1FFpD+VgLzUqtt3mwL1ssz7r2x8MggpLSQach2Xy/Q==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.21.0 + '@radix-ui/react-primitive': 1.0.2_biqbaboplfbrettd7655fr4n2y + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + dev: false + + /@radix-ui/react-presence/1.0.0_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-A+6XEvN01NfVWiKu38ybawfHsBjWum42MRPnEuqPsBZ4eV7e/7K321B5VgYMPv3Xx5An6o1/l9ZuDBgmcmWK3w==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.21.0 + '@radix-ui/react-compose-refs': 1.0.0_react@18.2.0 + '@radix-ui/react-use-layout-effect': 1.0.0_react@18.2.0 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + dev: false + + /@radix-ui/react-primitive/1.0.2_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-zY6G5Qq4R8diFPNwtyoLRZBxzu1Z+SXMlfYpChN7Dv8gvmx9X3qhDqiLWvKseKVJMuedFeU/Sa0Sy/Ia+t06Dw==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.21.0 + '@radix-ui/react-slot': 1.0.1_react@18.2.0 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + dev: false + + /@radix-ui/react-slot/1.0.1_react@18.2.0: + resolution: {integrity: sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.21.0 + '@radix-ui/react-compose-refs': 1.0.0_react@18.2.0 + react: 18.2.0 + dev: false + + /@radix-ui/react-use-callback-ref/1.0.0_react@18.2.0: + resolution: {integrity: sha512-GZtyzoHz95Rhs6S63D2t/eqvdFCm7I+yHMLVQheKM7nBD8mbZIt+ct1jz4536MDnaOGKIxynJ8eHTkVGVVkoTg==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.21.0 + react: 18.2.0 + dev: false + + /@radix-ui/react-use-controllable-state/1.0.0_react@18.2.0: + resolution: {integrity: sha512-FohDoZvk3mEXh9AWAVyRTYR4Sq7/gavuofglmiXB2g1aKyboUD4YtgWxKj8O5n+Uak52gXQ4wKz5IFST4vtJHg==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.21.0 + '@radix-ui/react-use-callback-ref': 1.0.0_react@18.2.0 + react: 18.2.0 + dev: false + + /@radix-ui/react-use-escape-keydown/1.0.2_react@18.2.0: + resolution: {integrity: sha512-DXGim3x74WgUv+iMNCF+cAo8xUHHeqvjx8zs7trKf+FkQKPQXLk2sX7Gx1ysH7Q76xCpZuxIJE7HLPxRE+Q+GA==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.21.0 + '@radix-ui/react-use-callback-ref': 1.0.0_react@18.2.0 + react: 18.2.0 + dev: false + + /@radix-ui/react-use-layout-effect/1.0.0_react@18.2.0: + resolution: {integrity: sha512-6Tpkq+R6LOlmQb1R5NNETLG0B4YP0wc+klfXafpUCj6JGyaUc8il7/kUZ7m59rGbXGczE9Bs+iz2qloqsZBduQ==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.21.0 + react: 18.2.0 + dev: false + + /@radix-ui/react-use-rect/1.0.0_react@18.2.0: + resolution: {integrity: sha512-TB7pID8NRMEHxb/qQJpvSt3hQU4sqNPM1VCTjTRjEOa7cEop/QMuq8S6fb/5Tsz64kqSvB9WnwsDHtjnrM9qew==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.21.0 + '@radix-ui/rect': 1.0.0 + react: 18.2.0 + dev: false + + /@radix-ui/react-use-size/1.0.0_react@18.2.0: + resolution: {integrity: sha512-imZ3aYcoYCKhhgNpkNDh/aTiU05qw9hX+HHI1QDBTyIlcFjgeFlKKySNGMwTp7nYFLQg/j0VA2FmCY4WPDDHMg==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.21.0 + '@radix-ui/react-use-layout-effect': 1.0.0_react@18.2.0 + react: 18.2.0 + dev: false + + /@radix-ui/rect/1.0.0: + resolution: {integrity: sha512-d0O68AYy/9oeEy1DdC07bz1/ZXX+DqCskRd3i4JzLSTXwefzaepQrKjXC7aNM8lTHjFLDO0pDgaEiQ7jEk+HVg==} + dependencies: + '@babel/runtime': 7.21.0 + dev: false + /@rushstack/eslint-patch/1.2.0: resolution: {integrity: sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==} dev: true @@ -556,6 +828,13 @@ packages: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} dev: true + /aria-hidden/1.2.3: + resolution: {integrity: sha512-xcLxITLe2HYa1cnYnwCjkOO1PqUHQpozB8x9AR0OgWN2woOBi5kSDVxKfd0b7sb1hw5qFeJhXm9H1nu3xSfLeQ==} + engines: {node: '>=10'} + dependencies: + tslib: 2.5.0 + dev: false + /aria-query/5.1.3: resolution: {integrity: sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==} dependencies: @@ -885,6 +1164,10 @@ packages: engines: {node: '>=6'} dev: false + /detect-node-es/1.1.0: + resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + dev: false + /detective/5.2.1: resolution: {integrity: sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==} engines: {node: '>=0.8.0'} @@ -1494,6 +1777,11 @@ packages: has-symbols: 1.0.3 dev: true + /get-nonce/1.0.1: + resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} + engines: {node: '>=6'} + dev: false + /get-symbol-description/1.0.0: resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} engines: {node: '>= 0.4'} @@ -1686,6 +1974,12 @@ packages: side-channel: 1.0.4 dev: true + /invariant/2.2.4: + resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} + dependencies: + loose-envify: 1.4.0 + dev: false + /is-arguments/1.1.1: resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} engines: {node: '>= 0.4'} @@ -2701,6 +2995,58 @@ packages: - supports-color dev: false + /react-remove-scroll-bar/2.3.4_3stiutgnnbnfnf3uowm5cip22i: + resolution: {integrity: sha512-63C4YQBUt0m6ALadE9XV56hV8BgJWDmmTPY758iIJjfQKt2nYwoUrPk0LXRXcB/yIj82T1/Ixfdpdk68LwIB0A==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.0.27 + react: 18.2.0 + react-style-singleton: 2.2.1_3stiutgnnbnfnf3uowm5cip22i + tslib: 2.5.0 + dev: false + + /react-remove-scroll/2.5.5_3stiutgnnbnfnf3uowm5cip22i: + resolution: {integrity: sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.0.27 + react: 18.2.0 + react-remove-scroll-bar: 2.3.4_3stiutgnnbnfnf3uowm5cip22i + react-style-singleton: 2.2.1_3stiutgnnbnfnf3uowm5cip22i + tslib: 2.5.0 + use-callback-ref: 1.3.0_3stiutgnnbnfnf3uowm5cip22i + use-sidecar: 1.1.2_3stiutgnnbnfnf3uowm5cip22i + dev: false + + /react-style-singleton/2.2.1_3stiutgnnbnfnf3uowm5cip22i: + resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.0.27 + get-nonce: 1.0.1 + invariant: 2.2.4 + react: 18.2.0 + tslib: 2.5.0 + dev: false + /react/18.2.0: resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} engines: {node: '>=0.10.0'} @@ -2723,7 +3069,6 @@ packages: /regenerator-runtime/0.13.11: resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} - dev: true /regexp.prototype.flags/1.4.3: resolution: {integrity: sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==} @@ -3175,6 +3520,50 @@ packages: punycode: 2.3.0 dev: true + /use-callback-ref/1.3.0_3stiutgnnbnfnf3uowm5cip22i: + resolution: {integrity: sha512-3FT9PRuRdbB9HfXhEq35u4oZkvpJ5kuYbpqhCfmiZyReuRgpnhDlbr2ZEnnuS0RrJAPn6l23xjFg9kpDM+Ms7w==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.0.27 + react: 18.2.0 + tslib: 2.5.0 + dev: false + + /use-isomorphic-layout-effect/1.1.2_3stiutgnnbnfnf3uowm5cip22i: + resolution: {integrity: sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.0.27 + react: 18.2.0 + dev: false + + /use-sidecar/1.1.2_3stiutgnnbnfnf3uowm5cip22i: + resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.9.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.0.27 + detect-node-es: 1.1.0 + react: 18.2.0 + tslib: 2.5.0 + dev: false + /use-sync-external-store/1.2.0_react@18.2.0: resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==} peerDependencies: diff --git a/styles/globals.css b/styles/globals.css index fe01a95..5cc833f 100644 --- a/styles/globals.css +++ b/styles/globals.css @@ -6,4 +6,16 @@ .console { @apply relative top-0.5 inline-block; } + input:-webkit-autofill, + input:-webkit-autofill:hover, + input:-webkit-autofill:focus, + textarea:-webkit-autofill, + textarea:-webkit-autofill:hover, + textarea:-webkit-autofill:focus, + select:-webkit-autofill, + select:-webkit-autofill:hover, + select:-webkit-autofill:focus { + -webkit-box-shadow: 0 0 0px 1000px hsl(258deg 15% 17%) inset; + transition: background-color 5000s ease-in-out 0s; + } } diff --git a/ui/Avatar.tsx b/ui/Avatar.tsx index 6589c0b..2df4c63 100644 --- a/ui/Avatar.tsx +++ b/ui/Avatar.tsx @@ -1,5 +1,46 @@ import BoringAvatar from 'boring-avatars'; +import Image from 'next/image'; -export default function Avatar({ name, size = 28 }: { name: string; size?: number }) { +import { cn } from '@/lib/utils'; + +export function Avatar({ name, size = 36 }: { name: string; size?: number }) { return ; } + +export function Base64Avatar({ + name, + src, + className +}: { + name: string; + src: string; + className?: string; +}) { + return ( + {name} + ); +} + +export default function AvatarComponent({ + name, + src, + size = 36, + className +}: { + name: string; + src: string; + size?: number; + className?: string; +}) { + return src ? ( + + ) : ( + + ); +} diff --git a/ui/Badge.tsx b/ui/Badge.tsx index a50ee3e..943afc5 100644 --- a/ui/Badge.tsx +++ b/ui/Badge.tsx @@ -4,6 +4,12 @@ import { cn } from '@/lib/utils'; export type Difficulty = 'easy' | 'medium' | 'hard'; +export const DIFFICULTY = { + 1: 'easy', + 2: 'medium', + 3: 'hard' +} + export default function Badge({ title, path, diff --git a/ui/Button.tsx b/ui/Button.tsx index 270ce0f..8e003ea 100644 --- a/ui/Button.tsx +++ b/ui/Button.tsx @@ -10,11 +10,12 @@ const Button = forwardRef<
+ ); + return (
-

{title}

-

{data}

+

{data}

+

{title}

); diff --git a/ui/Input.tsx b/ui/Input.tsx index dd4f0d5..cdede32 100644 --- a/ui/Input.tsx +++ b/ui/Input.tsx @@ -1,5 +1,6 @@ import { forwardRef } from 'react'; import ErrorMessage from './ErrorMessage'; +import Icon from './Icon'; import Label from './Label'; const Input = forwardRef< @@ -14,7 +15,8 @@ const Input = forwardRef< diff --git a/ui/Leaderboard.tsx b/ui/Leaderboard.tsx index 504f4a1..23ab4e0 100644 --- a/ui/Leaderboard.tsx +++ b/ui/Leaderboard.tsx @@ -2,78 +2,102 @@ import { useLeaderboard } from '@/lib/hooks/use-leaderboard'; import { cn } from '@/lib/utils'; -import Avatar from './Avatar'; +import { useMemo, useState } from 'react'; +import AvatarComponent from './Avatar'; import Select from './Select'; -// TODO: Generate this later const scoreColors = ['text-yellow-400', 'text-gray-400', 'text-orange-400']; -// TODO: Generate this later -export const options = [ - { value: '1i1', title: '1I1' }, - { value: '1i2', title: '1I2' }, - { value: '1i3', title: '1I3' }, - { value: '1i4', title: '1I4' }, - { value: '1i5', title: '1I5' }, - { value: '1i6', title: '1I6' }, - { value: '1i7', title: '1I7' }, - { value: '1i8', title: '1I8' } -]; +export default function Leaderboard({ token }: { token: string }) { + const { data, isLoading } = useLeaderboard({ token }); + + const [filter, setFilter] = useState(''); + + let options; + + if (data) { + options = data + .filter((score, index, self) => { + return index === self.findIndex((t) => t.group === score.group) && score.group !== ''; + }) + .sort((a, b) => (a.group > b.group ? 1 : -1)) + .map((score) => ({ value: score.group, title: score.group })); + options.unshift({ value: '', title: 'Tous' }); + options.push({ value: 'no-group', title: 'Sans groupe' }); + } + + const filteredData = useMemo(() => { + if (filter) { + if (filter === 'no-group') { + return data?.filter((score) => score.group === ''); + } + return data?.filter((score) => score.group === filter); + } + return data; + }, [data, filter]); -export default function Leaderboard() { - const { data, isLoading } = useLeaderboard(); return ( -
-
-
-
-
-

Tableau des scores

-

- Suivez la progression des élèves en direct -

-
- setFilter(event.target.value)} + /> + )) || ( + + )} +
+
+
    + {(!isLoading && + filteredData?.map((score, key) => ( +
  • +
    + {key + 1} +
    + +
    + {score.pseudo} + {score.group}
    - ))) || - [...Array(20).keys()].map((i) => ( - - ))} -
-
-
-
+
+
+ Puzzles + {score.completions} +
+
+ Score + {score.score} +
+
+ + ))) || + [...Array(20).keys()].map((i) => ( + + ))} + + + ); } diff --git a/ui/Popover.tsx b/ui/Popover.tsx new file mode 100644 index 0000000..2d91d20 --- /dev/null +++ b/ui/Popover.tsx @@ -0,0 +1,34 @@ +import * as PopoverPrimitive from '@radix-ui/react-popover'; +import type { ReactNode } from 'react'; + +type PopoverProps = { + trigger: ReactNode; + tooltip?: ReactNode; + children: ReactNode; + open?: boolean; + onOpenChange?: (open: boolean) => void; +}; + +export default function Popover({ trigger, tooltip, children, open, onOpenChange }: PopoverProps) { + return ( + + {/* {tooltip ? ( + + {trigger} + + ) : ( */} + {trigger} + {/* )} */} + + + {children} + + + + + ); +} diff --git a/ui/Puzzle.tsx b/ui/Puzzle.tsx index ae66e31..5832bba 100644 --- a/ui/Puzzle.tsx +++ b/ui/Puzzle.tsx @@ -73,12 +73,14 @@ export default function Puzzle({ puzzle }: { puzzle: PuzzleType }) { label="Réponse" type="text" placeholder="12" + required {...register('answer')} /> diff --git a/ui/Puzzles.tsx b/ui/Puzzles.tsx index 42982a2..ce68e88 100644 --- a/ui/Puzzles.tsx +++ b/ui/Puzzles.tsx @@ -4,8 +4,9 @@ import { usePuzzles } from '@/lib/hooks/use-puzzles'; import AppLink from './AppLink'; import Icon from './Icon'; -export default function Puzzles() { - const { data, isLoading } = usePuzzles(); +export default function Puzzles({ token }: { token: string }) { + const { data, isLoading } = usePuzzles({ token }); + console.log(data); return ( <> {(!isLoading && @@ -48,7 +49,7 @@ export default function Puzzles() { />
    - {[...Array(7).keys()].map((j) => ( + {[...Array(6).keys()].map((j) => ( >> + } + //& Partial>> >(({ options, className, label, description, error, ...props }, ref) => ( <>