diff --git a/app/dashboard/badges/page.tsx b/app/dashboard/badges/page.tsx index 34b3300..b70d5ec 100644 --- a/app/dashboard/badges/page.tsx +++ b/app/dashboard/badges/page.tsx @@ -1,40 +1,38 @@ 'use client'; -import { UserContext } from '@/context/user'; -import Badge from '@/ui/Badge'; import { useContext } from 'react'; +import { UserContext } from '@/context/user'; + +import Badge from '@/ui/Badge'; + export default function Page() { const { data: me } = useContext(UserContext); return ( -
-
-
-
-

Mes badges

-

- Vos badges sont affichés ici, vous pouvez les partager avec vos amis -

-
-
-
- {me?.badges ? ( - me?.badges.map((badge, i) => ( - - )) - ) : ( -

Aucun badge

- )} -
-
-
-
-
+
+
+

Mes badges

+

+ Vos badges sont affichés ici, vous pouvez les partager avec vos amis +

+
+
+
+ {me?.badges ? ( + me?.badges.map((badge, i) => ( + + )) + ) : ( +

Aucun badge

+ )} +
+
+
); } diff --git a/app/dashboard/puzzles/[id]/not-found.tsx b/app/dashboard/puzzles/[id]/not-found.tsx index 1b0e080..d4965df 100644 --- a/app/dashboard/puzzles/[id]/not-found.tsx +++ b/app/dashboard/puzzles/[id]/not-found.tsx @@ -5,7 +5,7 @@ export default function NotFound() { return (

Oh non! Un François 404

- François 404 + François 404
); } diff --git a/app/dashboard/puzzles/[id]/page.tsx b/app/dashboard/puzzles/[id]/page.tsx index c078490..bba8d8a 100644 --- a/app/dashboard/puzzles/[id]/page.tsx +++ b/app/dashboard/puzzles/[id]/page.tsx @@ -1,7 +1,8 @@ -import { cookies } from 'next/headers'; import { getPuzzle } from '@/lib/puzzles'; import Puzzle from '@/ui/Puzzle'; +import SWRFallback from '@/ui/SWRFallback'; import type { Metadata } from 'next'; +import { cookies } from 'next/headers'; import { notFound } from 'next/navigation'; export async function generateMetadata({ params }: { params: { id: number } }): Promise { @@ -17,8 +18,7 @@ export async function generateMetadata({ params }: { params: { id: number } }): return { title: `${puzzle.name} - Peer-at Code` }; } -export default async function Page({ params }: { params: { id: number } }) { - const { id } = params; +export default async function Page({ params: { id } }: { params: { id: number } }) { const token = cookies().get('token')?.value; if (!token) { @@ -35,13 +35,9 @@ export default async function Page({ params }: { params: { id: number } }) { notFound(); } - return ; + return ( + + + + ); } - -// export const dynamicParams = true; - -// export async function generateStaticParams() { -// const { puzzles } = await getPuzzles(); -// // every id is a number, but we need to return a string -// return puzzles.flatMap((puzzle) => ({ id: puzzle.id.toString() })); -// } diff --git a/app/dashboard/puzzles/page.tsx b/app/dashboard/puzzles/page.tsx index 29aea34..cd703a6 100644 --- a/app/dashboard/puzzles/page.tsx +++ b/app/dashboard/puzzles/page.tsx @@ -12,7 +12,9 @@ export default async function Page() { return (
+ {/* */} + {/* */}
); } diff --git a/app/dashboard/settings/page.tsx b/app/dashboard/settings/page.tsx deleted file mode 100644 index cb8cab1..0000000 --- a/app/dashboard/settings/page.tsx +++ /dev/null @@ -1,12 +0,0 @@ -export default function Page() { - return ( -
-
-

- Amuse toi avec Next.js et{' '} - Tailwindcss ! -

-
-
- ); -} diff --git a/app/layout.tsx b/app/layout.tsx index d314f7e..b0db892 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,12 +1,12 @@ import '@/styles/globals.css'; import 'remixicon/fonts/remixicon.css'; +import { type Metadata } from 'next'; import { Fira_Code } from 'next/font/google'; import localFont from 'next/font/local'; import { type ReactNode } from 'react'; -import { cn } from '@/lib/utils'; -import DefaultTags from '@/ui/DefaultTags'; +import { cn, getURL } from '@/lib/utils'; const sans = localFont({ variable: '--font-sans', @@ -20,9 +20,59 @@ const code = Fira_Code({ weight: 'variable' }); -export const metadata = { - title: 'Peer-at Code', - description: 'Peer-at Code a pour but de donner l’envie de coder et d’apprendre par le jeu!' +export const metadata: Metadata = { + title: { + default: 'Peer-at Code', + template: `%s - Peer-at Code` + }, + description: "Apprendre la programmation et la cybersécurité en s'amusant.", + // manifest: getURL('/favicon/site.webmanifest'), + openGraph: { + title: { + default: 'Peer-at Code', + template: `%s - Peer-at Code` + }, + description: "Apprendre la programmation et la cybersécurité en s'amusant.", + url: getURL(), + siteName: 'Peer-at Code', + // images: getURL('/assets/social.jpg'), + type: 'website' + }, + twitter: { + card: 'summary_large_image', + title: { + default: 'Peer-at Code', + template: `%s - Peer-at Code` + }, + description: "Apprendre la programmation et la cybersécurité en s'amusant." + // images: getURL('/assets/social.jpg'), + }, + alternates: { + canonical: getURL() + }, + robots: { + index: true, + follow: true, + googleBot: { + index: true, + follow: true + } + }, + icons: { + icon: [ + { + url: getURL('/assets/icons/favicon-32x32.png'), + sizes: '32x32' + }, + { + url: getURL('/assets/icons/favicon-16x16.png'), + sizes: '16x16' + } + ], + shortcut: getURL('/favicon.ico'), + apple: getURL('/assets/icons/apple-touch-icon.png') + }, + themeColor: '#110F15' }; export default function RootLayout({ children }: { children: ReactNode }) { @@ -36,32 +86,7 @@ export default function RootLayout({ children }: { children: ReactNode }) { code.variable )} > - - - - {/* TODO: Use Metadata from 13.2 */} - - {/* Peer-at Code - - - - - - - - - - - - - */} - +
{children}
diff --git a/app/page.tsx b/app/page.tsx index dc0934f..3a5cea6 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -6,22 +6,24 @@ export default function Page() { return (
-
+

Bienvenue sur

- Dashboard + Commencer
Peer-at Code logo

BIP BZZ BIIIP, HIIP, HELIP, HELLO, je suis Philipz ‘Cipher Wolf’ Barlow, une diff --git a/lib/nav-items.ts b/lib/nav-items.ts index 1e8cdb7..d252a7a 100644 --- a/lib/nav-items.ts +++ b/lib/nav-items.ts @@ -20,12 +20,12 @@ export type NavItem = { * @type {NavItem[]} */ export const navItems: NavItem[] = [ - { - name: 'Dashboard', - slug: '', - icon: 'dashboard-line', - disabled: false - }, + // { + // name: 'Dashboard', + // slug: '', + // icon: 'dashboard-line', + // disabled: false + // }, { name: 'Classement', slug: 'leaderboard', diff --git a/lib/puzzles.ts b/lib/puzzles.ts index cc3fe12..5c1a4b5 100644 --- a/lib/puzzles.ts +++ b/lib/puzzles.ts @@ -83,7 +83,9 @@ export type Puzzle = { id: number; name: string; content: string; + scoreMax: number; tags: Tag[] | null; + score?: number; }; export type Chapter = { diff --git a/middleware.ts b/middleware.ts index aced43a..eb7bcc0 100644 --- a/middleware.ts +++ b/middleware.ts @@ -27,7 +27,12 @@ export async function middleware(req: NextRequest) { } if (isAuth && req.nextUrl.pathname.includes('sign')) { - return NextResponse.redirect(getURL('/dashboard')); + return NextResponse.redirect(getURL('/dashboard/puzzles')); + } + + // TODO REMOVE + if (isAuth && req.nextUrl.pathname.match(/^\/dashboard\/?$/)) { + return NextResponse.redirect(getURL('/dashboard/puzzles')); } return res; diff --git a/styles/globals.css b/styles/globals.css index 5cc833f..e06bde9 100644 --- a/styles/globals.css +++ b/styles/globals.css @@ -11,10 +11,7 @@ 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 { + textarea:-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/DefaultTags.tsx b/ui/DefaultTags.tsx deleted file mode 100644 index e5cee56..0000000 --- a/ui/DefaultTags.tsx +++ /dev/null @@ -1,7 +0,0 @@ -export default function DefaultTags() { - return ( - <> - - - ); -} diff --git a/ui/Dialog.tsx b/ui/Dialog.tsx index b4a356d..eff2215 100644 --- a/ui/Dialog.tsx +++ b/ui/Dialog.tsx @@ -6,7 +6,7 @@ import Icon from './Icon'; type DialogProps = { title?: ReactNode; - tooltip: ReactNode; + tooltip?: ReactNode; trigger: ReactNode; children: ReactNode; open?: boolean; diff --git a/ui/Leaderboard.tsx b/ui/Leaderboard.tsx index 70467df..a79c6b2 100644 --- a/ui/Leaderboard.tsx +++ b/ui/Leaderboard.tsx @@ -40,8 +40,8 @@ export default function Leaderboard({ token }: { token: string }) { return (

-
-

Tableau des scores

+
+

Tableau des scores

Suivez la progression des élèves en direct

{(filteredData && ( diff --git a/ui/Puzzle.tsx b/ui/Puzzle.tsx index eb609de..ec9afb4 100644 --- a/ui/Puzzle.tsx +++ b/ui/Puzzle.tsx @@ -1,14 +1,17 @@ 'use client'; -import type { Puzzle as PuzzleType } from '@/lib/puzzles'; import cookies from 'js-cookie'; import { notFound } from 'next/navigation'; +import { useState } from 'react'; import { useForm } from 'react-hook-form'; +import { useSWRConfig } from 'swr'; +import { usePuzzle } from '@/lib/hooks/use-puzzles'; + +import AppLink from './AppLink'; import Button from './Button'; import Input from './Input'; import ToHTML from './ToHTML'; -import { useState } from 'react'; type PuzzleData = { answer: string; @@ -21,19 +24,13 @@ type Granted = { score?: number; }; -export default function Puzzle({ puzzle }: { puzzle: PuzzleType }) { - if (!puzzle) { - notFound(); - } - +export default function Puzzle({ token, id }: { token: string; id: number }) { const [granted, setGranted] = useState(null); - const { - register, - handleSubmit, - formState: { errors }, - setError - } = useForm({ + const { data: puzzle, isLoading } = usePuzzle({ token, id }); + const { mutate } = useSWRConfig(); + + const { register, handleSubmit } = useForm({ defaultValues: { answer: '' // filename: '', @@ -49,11 +46,11 @@ export default function Puzzle({ puzzle }: { puzzle: PuzzleType }) { // return; // } - formData.append('answer', data.answer.trim()); + formData.append('answer', data.answer); // formData.append('filename', 'placeholder'); // formData.append('code_file', new Blob(), 'placeholder'); - const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/puzzleResponse/${puzzle.id}`, { + const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/puzzleResponse/${puzzle!.id}`, { method: 'POST', body: formData, headers: { @@ -62,40 +59,47 @@ export default function Puzzle({ puzzle }: { puzzle: PuzzleType }) { }); if (res.ok || res.status === 406) { + mutate(`puzzles/${puzzle?.id}`); setGranted(await res.json()); } } + if (!puzzle && isLoading) { + return <>; + } + + if (!puzzle) { + notFound(); + } + return (
-
-

{puzzle.name}

- {/*

Chapitre

*/} -
+

{puzzle.name}

-
-
- - {granted && ( -
-

Tentatives actuelles : {granted.tries}

- {granted.score &&

Score : {granted.score}

} -
- )} - {/* +
+ + {granted && ( +
+

Tentatives actuelles : {granted.tries}

+ {granted.score &&

Score : {granted.score}

} +
+ )} + {/* */} +
+ + + ) : ( +
+

+ Score : {puzzle.score} (Score maximum: {puzzle.scoreMax}) +

+ + Retour aux puzzles +
- - + )}
); } diff --git a/ui/Puzzles.tsx b/ui/Puzzles.tsx index 1a1aaa3..c379357 100644 --- a/ui/Puzzles.tsx +++ b/ui/Puzzles.tsx @@ -3,7 +3,7 @@ import { UserContext } from '@/context/user'; import { useGroups } from '@/lib/hooks/use-groups'; import { usePuzzles } from '@/lib/hooks/use-puzzles'; -import type { Chapter, Puzzle, Tag } from '@/lib/puzzles'; +import type { Chapter, Puzzle } from '@/lib/puzzles'; import { cn } from '@/lib/utils'; import { useContext, useState } from 'react'; import { useForm } from 'react-hook-form'; @@ -14,11 +14,11 @@ import Icon from './Icon'; import Input from './Input'; import Select from './Select'; import { useRouter } from 'next/navigation'; +import { useSWRConfig } from 'swr'; export default function Puzzles({ token }: { token: string }) { const { data: me } = useContext(UserContext); const { data, isLoading } = usePuzzles({ token }); - const [isOpen, setIsOpen] = useState([]); function handleClick(index: number) { @@ -44,14 +44,11 @@ export default function Puzzles({ token }: { token: string }) {
-

- Chapitre {chapter.id} - {chapter.name}{' '} -

+

{chapter.name}

{!isInEventGroup(chapter) && ( handleClick(chapter.id)} trigger={ @@ -96,24 +93,24 @@ export default function Puzzles({ token }: { token: string }) { {chapter.puzzles && chapter.puzzles .sort((p1, p2) => { - if (p1.tags == undefined) return 1; - if (p2.tags == undefined) return -1; - const e1 = p1.tags.findIndex((tag) => tag.name === 'easy') >= 0; - const e2 = p2.tags.findIndex((tag) => tag.name === 'easy') >= 0; - if (e1 && e2) return p1.tags.length - p2.tags.length; - if (e1) return -1; - if (e2) return 1; - const m1 = p1.tags.findIndex((tag) => tag.name === 'medium') >= 0; - const m2 = p2.tags.findIndex((tag) => tag.name === 'medium') >= 0; - if (m1 && m2) return p1.tags.length - p2.tags.length; - if (m1) return -1; - if (m2) return 1; - const h1 = p1.tags.findIndex((tag) => tag.name === 'hard') >= 0; - const h2 = p2.tags.findIndex((tag) => tag.name === 'hard') >= 0; - if (h1 && h2) return p1.tags.length - p2.tags.length; - if (h1) return -1; - if (h2) return 1; - return p1.tags.length - p2.tags.length; + const p1Tags = p1.tags || []; + const p2Tags = p2.tags || []; + const p1Easy = p1Tags.some((tag) => tag.name === 'easy'); + const p2Easy = p2Tags.some((tag) => tag.name === 'easy'); + if (p1Easy !== p2Easy) { + return p1Easy ? -1 : 1; + } + const p1Medium = p1Tags.some((tag) => tag.name === 'medium'); + const p2Medium = p2Tags.some((tag) => tag.name === 'medium'); + if (p1Medium !== p2Medium) { + return p1Medium ? -1 : 1; + } + const p1Hard = p1Tags.some((tag) => tag.name === 'hard'); + const p2Hard = p2Tags.some((tag) => tag.name === 'hard'); + if (p1Hard !== p2Hard) { + return p1Hard ? -1 : 1; + } + return p1Tags.length - p2Tags.length; }) .map((puzzle) => ( @@ -241,17 +238,13 @@ type GroupData = { function GroupForm({ chapter, token }: { chapter: Chapter; token: string }) { const [isJoining, setIsJoining] = useState(false); - const router = useRouter(); const { data: groups } = useGroups({ token }); + const { mutate } = useSWRConfig(); - const { - register, - handleSubmit, - formState: { errors }, - setError, - reset - } = useForm({ + const router = useRouter(); + + const { register, handleSubmit, reset } = useForm({ defaultValues: { name: undefined, chapter: chapter.id, @@ -260,20 +253,22 @@ function GroupForm({ chapter, token }: { chapter: Chapter; token: string }) { }); async function onSubmit(data: GroupData) { - const res = await fetch( - `${process.env.NEXT_PUBLIC_API_URL}/${isJoining ? 'groupJoin' : 'groupCreate'}`, - { - method: 'POST', - body: JSON.stringify(data), - headers: { - Authorization: `Bearer ${token}` - } + await fetch(`${process.env.NEXT_PUBLIC_API_URL}/${isJoining ? 'groupJoin' : 'groupCreate'}`, { + method: 'POST', + body: JSON.stringify(data), + headers: { + Authorization: `Bearer ${token}` } - ); + }); // TODO: handle errors - if (res.ok) { - router.refresh(); - } + // if (res.ok) { + // if (!isJoining) { + // mutate('groups'); + // } else { + // mutate('me'); + // } + // router.refresh(); + // } } return ( diff --git a/ui/UserAuthForm.tsx b/ui/UserAuthForm.tsx index 20aeb80..ae8983d 100644 --- a/ui/UserAuthForm.tsx +++ b/ui/UserAuthForm.tsx @@ -131,7 +131,7 @@ export default function UserAuthForm() {
- Peer-at + Peer-at
@@ -56,10 +64,10 @@ export default function Sidenav({ isOpen, toggle }: { isOpen: boolean; toggle: (
  • diff --git a/ui/dashboard/Wrapper.tsx b/ui/dashboard/Wrapper.tsx index dd65d66..7411fff 100644 --- a/ui/dashboard/Wrapper.tsx +++ b/ui/dashboard/Wrapper.tsx @@ -8,6 +8,7 @@ import Usernav from './Usernav'; export default function Wrapper({ children }: { children: ReactNode }) { const [isOpen, setIsOpen] = useState(false); const toggle = () => setIsOpen(!isOpen); + return (
    diff --git a/ui/events/Leaderboard.tsx b/ui/events/Leaderboard.tsx index 4bc0291..cc0b987 100644 --- a/ui/events/Leaderboard.tsx +++ b/ui/events/Leaderboard.tsx @@ -25,7 +25,7 @@ export default function EventLeaderboard({ id }: { token: string; id: number }) return () => socket.close(); }; - const { data, error } = useSWRSubscription( + const { data } = useSWRSubscription( `wss://${process.env.NEXT_PUBLIC_API_URL?.split('//')[1]}/rleaderboard/${id}`, subscription ); @@ -65,12 +65,12 @@ export default function EventLeaderboard({ id }: { token: string; id: number })
    -
    - Essaies + {/*
    + Puzzles - {group.players.reduce((a, b) => a + b.tries, 0)} + {group.players.reduce((a, b) => a + b.completions, 0)} -
    +
    */}
    Score diff --git a/ui/events/podium/Podium.tsx b/ui/events/podium/Podium.tsx index 370358d..2bb70c8 100644 --- a/ui/events/podium/Podium.tsx +++ b/ui/events/podium/Podium.tsx @@ -5,8 +5,6 @@ export default function Podium({ score }: { score: any }) { .reduce((podiumOrder, position) => [...podiumOrder, score[position]] as any, []) .filter(Boolean); - console.log(podium); - return (