Added Dashboard page back
This commit is contained in:
parent
8e15b1793b
commit
a84a70fdc2
12 changed files with 166 additions and 197 deletions
|
@ -1,104 +1,99 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import { useContext } from 'react';
|
||||||
|
|
||||||
import { UserContext } from '@/context/user';
|
import { UserContext } from '@/context/user';
|
||||||
import Card from '@/ui/Card';
|
import Card from '@/ui/Card';
|
||||||
import { useContext } from 'react';
|
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const { data: me, isLoading } = useContext(UserContext);
|
const { data: me, isLoading } = useContext(UserContext);
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full w-full flex-col space-y-4">
|
<section className="w-full flex-col space-y-4">
|
||||||
<div className="w-full">
|
<header>
|
||||||
<section className="flex flex-col space-y-4">
|
<h1 className="text-xl font-semibold">Tableau de bord</h1>
|
||||||
<header>
|
<p className="text-highlight-secondary">Ceci est la page d'accueil du dashboard</p>
|
||||||
<h3 className="text-xl font-semibold">Tableau de bord</h3>
|
</header>
|
||||||
<p className="text-muted">Ceci est la page d'accueil du dashboard</p>
|
<main className="flex-col space-y-4">
|
||||||
</header>
|
<div className="w-full flex-col justify-between space-x-0 space-y-4 md:flex md:flex-row md:space-x-6 md:space-y-0">
|
||||||
<main className="flex-col justify-between space-x-0 space-y-4 md:flex md:flex-row md:space-x-6 md:space-y-0">
|
<Card
|
||||||
<Card
|
isLoading={isLoading}
|
||||||
isLoading={isLoading}
|
icon="pie-chart-line"
|
||||||
icon="pie-chart-line"
|
title="Puzzles résolus"
|
||||||
title="Puzzles résolus"
|
data={me?.completions ?? 0}
|
||||||
data={me?.completions || 0}
|
link="/dashboard/puzzles"
|
||||||
/>
|
|
||||||
<Card
|
|
||||||
isLoading={isLoading}
|
|
||||||
icon="award-line"
|
|
||||||
title="Badges obtenus"
|
|
||||||
data={me?.badges?.length || 'Aucun'}
|
|
||||||
/>
|
|
||||||
<Card
|
|
||||||
isLoading={isLoading}
|
|
||||||
icon="bar-chart-line"
|
|
||||||
title="Rang actuel"
|
|
||||||
data={me?.rank || 'Non classé'}
|
|
||||||
/>
|
|
||||||
</main>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
<div className="h-full w-full flex-col justify-between space-x-0 space-y-4 md:flex md:flex-row md:space-x-6 md:space-y-0">
|
|
||||||
<section className="flex h-full w-full flex-col space-y-4">
|
|
||||||
<header>
|
|
||||||
<h3 className="text-xl font-semibold">Guides</h3>
|
|
||||||
</header>
|
|
||||||
<main className="h-full w-full flex-col justify-between space-x-0 space-y-4 rounded-lg border border-highlight-primary bg-primary-700 md:flex md:flex-row md:space-x-6 md:space-y-0">
|
|
||||||
Work in progress
|
|
||||||
</main>
|
|
||||||
</section>
|
|
||||||
<section className="flex h-full w-full flex-col space-y-4">
|
|
||||||
<header>
|
|
||||||
<h3 className="text-xl font-semibold">Historiques</h3>
|
|
||||||
</header>
|
|
||||||
<main className="h-full w-full flex-col justify-between space-x-0 space-y-4 rounded-lg border border-highlight-primary bg-primary-700 md:flex md:flex-row md:space-x-6 md:space-y-0">
|
|
||||||
Work in progress
|
|
||||||
</main>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
{/* TODO fix ça c'est pas responsive */}
|
|
||||||
{/* <section className="flex-col space-y-4">
|
|
||||||
<header>
|
|
||||||
<h3 className="text-xl font-semibold">Statistiques</h3>
|
|
||||||
<p className="text-muted">Ceci est la page d'accueil du dashboard</p>
|
|
||||||
</header>
|
|
||||||
<main className="flex-col justify-between space-x-0 space-y-4 sm:flex sm:flex-row sm:space-x-6 sm:space-y-0">
|
|
||||||
<CardTable
|
|
||||||
puzzles={[
|
|
||||||
{ name: 'Jour 0 | Save Conway Gadgetski', id: 1', content: '' },
|
|
||||||
{ name: 'Jour 1 | Next', id: 2', content: '' },
|
|
||||||
{ name: 'Jour 2 | Previous', id: '3', content: '' },
|
|
||||||
{ name: 'Jour 3 | Next 1 loop', id: '4', content: '' },
|
|
||||||
{ name: 'Jour 4 | Next no loop + recursion', id: '5', content: '' },
|
|
||||||
{ name: 'Jour 5 | N first rows', id: '6', content: '' },
|
|
||||||
{ name: 'Week-end | Game of Life', id: '7', content: '' },
|
|
||||||
{ name: 'Jour 0 | Save Conway Gadgetski', id: '1', content: '' },
|
|
||||||
{ name: 'Jour 1 | Next', id: '2', content: '' },
|
|
||||||
{ name: 'Jour 2 | Previous', id: '3', content: '' },
|
|
||||||
{ name: 'Jour 3 | Next 1 loop', id: '4', content: '' },
|
|
||||||
{ name: 'Jour 4 | Next no loop + recursion', id: '5', content: '' },
|
|
||||||
{ name: 'Jour 5 | N first rows', id: '6', content: '' },
|
|
||||||
{ name: 'Week-end | Game of Life', id: '7', content: '' },
|
|
||||||
{ name: 'Jour 0 | Save Conway Gadgetski', id: '1', content: '' },
|
|
||||||
{ name: 'Jour 1 | Next', id: '2', content: '' },
|
|
||||||
{ name: 'Jour 2 | Previous', id: '3', content: '' },
|
|
||||||
{ name: 'Jour 3 | Next 1 loop', id: '4', content: '' },
|
|
||||||
{ name: 'Jour 4 | Next no loop + recursion', id: '5', content: '' },
|
|
||||||
{ name: 'Jour 5 | N first rows', id: '6', content: '' },
|
|
||||||
{ name: 'Week-end | Game of Life', id: '7', content: '' }
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
<CardTable
|
<Card
|
||||||
puzzles={[
|
isLoading={isLoading}
|
||||||
{ name: 'Jour 0 | Save Conway Gadgetski', id: '1', content: '' },
|
icon="award-line"
|
||||||
{ name: 'Jour 1 | Next', id: '2', content: '' },
|
title="Badges obtenus"
|
||||||
{ name: 'Jour 2 | Previous', id: '3', content: '' },
|
data={me?.badges?.length ?? 'Aucun'}
|
||||||
{ name: 'Jour 3 | Next 1 loop', id: '4', content: '' },
|
link="/dashboard/badges"
|
||||||
{ name: 'Jour 4 | Next no loop + recursion', id: '5', content: '' },
|
|
||||||
{ name: 'Jour 5 | N first rows', id: '6', content: '' },
|
|
||||||
{ name: 'Week-end | Game of Life', id: '7', content: '' }
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
</main>
|
<Card
|
||||||
</section> */}
|
isLoading={isLoading}
|
||||||
</div>
|
icon="bar-chart-line"
|
||||||
|
title="Rang actuel"
|
||||||
|
data={me?.rank ?? 'Non classé'}
|
||||||
|
link="/dashboard/leaderboard"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-1 gap-4">
|
||||||
|
<div className="flex flex-col space-y-4">
|
||||||
|
<header>
|
||||||
|
<h2 className="text-lg font-semibold">Derniers puzzles</h2>
|
||||||
|
<p className="text-highlight-secondary">
|
||||||
|
Voici les derniers puzzles que vous avez résolus ou essayer de résoudres
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
<div className="h-full max-h-96 overflow-y-scroll rounded-lg border-2 border-highlight-primary bg-primary-700 p-4 shadow-md">
|
||||||
|
<ul className="flex flex-col space-y-2">
|
||||||
|
{me?.completionsList && me.completionsList.length > 0 ? (
|
||||||
|
me?.completionsList
|
||||||
|
.sort(
|
||||||
|
(a, b) =>
|
||||||
|
a.score - b.score ||
|
||||||
|
a.tries - b.tries ||
|
||||||
|
a.puzzleName.localeCompare(b.puzzleName)
|
||||||
|
)
|
||||||
|
.map((completion, key) => {
|
||||||
|
return (
|
||||||
|
<li key={key} className="flex justify-between space-x-2">
|
||||||
|
<div className="flex items-center space-x-4">
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<span className="text-lg">{completion.puzzleName}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-4">
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<span className="text-sm font-semibold">
|
||||||
|
Essai{completion.tries > 1 ? 's' : ''}
|
||||||
|
</span>
|
||||||
|
<span className="text-right text-lg text-highlight-secondary">
|
||||||
|
{completion.tries}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<span className="text-sm font-semibold">Score</span>
|
||||||
|
<span className="text-right text-lg text-highlight-secondary">
|
||||||
|
{completion.score}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
<li className="m-auto flex items-center justify-center">
|
||||||
|
<span className="text-lg text-highlight-secondary">
|
||||||
|
{isLoading ? 'Chargement en cours...' : 'Aucun puzzles'}
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ export default function Page() {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
router.push('/');
|
router.push('/');
|
||||||
}, []);
|
}, [router]);
|
||||||
|
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
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;
|
|
|
@ -20,33 +20,33 @@ export type NavItem = {
|
||||||
* @type {NavItem[]}
|
* @type {NavItem[]}
|
||||||
*/
|
*/
|
||||||
export const navItems: NavItem[] = [
|
export const navItems: NavItem[] = [
|
||||||
// {
|
{
|
||||||
// name: 'Dashboard',
|
name: 'Dashboard',
|
||||||
// slug: '',
|
slug: 'dashboard',
|
||||||
// icon: 'dashboard-line',
|
icon: 'dashboard-line',
|
||||||
// disabled: false
|
disabled: false
|
||||||
// },
|
},
|
||||||
{
|
{
|
||||||
name: 'Classement',
|
name: 'Classement',
|
||||||
slug: 'leaderboard',
|
slug: 'dashboard/leaderboard',
|
||||||
icon: 'line-chart-line',
|
icon: 'line-chart-line',
|
||||||
disabled: false
|
disabled: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Puzzles',
|
name: 'Puzzles',
|
||||||
slug: 'puzzles',
|
slug: 'dashboard/puzzles',
|
||||||
icon: 'code-s-slash-line',
|
icon: 'code-s-slash-line',
|
||||||
disabled: false
|
disabled: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Badges',
|
name: 'Badges',
|
||||||
slug: 'badges',
|
slug: 'dashboard/badges',
|
||||||
icon: 'award-fill',
|
icon: 'award-fill',
|
||||||
disabled: false
|
disabled: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Paramètres',
|
name: 'Paramètres',
|
||||||
slug: 'settings',
|
slug: 'dashboard/settings',
|
||||||
icon: 'equalizer-line',
|
icon: 'equalizer-line',
|
||||||
disabled: false
|
disabled: false
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,7 @@ export type Player = {
|
||||||
tries: number;
|
tries: number;
|
||||||
completions: number;
|
completions: number;
|
||||||
rank: number;
|
rank: number;
|
||||||
|
completionsList: Completion[];
|
||||||
badges: Badge[] | null;
|
badges: Badge[] | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -46,3 +47,9 @@ export type Badge = {
|
||||||
level: number;
|
level: number;
|
||||||
logo?: string;
|
logo?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type Completion = {
|
||||||
|
puzzleName: string;
|
||||||
|
tries: number;
|
||||||
|
score: number;
|
||||||
|
};
|
||||||
|
|
27
ui/Card.tsx
27
ui/Card.tsx
|
@ -1,23 +1,28 @@
|
||||||
|
import { getURL } from '@/lib/utils';
|
||||||
|
|
||||||
|
import AppLink from './AppLink';
|
||||||
import Icon from './Icon';
|
import Icon from './Icon';
|
||||||
|
|
||||||
export default function Card({
|
export default function Card({
|
||||||
isLoading,
|
isLoading,
|
||||||
icon,
|
icon,
|
||||||
title,
|
title,
|
||||||
data
|
data,
|
||||||
|
link
|
||||||
}: {
|
}: {
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
icon: string;
|
icon: string;
|
||||||
title: string;
|
title: string;
|
||||||
data: any;
|
data: any;
|
||||||
|
link?: string;
|
||||||
}) {
|
}) {
|
||||||
if (isLoading)
|
if (isLoading)
|
||||||
return (
|
return (
|
||||||
<div className="flex w-full items-center space-x-4 rounded-lg border-2 border-highlight-primary bg-primary-700 p-4 shadow-md">
|
<div className="flex w-full items-center space-x-4 rounded-lg border-2 border-highlight-primary bg-primary-700 p-4 shadow-md">
|
||||||
<Icon name={icon} className="text-2xl text-muted" />
|
<Icon name={icon} className="text-2xl text-muted" />
|
||||||
<div className="flex flex-col space-y-4">
|
<div className="flex flex-col space-y-4">
|
||||||
<span className="h-4 w-32 animate-pulse rounded bg-highlight-primary" />
|
<span className="h-[18px] w-32 animate-pulse rounded bg-highlight-primary" />
|
||||||
<span className="h-4 w-24 animate-pulse rounded bg-highlight-primary" />
|
<span className="h-[18px] w-24 animate-pulse rounded bg-highlight-primary" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -25,9 +30,19 @@ export default function Card({
|
||||||
return (
|
return (
|
||||||
<div className="flex w-full items-center space-x-4 rounded-lg border-2 border-highlight-primary bg-primary-700 p-4 shadow-md">
|
<div className="flex w-full items-center space-x-4 rounded-lg border-2 border-highlight-primary bg-primary-700 p-4 shadow-md">
|
||||||
<Icon name={icon} className="text-2xl text-muted" />
|
<Icon name={icon} className="text-2xl text-muted" />
|
||||||
<div className="flex flex-col">
|
<div className="flex w-full items-center justify-between">
|
||||||
<h3 className="text-xl font-semibold">{data}</h3>
|
<div className="flex-col">
|
||||||
<p className="text-muted">{title}</p>
|
<h2 className="text-xl font-semibold">{data}</h2>
|
||||||
|
<p className="text-muted">{title}</p>
|
||||||
|
</div>
|
||||||
|
{link && (
|
||||||
|
<AppLink
|
||||||
|
className="text-highlight-secondary transition-colors duration-150 hover:text-brand"
|
||||||
|
href={getURL(link)}
|
||||||
|
>
|
||||||
|
<Icon name="arrow-right-line" />
|
||||||
|
</AppLink>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,45 +0,0 @@
|
||||||
import { type Puzzle } from '@/lib/puzzles';
|
|
||||||
|
|
||||||
import AppLink from './AppLink';
|
|
||||||
|
|
||||||
export default function CardTable({ puzzles }: { puzzles: Puzzle[] }) {
|
|
||||||
return (
|
|
||||||
<></>
|
|
||||||
// <div className="relative flex h-96 w-full overflow-scroll">
|
|
||||||
// <table className="w-full table-auto border-collapse rounded-lg border-2 border-highlight-primary bg-primary-700 text-left text-sm text-muted shadow-md">
|
|
||||||
// {/* <thead className="z-1 sticky -top-1 bg-primary-600 text-xs uppercase text-white ">
|
|
||||||
// <tr>
|
|
||||||
// <th className="px-6 py-3">Exercice</th>
|
|
||||||
// <th className="px-6 py-3">Tentative</th>
|
|
||||||
// <th className="px-6 py-3">Score</th>
|
|
||||||
// <th className="px-6 py-3">Dernier essai</th>
|
|
||||||
// <th className="px-6 py-3">
|
|
||||||
// <span className="sr-only">Reprendre</span>
|
|
||||||
// </th>
|
|
||||||
// </tr>
|
|
||||||
// </thead> */}
|
|
||||||
// <tbody className="overflow-scroll">
|
|
||||||
// {puzzles.length &&
|
|
||||||
// puzzles.map((puzzle) => (
|
|
||||||
// <tr key={puzzle.id} className="bg-primary-700 hover:bg-primary-800 ">
|
|
||||||
// <th scope="row" className="whitespace-nowrap px-6 py-4 font-medium text-white">
|
|
||||||
// {puzzle.name}
|
|
||||||
// </th>
|
|
||||||
// <td className="px-6 py-4">30</td>
|
|
||||||
// <td className="px-6 py-4">300</td>
|
|
||||||
// <td className="px-6 py-4">10/10/2010</td>
|
|
||||||
// <td className="px-6 py-4 text-right">
|
|
||||||
// <AppLink
|
|
||||||
// href={`dashboard/puzzles/${puzzle.id}`}
|
|
||||||
// className="font-medium text-brand hover:underline"
|
|
||||||
// >
|
|
||||||
// Reprendre
|
|
||||||
// </AppLink>
|
|
||||||
// </td>
|
|
||||||
// </tr>
|
|
||||||
// ))}
|
|
||||||
// </tbody>
|
|
||||||
// </table>
|
|
||||||
// </div>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,37 +1,40 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { AnimatePresence, motion } from 'framer-motion';
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
import useSWRSubscription, { type SWRSubscription } from 'swr/subscription';
|
|
||||||
|
|
||||||
import { type ScoreEvent } from '@/lib/leaderboard';
|
import { useLeaderboardEvent } from '@/lib/hooks/use-leaderboard';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import Podium from '@/ui/events/podium/Podium';
|
import Podium from '@/ui/events/podium/Podium';
|
||||||
import { Timer } from './Timer';
|
import { Timer } from './Timer';
|
||||||
|
import { type ScoreEvent } from '@/lib/leaderboard';
|
||||||
|
import useSWRSubscription, { type SWRSubscription } from 'swr/subscription';
|
||||||
|
|
||||||
const SCORE_COLORS = ['text-yellow-400', 'text-gray-400', 'text-orange-400'];
|
const SCORE_COLORS = ['text-yellow-400', 'text-gray-400', 'text-orange-400'];
|
||||||
|
|
||||||
export default function Leaderboard() {
|
export default function Leaderboard({ token }: { token: string }) {
|
||||||
// TODO CHANGER CECI
|
// TODO CHANGER CECI
|
||||||
const CHAPITRE_EVENT = 1;
|
const CHAPITRE_EVENT = 1;
|
||||||
|
|
||||||
const subscription: SWRSubscription<string, ScoreEvent, Error> = (key, { next }) => {
|
// const subscription: SWRSubscription<string, ScoreEvent, Error> = (key, { next }) => {
|
||||||
const socket = new WebSocket(key);
|
// const socket = new WebSocket(key);
|
||||||
|
|
||||||
socket.addEventListener('message', (event) => {
|
// socket.addEventListener('message', (event) => {
|
||||||
next(null, JSON.parse(event.data));
|
// next(null, JSON.parse(event.data));
|
||||||
});
|
// });
|
||||||
|
|
||||||
socket.addEventListener('error', (event) => {
|
// socket.addEventListener('error', (event) => {
|
||||||
console.error(event);
|
// console.error(event);
|
||||||
});
|
// });
|
||||||
|
|
||||||
return () => socket.close();
|
// return () => socket.close();
|
||||||
};
|
// };
|
||||||
|
|
||||||
const { data } = useSWRSubscription(
|
// 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
|
// subscription
|
||||||
);
|
// );
|
||||||
|
|
||||||
|
const { data, isLoading } = useLeaderboardEvent({ token: token, id: CHAPITRE_EVENT });
|
||||||
|
|
||||||
const scores = [data?.groups]
|
const scores = [data?.groups]
|
||||||
.flat()
|
.flat()
|
||||||
|
@ -60,8 +63,8 @@ export default function Leaderboard() {
|
||||||
const tries = group.players.reduce((a, b) => a + b.tries, 0) || 0;
|
const tries = group.players.reduce((a, b) => a + b.tries, 0) || 0;
|
||||||
return (
|
return (
|
||||||
<motion.li
|
<motion.li
|
||||||
layout
|
|
||||||
initial={{ opacity: 0, y: -10 }}
|
initial={{ opacity: 0, y: -10 }}
|
||||||
|
// @ts-ignore TODO Je sais pas c'est quoi cette merde
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
exit={{ opacity: 0, y: -10 }}
|
exit={{ opacity: 0, y: -10 }}
|
||||||
transition={{ duration: 0.5 }}
|
transition={{ duration: 0.5 }}
|
||||||
|
@ -69,13 +72,18 @@ export default function Leaderboard() {
|
||||||
className="flex justify-between space-x-2"
|
className="flex justify-between space-x-2"
|
||||||
>
|
>
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-center space-x-4">
|
||||||
<span className={cn('font-semibold', SCORE_COLORS[group.rank - 1])}>
|
<span
|
||||||
|
className={cn(
|
||||||
|
'font-semibold text-highlight-secondary',
|
||||||
|
SCORE_COLORS[group.rank - 1]
|
||||||
|
)}
|
||||||
|
>
|
||||||
{group.rank}
|
{group.rank}
|
||||||
</span>
|
</span>
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<div className="flex flex-col gap-x-2 sm:flex-row sm:items-center">
|
<div className="flex flex-col gap-x-2 sm:flex-row sm:items-center">
|
||||||
<span className="text-lg">{group.name}</span>
|
<span className="text-lg">{group.name}</span>
|
||||||
<span className="text-sm text-muted">
|
<span className="text-sm text-highlight-secondary">
|
||||||
{group.players && group.players.length > 1
|
{group.players && group.players.length > 1
|
||||||
? group.players
|
? group.players
|
||||||
.map((player) => player.pseudo || 'Anonyme')
|
.map((player) => player.pseudo || 'Anonyme')
|
||||||
|
@ -89,15 +97,15 @@ export default function Leaderboard() {
|
||||||
<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">Puzzle{puzzles > 1 ? 's' : ''}</span>
|
<span className="text-sm font-semibold">Puzzle{puzzles > 1 ? 's' : ''}</span>
|
||||||
<span className="text-lg text-muted">{puzzles}</span>
|
<span className="text-lg text-highlight-secondary">{puzzles}</span>
|
||||||
</div> */}
|
</div> */}
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<span className="text-sm font-semibold">Essai{tries > 1 ? 's' : ''}</span>
|
<span className="text-sm font-semibold">Essai{tries > 1 ? 's' : ''}</span>
|
||||||
<span className="text-lg text-muted">{tries}</span>
|
<span className="text-lg text-highlight-secondary">{tries}</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-highlight-secondary">
|
||||||
{group.players.reduce((a, b) => a + b.score, 0)}
|
{group.players.reduce((a, b) => a + b.score, 0)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -257,14 +257,14 @@ function PuzzleProp({ puzzle, chapter }: { puzzle: Puzzle; chapter: Chapter }) {
|
||||||
{puzzle.tags
|
{puzzle.tags
|
||||||
.filter((tag) => !['easy', 'medium', 'hard'].includes(tag.name))
|
.filter((tag) => !['easy', 'medium', 'hard'].includes(tag.name))
|
||||||
.map((tag, i) => (
|
.map((tag, i) => (
|
||||||
<span key={i} className="inline-block rounded-md bg-primary-900 px-2 py-1">
|
<span key={i} className="inline-block rounded-md bg-primary-800 px-2 py-1">
|
||||||
{tag.name}
|
{tag.name}
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<Icon
|
<Icon
|
||||||
className="-translate-x-2 transform-gpu duration-300 group-hover:translate-x-0"
|
className="-translate-x-2 transform-gpu text-highlight-secondary duration-300 group-hover:translate-x-0 group-hover:text-brand"
|
||||||
name="arrow-right-line"
|
name="arrow-right-line"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -292,7 +292,7 @@ function PuzzleProp({ puzzle, chapter }: { puzzle: Puzzle; chapter: Chapter }) {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<Icon
|
<Icon
|
||||||
className="-translate-x-2 transform-gpu duration-300 group-hover:translate-x-0"
|
className="-translate-x-2 transform-gpu text-highlight-secondary duration-300 group-hover:translate-x-0"
|
||||||
name="arrow-right-line"
|
name="arrow-right-line"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -13,7 +13,7 @@ export default function ToHTML({ data, className }: { data: string; className?:
|
||||||
a: ({ node, ...props }) => (
|
a: ({ node, ...props }) => (
|
||||||
<a
|
<a
|
||||||
{...props}
|
{...props}
|
||||||
className="inline text-brand-accent hover:text-brand hover:underline"
|
className="inline text-brand transition-colors duration-150 hover:text-brand-accent hover:underline"
|
||||||
// MAKE thIS SHIT DOWNLOADABLE
|
// MAKE thIS SHIT DOWNLOADABLE
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener"
|
rel="noopener"
|
||||||
|
|
|
@ -37,6 +37,7 @@ export default function UserAuthForm() {
|
||||||
avatar: ''
|
avatar: ''
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const pathname = usePathname()!;
|
const pathname = usePathname()!;
|
||||||
|
@ -101,6 +102,7 @@ export default function UserAuthForm() {
|
||||||
<form
|
<form
|
||||||
className="flex w-52 flex-col justify-center space-y-4 sm:w-72"
|
className="flex w-52 flex-col justify-center space-y-4 sm:w-72"
|
||||||
onSubmit={handleSubmit(onSubmit)}
|
onSubmit={handleSubmit(onSubmit)}
|
||||||
|
method="POST"
|
||||||
>
|
>
|
||||||
{!isSignIn && (
|
{!isSignIn && (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -90,13 +90,12 @@ function NavItem({
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
}) {
|
}) {
|
||||||
const segment = useSelectedLayoutSegment();
|
const segment = useSelectedLayoutSegment();
|
||||||
const pathname = segment?.split('/').pop() || '';
|
|
||||||
const isHttp = item.slug.includes('http');
|
const isHttp = item.slug.includes('http');
|
||||||
const isActive = pathname === item.slug || (item.slug === '' && !segment);
|
const pathname = item.slug.split('/').pop();
|
||||||
|
const isActive = segment === pathname || (segment === null && pathname === 'dashboard');
|
||||||
return (
|
return (
|
||||||
<AppLink
|
<AppLink
|
||||||
aria-disabled={item.disabled}
|
href={isHttp ? item.slug : `/${item.slug}`}
|
||||||
href={isHttp ? item.slug : `dashboard/${item.slug}`}
|
|
||||||
target={isHttp ? '_blank' : undefined}
|
target={isHttp ? '_blank' : undefined}
|
||||||
rel={isHttp ? 'noopener noreferrer' : undefined}
|
rel={isHttp ? 'noopener noreferrer' : undefined}
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|
Loading…
Add table
Reference in a new issue