diff --git a/app/dashboard/leaderboard/page.tsx b/app/dashboard/leaderboard/page.tsx index ba22a15..d88d78a 100644 --- a/app/dashboard/leaderboard/page.tsx +++ b/app/dashboard/leaderboard/page.tsx @@ -1,12 +1,10 @@ import Leaderboard from '@/ui/Leaderboard'; -import { cookies } from 'next/headers'; export const metadata = { - title: 'Tableau des scores - Peer-at Code', + title: 'Tableau des scores', description: 'Suivez la progression des élèves en direct' }; export default async function Page() { - const token = cookies().get('token')?.value; - return ; + return ; } diff --git a/app/dashboard/puzzles/[id]/page.tsx b/app/dashboard/puzzles/[id]/page.tsx index bba8d8a..09a97f1 100644 --- a/app/dashboard/puzzles/[id]/page.tsx +++ b/app/dashboard/puzzles/[id]/page.tsx @@ -1,4 +1,5 @@ import { getPuzzle } from '@/lib/puzzles'; +import { getURL } from '@/lib/utils'; import Puzzle from '@/ui/Puzzle'; import SWRFallback from '@/ui/SWRFallback'; import type { Metadata } from 'next'; @@ -15,7 +16,22 @@ export async function generateMetadata({ params }: { params: { id: number } }): const puzzle = await getPuzzle({ token, id }); - return { title: `${puzzle.name} - Peer-at Code` }; + if (!puzzle) { + notFound(); + } + + return { + title: puzzle.name, + openGraph: { + title: puzzle.name, + type: 'website', + url: getURL(`/dashboard/puzzles/${puzzle.id}`) + // IMAGES WITH OG IMAGE + }, + twitter: { + title: puzzle.name + } + }; } export default async function Page({ params: { id } }: { params: { id: number } }) { @@ -25,11 +41,7 @@ export default async function Page({ params: { id } }: { params: { id: number } notFound(); } - const puzzle = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/puzzle/${id}`, { - headers: { - Authorization: `Bearer ${token}` - } - }).then((res) => res.json()); + const puzzle = await getPuzzle({ token, id }); if (!puzzle) { notFound(); diff --git a/app/dashboard/puzzles/page.tsx b/app/dashboard/puzzles/page.tsx index cd703a6..62bef6c 100644 --- a/app/dashboard/puzzles/page.tsx +++ b/app/dashboard/puzzles/page.tsx @@ -1,20 +1,29 @@ import { cookies } from 'next/headers'; import Puzzles from '@/ui/Puzzles'; +import SWRFallback from '@/ui/SWRFallback'; +import { getPuzzles } from '@/lib/puzzles'; +import { notFound } from 'next/navigation'; export const metadata = { - title: 'Puzzles - Peer-at Code' + title: 'Puzzles' }; export default async function Page() { const cookieStore = cookies(); - const token = cookieStore.get('token')?.value; + const token = cookieStore.get('token')!.value; + + const puzzles = await getPuzzles({ token }); + + if (!puzzles) { + notFound(); + } return (
- {/* */} - - {/* */} + + +
); } diff --git a/app/page.tsx b/app/page.tsx index 3a5cea6..3d88e0a 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -11,7 +11,7 @@ export default function Page() { - Commencer + Commencer l'aventure
diff --git a/lib/groups.ts b/lib/groups.ts index b03740b..18f9482 100644 --- a/lib/groups.ts +++ b/lib/groups.ts @@ -1,23 +1,21 @@ -import fetcher from './fetcher'; - export const getGroups = async ({ token }: { token: string }): Promise => { - const { data, status } = await fetcher.get(`/groups`, { + const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/groups`, { headers: { Authorization: `Bearer ${token}` } }); - const groups = data; + const groups = (await res.json()) as Group[]; - if (status !== 200) { + if (!res.ok) { throw new Error('Failed to fetch groups'); } if (!groups) { - return [] as Group[]; + return []; } - return groups as Group[]; + return groups; }; export type Group = { diff --git a/lib/leaderboard.ts b/lib/leaderboard.ts index 525957b..b41f53a 100644 --- a/lib/leaderboard.ts +++ b/lib/leaderboard.ts @@ -1,24 +1,23 @@ -import fetcher from './fetcher'; import { type Group } from './groups'; export const getScores = async ({ token }: { token: string }): Promise => { - const { data, status } = await fetcher.get(`/leaderboard`, { + const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/leaderboard`, { headers: { Authorization: `Bearer ${token}` } }); - const scores = data; + const scores = (await res.json()) as Score[]; - if (status !== 200) { + if (!res.ok) { throw new Error('Failed to fetch scores'); } if (!scores) { - return [] as Score[]; + return []; } - return scores as Score[]; + return scores; }; export const getScoresEvent = async ({ @@ -28,16 +27,16 @@ export const getScoresEvent = async ({ token: string; id: number; }): Promise => { - const { data, status } = await fetcher.get(`/leaderboard/${id}`, { + const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/leaderboard/${id}`, { headers: { Authorization: `Bearer ${token}` } }); - const scores = data; + const scores = (await res.json()) as ScoreEvent; - if (status !== 200) { - throw new Error('Failed to fetch scores'); + if (!res.ok) { + throw new Error('Failed to fetch event scores'); } if (!scores) { diff --git a/lib/players.ts b/lib/players.ts index 845e098..9cf09d3 100644 --- a/lib/players.ts +++ b/lib/players.ts @@ -1,4 +1,3 @@ -import fetcher from './fetcher'; import { type Group } from './groups'; export const getPlayer = async ({ @@ -8,15 +7,15 @@ export const getPlayer = async ({ token: string; username?: string; }): Promise => { - const { data, status } = await fetcher.get(`/player/${username}`, { + const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/player/${username}`, { headers: { Authorization: `Bearer ${token}` } }); - const player = data; + const player = (await res.json()) as Player; - if (status !== 200) { + if (!res.ok) { throw new Error('Failed to fetch player'); } @@ -24,7 +23,7 @@ export const getPlayer = async ({ return null; } - return player as Player; + return player; }; export type Player = { diff --git a/lib/puzzles.ts b/lib/puzzles.ts index 158a41d..570162d 100644 --- a/lib/puzzles.ts +++ b/lib/puzzles.ts @@ -1,15 +1,13 @@ -import fetcher from './fetcher'; - export const getChapters = async ({ token }: { token: string }): Promise => { - const { data, status } = await fetcher.get(`/chapters`, { + const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/chapters`, { headers: { Authorization: `Bearer ${token}` } }); - const chapters = data; + const chapters = (await res.json()) as Chapter[]; - if (status !== 200) { + if (!res.ok) { throw new Error('Failed to fetch puzzles'); } @@ -17,7 +15,7 @@ export const getChapters = async ({ token }: { token: string }): Promise => { - const { data, status } = await fetcher.get(`/chapter/${id}`, { + const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/chapter/${id}`, { headers: { Authorization: `Bearer ${token}` } }); - const chapter = data; + const chapter = (await res.json()) as Chapter; - if (status !== 200) { + if (!res.ok) { throw new Error('Failed to fetch puzzles'); } @@ -43,40 +41,50 @@ export const getChapter = async ({ return null; } - return chapter as Chapter; + return chapter; }; export const getPuzzles = async ({ token }: { token: string }): Promise => { const chapters = await getChapters({ token }); - for (let i = 0; i < chapters.length; i++) { - const chapter = chapters[i]; - const chapterData = await getChapter({ token, id: chapter.id }); - if (!chapterData) continue; - chapters[i].puzzles = chapterData.puzzles; + if (!chapters) { + return []; } - return chapters as Chapter[]; + for (let i = 0; i < chapters.length; i++) { + let chapter = chapters[i]; + chapter = (await getChapter({ token, id: chapter.id })) as Chapter; + if (!chapter) continue; + chapters[i].puzzles = chapter.puzzles; + } + + return chapters; }; -export const getPuzzle = async ({ token, id }: { token: string; id: number }): Promise => { - const { data, status } = await fetcher.get(`/puzzle/${id}`, { +export const getPuzzle = async ({ + token, + id +}: { + token: string; + id: number; +}): Promise => { + const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/puzzle/${id}`, { headers: { Authorization: `Bearer ${token}` } }); - const puzzle = data; + const puzzle = (await res.json()) as Puzzle; - if (status !== 200) { + if (!res.ok) { throw new Error('Failed to fetch puzzle'); } if (!puzzle) { - return {} as Puzzle; + return null; } - return puzzle as Puzzle; + return puzzle; }; export type Puzzle = { diff --git a/ui/Console.tsx b/ui/Console.tsx index 9dc2823..6e588bc 100644 --- a/ui/Console.tsx +++ b/ui/Console.tsx @@ -7,7 +7,7 @@ export default function Console({ text, className }: { text: string; className?: const [message, setMessage] = useState(''); const [typingIndex, setTypingIndex] = useState(0); - const typingDelay = 400; // The delay between each character being typed, in milliseconds + const typingDelay = 200; // The delay between each character being typed, in milliseconds useEffect(() => { const intervalId = setInterval(() => { diff --git a/ui/Leaderboard.tsx b/ui/Leaderboard.tsx index 1c4c046..5365b85 100644 --- a/ui/Leaderboard.tsx +++ b/ui/Leaderboard.tsx @@ -1,19 +1,15 @@ 'use client'; -import { useLeaderboard } from '@/lib/hooks/use-leaderboard'; -import { cn } from '@/lib/utils'; -import { useMemo, useState } from 'react'; -import AvatarComponent from './Avatar'; -import Select from './Select'; -import { type ScoreEvent } from '@/lib/leaderboard'; import useSWRSubscription, { type SWRSubscription } from 'swr/subscription'; -import { useGroups } from '@/lib/hooks/use-groups'; + +import { type ScoreEvent } from '@/lib/leaderboard'; +import { cn } from '@/lib/utils'; +import Podium from '@/ui/events/podium/Podium'; const SCORE_COLORS = ['text-yellow-400', 'text-gray-400', 'text-orange-400']; -export default function Leaderboard({ token }: { token: string }) { - // const { data, isLoading } = useLeaderboard({ token }); - +export default function Leaderboard() { + // TODO CHANGER CECI const CHAPITRE_EVENT = 3; const subscription: SWRSubscription = (key, { next }) => { @@ -31,34 +27,17 @@ export default function Leaderboard({ token }: { token: string }) { }; const { data } = useSWRSubscription( - `wss://${process.env.NEXT_PUBLIC_API_URL?.split('//')[1]}/rleaderboard${CHAPITRE_EVENT}`, + `wss://${process.env.NEXT_PUBLIC_API_URL?.split('//')[1]}/rleaderboard/${CHAPITRE_EVENT}`, subscription ); - const [filter, setFilter] = useState(''); - - let options = [] as { value: string; title: string }[]; - - const { data: groups } = useGroups({ token }); - - console.log(groups); - - if (groups) { - options = groups - .filter((group) => group.chapter === null) - .map((group) => ({ value: group.name, title: group.name })) - .filter((group, index, self) => self.findIndex((g) => g.value === group.value) === index) - .sort((a, b) => a.title.localeCompare(b.title)); - - options.unshift({ value: '', title: 'Tous' }); - } - - const filteredData = useMemo(() => { - if (filter) { - return data?.groups.filter((group) => group.name === filter); - } - return data; - }, [data, filter]); + const scores = [data?.groups] + .flat() + .sort((a, b) => a!.rank - b!.rank) + .map((group, place) => ({ + ...group, + place + })); return (
@@ -67,23 +46,9 @@ export default function Leaderboard({ token }: { token: string }) {

Tableau des scores

Suivez la progression des élèves en direct

- {/* {(filteredData && ( - setFilter(event.target.value)} + /> + ); +} + type GroupData = { name?: string; chapter?: number; diff --git a/ui/ToHTML.tsx b/ui/ToHTML.tsx index 1b6ccd1..76ec86d 100644 --- a/ui/ToHTML.tsx +++ b/ui/ToHTML.tsx @@ -2,27 +2,27 @@ import { cn } from '@/lib/utils'; import Mardown from 'react-markdown'; -import remarkGfm from 'remark-gfm'; import remarkBreaks from 'remark-breaks'; +import remarkGfm from 'remark-gfm'; export default function ToHTML({ data, className }: { data: string; className?: string }) { return (
( + a: ({ node, ...props }) => ( ), - code: ({ ...props }) => ( + code: ({ node, ...props }) => ( ) }} diff --git a/ui/events/podium/PodiumStep.tsx b/ui/events/podium/PodiumStep.tsx index ae5ab3e..f8cf968 100644 --- a/ui/events/podium/PodiumStep.tsx +++ b/ui/events/podium/PodiumStep.tsx @@ -25,7 +25,7 @@ export default function PodiumStep({ visible: () => ({ opacity: 1, transition: { - delay: podium.length - group.place + 1, + delay: podium.length - group.place + 0.5, duration: 0.75 } }), @@ -44,7 +44,7 @@ export default function PodiumStep({ height: 200 * ((podium.length - group.place) / podium.length), opacity: 2, transition: { - delay: podium.length - group.place, + delay: podium.length - group.place - 0.5, duration: 1.25, ease: 'backInOut' }