Big update & refactor & metadata

This commit is contained in:
Théo 2023-04-17 21:03:12 +02:00
parent 0773cba2c8
commit cc1fb080b5
21 changed files with 235 additions and 212 deletions

View file

@ -1,40 +1,38 @@
'use client'; 'use client';
import { UserContext } from '@/context/user';
import Badge from '@/ui/Badge';
import { useContext } from 'react'; import { useContext } from 'react';
import { UserContext } from '@/context/user';
import Badge from '@/ui/Badge';
export default function Page() { export default function Page() {
const { data: me } = useContext(UserContext); const { data: me } = useContext(UserContext);
return ( return (
<div className="flex h-full w-full flex-col space-y-4"> <section className="flex h-full w-full flex-col space-y-4">
<div className="w-full"> <header className="flex flex-col">
<section className="flex flex-col space-y-4"> <h1 className="text-xl font-semibold">Mes badges</h1>
<header className="flex flex-col"> <p className="text-muted">
<h3 className="text-xl font-semibold">Mes badges</h3> Vos badges sont affichés ici, vous pouvez les partager avec vos amis
<p className="text-muted"> </p>
Vos badges sont affichés ici, vous pouvez les partager avec vos amis </header>
</p> <main className="flex flex-col justify-between space-x-0 space-y-4">
</header> <div className="flex space-x-2">
<main className="flex flex-col justify-between space-x-0 space-y-4"> {me?.badges ? (
<div className="flex space-x-2"> me?.badges.map((badge, i) => (
{me?.badges ? ( <Badge
me?.badges.map((badge, i) => ( key={i}
<Badge name={badge.name}
key={i} src={badge.logo || '/assets/badges/java.png'}
name={badge.name} alt={badge.name}
src={badge.logo || '/assets/badges/java.png'} level={badge.level}
alt={badge.name} />
level={badge.level} ))
/> ) : (
)) <p className="text-muted">Aucun badge</p>
) : ( )}
<p className="text-muted">Aucun badge</p> </div>
)} </main>
</div> </section>
</main>
</section>
</div>
</div>
); );
} }

View file

@ -5,7 +5,7 @@ export default function NotFound() {
return ( return (
<div className="m-auto flex h-screen flex-col items-center justify-center space-y-6"> <div className="m-auto flex h-screen flex-col items-center justify-center space-y-6">
<h2 className="text-6xl">Oh non! Un François 404</h2> <h2 className="text-6xl">Oh non! Un François 404</h2>
<Image src={error404} alt="François 404" width={1000} height={1000} /> <Image priority src={error404} alt="François 404" width={1000} height={1000} />
</div> </div>
); );
} }

View file

@ -1,7 +1,8 @@
import { cookies } from 'next/headers';
import { getPuzzle } from '@/lib/puzzles'; import { getPuzzle } from '@/lib/puzzles';
import Puzzle from '@/ui/Puzzle'; import Puzzle from '@/ui/Puzzle';
import SWRFallback from '@/ui/SWRFallback';
import type { Metadata } from 'next'; import type { Metadata } from 'next';
import { cookies } from 'next/headers';
import { notFound } from 'next/navigation'; import { notFound } from 'next/navigation';
export async function generateMetadata({ params }: { params: { id: number } }): Promise<Metadata> { export async function generateMetadata({ params }: { params: { id: number } }): Promise<Metadata> {
@ -17,8 +18,7 @@ export async function generateMetadata({ params }: { params: { id: number } }):
return { title: `${puzzle.name} - Peer-at Code` }; return { title: `${puzzle.name} - Peer-at Code` };
} }
export default async function Page({ params }: { params: { id: number } }) { export default async function Page({ params: { id } }: { params: { id: number } }) {
const { id } = params;
const token = cookies().get('token')?.value; const token = cookies().get('token')?.value;
if (!token) { if (!token) {
@ -35,13 +35,9 @@ export default async function Page({ params }: { params: { id: number } }) {
notFound(); notFound();
} }
return <Puzzle puzzle={puzzle} />; return (
<SWRFallback fallback={{ [`puzzles/${puzzle.id}`]: puzzle }}>
<Puzzle token={token} id={id} />
</SWRFallback>
);
} }
// 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() }));
// }

View file

@ -12,7 +12,9 @@ export default async function Page() {
return ( return (
<div className="flex flex-col space-y-6"> <div className="flex flex-col space-y-6">
{/* <SWRFallback fallback={{ ['puzzles']: chapters }}> */}
<Puzzles token={token!} /> <Puzzles token={token!} />
{/* </SWRFallback> */}
</div> </div>
); );
} }

View file

@ -1,12 +0,0 @@
export default function Page() {
return (
<div className="flex h-screen w-full">
<div className="m-auto">
<h1 className="text-center text-4xl font-bold">
Amuse toi avec <span className="rounded-md bg-white p-1 text-black ">Next.js</span> et{' '}
<span className="text-blue-500">Tailwindcss</span> !
</h1>
</div>
</div>
);
}

View file

@ -1,12 +1,12 @@
import '@/styles/globals.css'; import '@/styles/globals.css';
import 'remixicon/fonts/remixicon.css'; import 'remixicon/fonts/remixicon.css';
import { type Metadata } from 'next';
import { Fira_Code } from 'next/font/google'; import { Fira_Code } from 'next/font/google';
import localFont from 'next/font/local'; import localFont from 'next/font/local';
import { type ReactNode } from 'react'; import { type ReactNode } from 'react';
import { cn } from '@/lib/utils'; import { cn, getURL } from '@/lib/utils';
import DefaultTags from '@/ui/DefaultTags';
const sans = localFont({ const sans = localFont({
variable: '--font-sans', variable: '--font-sans',
@ -20,9 +20,59 @@ const code = Fira_Code({
weight: 'variable' weight: 'variable'
}); });
export const metadata = { export const metadata: Metadata = {
title: 'Peer-at Code', title: {
description: 'Peer-at Code a pour but de donner lenvie de coder et dapprendre par le jeu!' 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 }) { export default function RootLayout({ children }: { children: ReactNode }) {
@ -36,32 +86,7 @@ export default function RootLayout({ children }: { children: ReactNode }) {
code.variable code.variable
)} )}
> >
<head> <head />
<DefaultTags />
{/* TODO: Use Metadata from 13.2 */}
{/* <title>Peer-at Code</title>
<meta property="og:type" content="website" />
<meta property="og:url" content="https://peer-at-code.be/" />
<meta property="og:title" content="Peer-at Code" />
<meta
property="og:description"
content="Peer-at Code a pour but de donner lenvie de coder et dapprendre par le jeu!"
/>
<meta property="og:image" content="/assets/social.jpg" />
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content="https://peet-at-code.be/" />
<meta property="twitter:title" content="Peer-at Code" />
<meta
property="twitter:description"
content="Peer-at Code a pour but de donner lenvie de coder et dapprendre par le jeu!"
/>
<meta property="twitter:image" content="/assets/social.jpg" /> */}
</head>
<body className="relative min-h-screen"> <body className="relative min-h-screen">
<main>{children}</main> <main>{children}</main>
</body> </body>

View file

@ -6,22 +6,24 @@ export default function Page() {
return ( return (
<div> <div>
<div className="flex h-screen w-full"> <div className="flex h-screen w-full">
<div className="m-auto flex flex-col space-y-2"> <div className="m-auto flex flex-col space-y-2 p-2">
<h1 className="text-center text-6xl font-bold">Bienvenue sur</h1> <h1 className="text-center text-6xl font-bold">Bienvenue sur</h1>
<span> <span>
<Console text="Peer-at Code" className="text-6xl" /> <Console text="Peer-at Code" className="text-6xl" />
</span> </span>
<AppLink href="/dashboard">Dashboard</AppLink> <AppLink href="/dashboard/puzzles">Commencer</AppLink>
</div> </div>
</div> </div>
<div className="item-center flex h-screen w-full px-2"> <div className="item-center flex h-screen w-full px-2">
<div className="m-auto flex flex-col justify-center md:flex-row"> <div className="m-auto flex flex-col justify-center md:flex-row">
<Image <Image
title="Philipz 'Cipher Wolf' Barlow"
src="/assets/brand/peerat.png" src="/assets/brand/peerat.png"
width={500} width={500}
height={500} height={500}
alt="Peer-at Code logo" alt="Peer-at Code logo"
className="self-center" className="self-center"
priority
/> />
<p className="self-center text-justify sm:w-3/6 md:w-2/6"> <p className="self-center text-justify sm:w-3/6 md:w-2/6">
BIP BZZ BIIIP, HIIP, HELIP, HELLO, je suis Philipz Cipher Wolf Barlow, une BIP BZZ BIIIP, HIIP, HELIP, HELLO, je suis Philipz Cipher Wolf Barlow, une

View file

@ -20,12 +20,12 @@ export type NavItem = {
* @type {NavItem[]} * @type {NavItem[]}
*/ */
export const navItems: NavItem[] = [ export const navItems: NavItem[] = [
{ // {
name: 'Dashboard', // name: 'Dashboard',
slug: '', // slug: '',
icon: 'dashboard-line', // icon: 'dashboard-line',
disabled: false // disabled: false
}, // },
{ {
name: 'Classement', name: 'Classement',
slug: 'leaderboard', slug: 'leaderboard',

View file

@ -83,7 +83,9 @@ export type Puzzle = {
id: number; id: number;
name: string; name: string;
content: string; content: string;
scoreMax: number;
tags: Tag[] | null; tags: Tag[] | null;
score?: number;
}; };
export type Chapter = { export type Chapter = {

View file

@ -27,7 +27,12 @@ export async function middleware(req: NextRequest) {
} }
if (isAuth && req.nextUrl.pathname.includes('sign')) { 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; return res;

View file

@ -11,10 +11,7 @@
input:-webkit-autofill:focus, input:-webkit-autofill:focus,
textarea:-webkit-autofill, textarea:-webkit-autofill,
textarea:-webkit-autofill:hover, textarea:-webkit-autofill:hover,
textarea:-webkit-autofill:focus, 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; -webkit-box-shadow: 0 0 0px 1000px hsl(258deg 15% 17%) inset;
transition: background-color 5000s ease-in-out 0s; transition: background-color 5000s ease-in-out 0s;
} }

View file

@ -1,7 +0,0 @@
export default function DefaultTags() {
return (
<>
<link href="/favicon.ico" rel="shortcut icon" />
</>
);
}

View file

@ -6,7 +6,7 @@ import Icon from './Icon';
type DialogProps = { type DialogProps = {
title?: ReactNode; title?: ReactNode;
tooltip: ReactNode; tooltip?: ReactNode;
trigger: ReactNode; trigger: ReactNode;
children: ReactNode; children: ReactNode;
open?: boolean; open?: boolean;

View file

@ -40,8 +40,8 @@ export default function Leaderboard({ token }: { token: string }) {
return ( return (
<section className="flex h-full w-full flex-col space-y-4"> <section className="flex h-full w-full flex-col space-y-4">
<header className="sticky flex items-center justify-between"> <header className="sticky flex items-center justify-between">
<div> <div className="flex flex-col">
<h3 className="text-xl font-semibold">Tableau des scores</h3> <h1 className="text-xl font-semibold">Tableau des scores</h1>
<p className="hidden text-muted sm:block">Suivez la progression des élèves en direct</p> <p className="hidden text-muted sm:block">Suivez la progression des élèves en direct</p>
</div> </div>
{(filteredData && ( {(filteredData && (

View file

@ -1,14 +1,17 @@
'use client'; 'use client';
import type { Puzzle as PuzzleType } from '@/lib/puzzles';
import cookies from 'js-cookie'; import cookies from 'js-cookie';
import { notFound } from 'next/navigation'; import { notFound } from 'next/navigation';
import { useState } from 'react';
import { useForm } from 'react-hook-form'; 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 Button from './Button';
import Input from './Input'; import Input from './Input';
import ToHTML from './ToHTML'; import ToHTML from './ToHTML';
import { useState } from 'react';
type PuzzleData = { type PuzzleData = {
answer: string; answer: string;
@ -21,19 +24,13 @@ type Granted = {
score?: number; score?: number;
}; };
export default function Puzzle({ puzzle }: { puzzle: PuzzleType }) { export default function Puzzle({ token, id }: { token: string; id: number }) {
if (!puzzle) {
notFound();
}
const [granted, setGranted] = useState<Granted | null>(null); const [granted, setGranted] = useState<Granted | null>(null);
const { const { data: puzzle, isLoading } = usePuzzle({ token, id });
register, const { mutate } = useSWRConfig();
handleSubmit,
formState: { errors }, const { register, handleSubmit } = useForm<PuzzleData>({
setError
} = useForm<PuzzleData>({
defaultValues: { defaultValues: {
answer: '' answer: ''
// filename: '', // filename: '',
@ -49,11 +46,11 @@ export default function Puzzle({ puzzle }: { puzzle: PuzzleType }) {
// return; // return;
// } // }
formData.append('answer', data.answer.trim()); formData.append('answer', data.answer);
// formData.append('filename', 'placeholder'); // formData.append('filename', 'placeholder');
// formData.append('code_file', new Blob(), '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', method: 'POST',
body: formData, body: formData,
headers: { headers: {
@ -62,40 +59,47 @@ export default function Puzzle({ puzzle }: { puzzle: PuzzleType }) {
}); });
if (res.ok || res.status === 406) { if (res.ok || res.status === 406) {
mutate(`puzzles/${puzzle?.id}`);
setGranted(await res.json()); setGranted(await res.json());
} }
} }
if (!puzzle && isLoading) {
return <></>;
}
if (!puzzle) {
notFound();
}
return ( return (
<div className="flex h-full w-full flex-col justify-between space-y-4"> <div className="flex h-full w-full flex-col justify-between space-y-4">
<div className="flex flex-col space-y-2"> <h1 className="text-2xl font-bold sm:text-3xl md:text-4xl">{puzzle.name}</h1>
<h2 className="text-xl font-bold sm:text-2xl md:text-3xl">{puzzle.name}</h2>
{/* <p className="text-sm text-muted">Chapitre</p> */}
</div>
<div className="flex h-screen w-full overflow-y-auto"> <div className="flex h-screen w-full overflow-y-auto">
<ToHTML className="font-code text-xs sm:text-base" data={puzzle.content} /> <ToHTML className="font-code text-xs sm:text-base" data={puzzle.content} />
</div> </div>
<form {!puzzle.score ? (
className="flex w-full flex-col justify-between sm:flex-row" <form
onSubmit={handleSubmit(onSubmit)} className="flex w-full flex-col justify-between sm:flex-row"
encType="multipart/form-data" onSubmit={handleSubmit(onSubmit)}
> encType="multipart/form-data"
<div className="flex flex-col items-center justify-center space-x-0 sm:flex-row sm:space-x-6"> >
<Input <div className="flex flex-col items-center justify-center space-x-0 sm:flex-row sm:space-x-6">
className="w-full" <Input
label="Réponse" className="w-full"
type="text" label="Réponse"
placeholder="12" type="text"
required placeholder="12"
{...register('answer')} required
/> {...register('answer')}
{granted && ( />
<div className="flex flex-col"> {granted && (
<p className="text-sm text-muted">Tentatives actuelles : {granted.tries}</p> <div className="flex flex-col">
{granted.score && <p className="text-sm text-muted">Score : {granted.score}</p>} <p className="text-sm text-muted">Tentatives actuelles : {granted.tries}</p>
</div> {granted.score && <p className="text-sm text-muted">Score : {granted.score}</p>}
)} </div>
{/* <Input )}
{/* <Input
className="h-16 w-full sm:w-1/3" className="h-16 w-full sm:w-1/3"
label="Code" label="Code"
type="file" type="file"
@ -103,11 +107,21 @@ export default function Puzzle({ puzzle }: { puzzle: PuzzleType }) {
accept=".py,.js,.ts,.java,.rs,.c" accept=".py,.js,.ts,.java,.rs,.c"
{...register('code_file')} {...register('code_file')}
/> */} /> */}
</div>
<Button kind="brand" className="mt-6" type="submit">
Envoyer
</Button>
</form>
) : (
<div className="flex items-center justify-between">
<p className="text-sm text-highlight-secondary">
Score : {puzzle.score} (Score maximum: {puzzle.scoreMax})
</p>
<AppLink href="/dashboard/puzzles" className="text-sm text-highlight-secondary">
Retour aux puzzles
</AppLink>
</div> </div>
<Button kind="brand" className="mt-6" type="submit"> )}
Envoyer
</Button>
</form>
</div> </div>
); );
} }

View file

@ -3,7 +3,7 @@
import { UserContext } from '@/context/user'; import { UserContext } from '@/context/user';
import { useGroups } from '@/lib/hooks/use-groups'; import { useGroups } from '@/lib/hooks/use-groups';
import { usePuzzles } from '@/lib/hooks/use-puzzles'; 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 { cn } from '@/lib/utils';
import { useContext, useState } from 'react'; import { useContext, useState } from 'react';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
@ -14,11 +14,11 @@ import Icon from './Icon';
import Input from './Input'; import Input from './Input';
import Select from './Select'; import Select from './Select';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { useSWRConfig } from 'swr';
export default function Puzzles({ token }: { token: string }) { export default function Puzzles({ token }: { token: string }) {
const { data: me } = useContext(UserContext); const { data: me } = useContext(UserContext);
const { data, isLoading } = usePuzzles({ token }); const { data, isLoading } = usePuzzles({ token });
const [isOpen, setIsOpen] = useState<boolean[]>([]); const [isOpen, setIsOpen] = useState<boolean[]>([]);
function handleClick(index: number) { function handleClick(index: number) {
@ -44,14 +44,11 @@ export default function Puzzles({ token }: { token: string }) {
<div key={chapter.id} className="flex flex-col space-y-4"> <div key={chapter.id} className="flex flex-col space-y-4">
<div className="flex flex-col justify-between lg:flex-row lg:items-center"> <div className="flex flex-col justify-between lg:flex-row lg:items-center">
<div className="flex items-center gap-x-2"> <div className="flex items-center gap-x-2">
<h3 className="text-xl font-semibold sm:text-2xl"> <h1 className="text-xl font-semibold sm:text-2xl">{chapter.name}</h1>
Chapitre {chapter.id} - {chapter.name}{' '}
</h3>
{!isInEventGroup(chapter) && ( {!isInEventGroup(chapter) && (
<Dialog <Dialog
key={chapter.id} key={chapter.id}
title={chapter.name} title={chapter.name}
tooltip="Select Hogwarts Level"
open={isOpen[chapter.id]} open={isOpen[chapter.id]}
onOpenChange={() => handleClick(chapter.id)} onOpenChange={() => handleClick(chapter.id)}
trigger={ trigger={
@ -96,24 +93,24 @@ export default function Puzzles({ token }: { token: string }) {
{chapter.puzzles && {chapter.puzzles &&
chapter.puzzles chapter.puzzles
.sort((p1, p2) => { .sort((p1, p2) => {
if (p1.tags == undefined) return 1; const p1Tags = p1.tags || [];
if (p2.tags == undefined) return -1; const p2Tags = p2.tags || [];
const e1 = p1.tags.findIndex((tag) => tag.name === 'easy') >= 0; const p1Easy = p1Tags.some((tag) => tag.name === 'easy');
const e2 = p2.tags.findIndex((tag) => tag.name === 'easy') >= 0; const p2Easy = p2Tags.some((tag) => tag.name === 'easy');
if (e1 && e2) return p1.tags.length - p2.tags.length; if (p1Easy !== p2Easy) {
if (e1) return -1; return p1Easy ? -1 : 1;
if (e2) return 1; }
const m1 = p1.tags.findIndex((tag) => tag.name === 'medium') >= 0; const p1Medium = p1Tags.some((tag) => tag.name === 'medium');
const m2 = p2.tags.findIndex((tag) => tag.name === 'medium') >= 0; const p2Medium = p2Tags.some((tag) => tag.name === 'medium');
if (m1 && m2) return p1.tags.length - p2.tags.length; if (p1Medium !== p2Medium) {
if (m1) return -1; return p1Medium ? -1 : 1;
if (m2) return 1; }
const h1 = p1.tags.findIndex((tag) => tag.name === 'hard') >= 0; const p1Hard = p1Tags.some((tag) => tag.name === 'hard');
const h2 = p2.tags.findIndex((tag) => tag.name === 'hard') >= 0; const p2Hard = p2Tags.some((tag) => tag.name === 'hard');
if (h1 && h2) return p1.tags.length - p2.tags.length; if (p1Hard !== p2Hard) {
if (h1) return -1; return p1Hard ? -1 : 1;
if (h2) return 1; }
return p1.tags.length - p2.tags.length; return p1Tags.length - p2Tags.length;
}) })
.map((puzzle) => ( .map((puzzle) => (
<PuzzleProp key={puzzle.id} puzzle={puzzle} chapter={chapter} /> <PuzzleProp key={puzzle.id} puzzle={puzzle} chapter={chapter} />
@ -241,17 +238,13 @@ type GroupData = {
function GroupForm({ chapter, token }: { chapter: Chapter; token: string }) { function GroupForm({ chapter, token }: { chapter: Chapter; token: string }) {
const [isJoining, setIsJoining] = useState(false); const [isJoining, setIsJoining] = useState(false);
const router = useRouter();
const { data: groups } = useGroups({ token }); const { data: groups } = useGroups({ token });
const { mutate } = useSWRConfig();
const { const router = useRouter();
register,
handleSubmit, const { register, handleSubmit, reset } = useForm<GroupData>({
formState: { errors },
setError,
reset
} = useForm<GroupData>({
defaultValues: { defaultValues: {
name: undefined, name: undefined,
chapter: chapter.id, chapter: chapter.id,
@ -260,20 +253,22 @@ function GroupForm({ chapter, token }: { chapter: Chapter; token: string }) {
}); });
async function onSubmit(data: GroupData) { async function onSubmit(data: GroupData) {
const res = await fetch( await fetch(`${process.env.NEXT_PUBLIC_API_URL}/${isJoining ? 'groupJoin' : 'groupCreate'}`, {
`${process.env.NEXT_PUBLIC_API_URL}/${isJoining ? 'groupJoin' : 'groupCreate'}`, method: 'POST',
{ body: JSON.stringify(data),
method: 'POST', headers: {
body: JSON.stringify(data), Authorization: `Bearer ${token}`
headers: {
Authorization: `Bearer ${token}`
}
} }
); });
// TODO: handle errors // TODO: handle errors
if (res.ok) { // if (res.ok) {
router.refresh(); // if (!isJoining) {
} // mutate('groups');
// } else {
// mutate('me');
// }
// router.refresh();
// }
} }
return ( return (

View file

@ -131,7 +131,7 @@ export default function UserAuthForm() {
<Input <Input
label="Nom d'utilisateur" label="Nom d'utilisateur"
type="text" type="text"
placeholder="CZ" placeholder="CW"
required required
error={errors.pseudo?.message} error={errors.pseudo?.message}
{...register('pseudo')} {...register('pseudo')}

View file

@ -21,7 +21,15 @@ export default function Sidenav({ isOpen, toggle }: { isOpen: boolean; toggle: (
<div className="flex h-full flex-col"> <div className="flex h-full flex-col">
<div className="flex w-full justify-center p-[9px]"> <div className="flex w-full justify-center p-[9px]">
<AppLink href="/"> <AppLink href="/">
<Image src="/assets/brand/peerat.png" alt="Peer-at" width={50} height={50} /> <Image
title="Logo"
src="/assets/brand/peerat.png"
alt="Peer-at"
width={50}
height={50}
loading="eager"
priority
/>
</AppLink> </AppLink>
</div> </div>
<div className="px-4"> <div className="px-4">
@ -56,10 +64,10 @@ export default function Sidenav({ isOpen, toggle }: { isOpen: boolean; toggle: (
<li> <li>
<NavItem <NavItem
item={{ item={{
name: 'Tutoriels', name: 'Git',
slug: 'tutorials', slug: 'https://git.peerat.dev/Peer-at-Code',
icon: 'question-line', icon: 'git-repository-line',
disabled: true disabled: false
}} }}
isOpen={isOpen} isOpen={isOpen}
onClick={toggle} onClick={toggle}
@ -99,7 +107,6 @@ function NavItem({
'justify-start sm:justify-center': !isOpen 'justify-start sm:justify-center': !isOpen
})} })}
onClick={onClick} onClick={onClick}
passHref
> >
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<Icon className="text-2xl" name={item.icon} /> <Icon className="text-2xl" name={item.icon} />

View file

@ -8,6 +8,7 @@ import Usernav from './Usernav';
export default function Wrapper({ children }: { children: ReactNode }) { export default function Wrapper({ children }: { children: ReactNode }) {
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const toggle = () => setIsOpen(!isOpen); const toggle = () => setIsOpen(!isOpen);
return ( return (
<div className="flex flex-1 overflow-hidden"> <div className="flex flex-1 overflow-hidden">
<Sidenav isOpen={isOpen} toggle={toggle} /> <Sidenav isOpen={isOpen} toggle={toggle} />

View file

@ -25,7 +25,7 @@ export default function EventLeaderboard({ id }: { token: string; id: number })
return () => socket.close(); return () => socket.close();
}; };
const { data, error } = useSWRSubscription( const { data } = useSWRSubscription(
`wss://${process.env.NEXT_PUBLIC_API_URL?.split('//')[1]}/rleaderboard/${id}`, `wss://${process.env.NEXT_PUBLIC_API_URL?.split('//')[1]}/rleaderboard/${id}`,
subscription subscription
); );
@ -65,12 +65,12 @@ export default function EventLeaderboard({ id }: { token: string; id: number })
</div> </div>
</div> </div>
<div className="flex items-center space-x-4"> <div className="flex items-center space-x-4">
<div className="flex flex-col"> {/* <div className="flex flex-col">
<span className="text-sm font-semibold">Essaies</span> <span className="text-sm font-semibold">Puzzles</span>
<span className="text-lg text-muted"> <span className="text-lg text-muted">
{group.players.reduce((a, b) => a + b.tries, 0)} {group.players.reduce((a, b) => a + b.completions, 0)}
</span> </span>
</div> </div> */}
<div className="flex flex-col"> <div className="flex flex-col">
<span className="text-sm font-semibold">Score</span> <span className="text-sm font-semibold">Score</span>
<span className="text-lg text-muted"> <span className="text-lg text-muted">

View file

@ -5,8 +5,6 @@ export default function Podium({ score }: { score: any }) {
.reduce((podiumOrder, position) => [...podiumOrder, score[position]] as any, []) .reduce((podiumOrder, position) => [...podiumOrder, score[position]] as any, [])
.filter(Boolean); .filter(Boolean);
console.log(podium);
return ( return (
<div <div
className="mt-8 grid grid-flow-col-dense place-content-center content-end items-end justify-center justify-items-center gap-2" className="mt-8 grid grid-flow-col-dense place-content-center content-end items-end justify-center justify-items-center gap-2"