Merge pull request #11 from Peer-at-Code/feat/dashboard-auth
feat/dashboard-auth
This commit is contained in:
commit
477d628b6c
27 changed files with 837 additions and 143 deletions
|
@ -1,4 +1,5 @@
|
||||||
import Leaderboard from '@/ui/Leaderboard';
|
import Leaderboard from '@/ui/Leaderboard';
|
||||||
|
import { cookies } from 'next/headers';
|
||||||
|
|
||||||
export const metadata = {
|
export const metadata = {
|
||||||
title: 'Tableau des scores - Peer-at Code',
|
title: 'Tableau des scores - Peer-at Code',
|
||||||
|
@ -6,5 +7,6 @@ export const metadata = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export default async function Page() {
|
export default async function Page() {
|
||||||
return <Leaderboard />;
|
const token = cookies().get('token')?.value;
|
||||||
|
return <Leaderboard token={token!} />;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
import Card from '@/ui/Card';
|
'use client';
|
||||||
|
|
||||||
export const metadata = {
|
import { useMe } from '@/lib/hooks/use-players';
|
||||||
title: 'Dashboard - Peer-at Code'
|
import Card from '@/ui/Card';
|
||||||
};
|
import cookies from 'js-cookie';
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
|
const token = cookies.get('token');
|
||||||
|
const { data: me, isLoading } = useMe({ token: token! });
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full w-full flex-col space-y-4">
|
<div className="flex h-full w-full flex-col space-y-4">
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
|
@ -14,9 +16,24 @@ export default function Page() {
|
||||||
<p className="text-muted">Ceci est la page d'accueil du dashboard</p>
|
<p className="text-muted">Ceci est la page d'accueil du dashboard</p>
|
||||||
</header>
|
</header>
|
||||||
<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">
|
<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 icon="pie-chart-line" title="46" data="Puzzles" />
|
<Card
|
||||||
<Card icon="award-line" title="3" data="Badges" />
|
isLoading={isLoading}
|
||||||
<Card icon="bar-chart-line" title="10 ème" data="Classement" />
|
icon="pie-chart-line"
|
||||||
|
title="Puzzles"
|
||||||
|
data={me?.completions}
|
||||||
|
/>
|
||||||
|
<Card
|
||||||
|
isLoading={isLoading}
|
||||||
|
icon="award-line"
|
||||||
|
title="Badges"
|
||||||
|
data={me?.badges || 'Aucun'}
|
||||||
|
/>
|
||||||
|
<Card
|
||||||
|
isLoading={isLoading}
|
||||||
|
icon="bar-chart-line"
|
||||||
|
title="Score (classement plus tard)"
|
||||||
|
data={me?.score}
|
||||||
|
/>
|
||||||
</main>
|
</main>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
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 type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
|
@ -5,16 +6,26 @@ 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> {
|
||||||
const { id } = params;
|
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` };
|
return { title: `${puzzle.name} - Peer-at Code` };
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function Page({ params }: { params: { id: number } }) {
|
export default async function Page({ params }: { params: { id: number } }) {
|
||||||
const { id } = params;
|
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) {
|
if (!puzzle) {
|
||||||
notFound();
|
notFound();
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { cookies } from 'next/headers';
|
||||||
|
|
||||||
import Puzzles from '@/ui/Puzzles';
|
import Puzzles from '@/ui/Puzzles';
|
||||||
|
|
||||||
export const metadata = {
|
export const metadata = {
|
||||||
|
@ -5,9 +7,12 @@ export const metadata = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export default async function Page() {
|
export default async function Page() {
|
||||||
|
const cookieStore = cookies();
|
||||||
|
const token = cookieStore.get('token')?.value;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col space-y-6">
|
<div className="flex flex-col space-y-6">
|
||||||
<Puzzles />
|
<Puzzles token={token!} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@ import Console from '@/ui/Console';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
// TODO: Fix this (image)
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="flex h-screen w-full">
|
<div className="flex h-screen w-full">
|
||||||
|
|
12
lib/fetcher.ts
Normal file
12
lib/fetcher.ts
Normal file
|
@ -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;
|
|
@ -1,6 +1,6 @@
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
import { getScores } from '../leaderboard';
|
import { getScores } from '../leaderboard';
|
||||||
|
|
||||||
export function useLeaderboard() {
|
export function useLeaderboard({ token }: { token: string }) {
|
||||||
return useSWR('leaderboard', () => getScores());
|
return useSWR('leaderboard', () => getScores({ token }));
|
||||||
}
|
}
|
||||||
|
|
12
lib/hooks/use-players.ts
Normal file
12
lib/hooks/use-players.ts
Normal file
|
@ -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 }));
|
||||||
|
}
|
|
@ -2,14 +2,14 @@ import useSWR from 'swr';
|
||||||
|
|
||||||
import { getChapters, getPuzzle, getPuzzles } from '../puzzles';
|
import { getChapters, getPuzzle, getPuzzles } from '../puzzles';
|
||||||
|
|
||||||
export function useChapters() {
|
export function useChapters({ token }: { token: string }) {
|
||||||
return useSWR('chapters', () => getChapters());
|
return useSWR('chapters', () => getChapters({ token }));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function usePuzzles() {
|
export function usePuzzles({ token }: { token: string }) {
|
||||||
return useSWR('puzzles', () => getPuzzles());
|
return useSWR('puzzles', () => getPuzzles({ token }));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function usePuzzle(id: number) {
|
export function usePuzzle({ token, id }: { token: string; id: number }) {
|
||||||
return useSWR(`puzzles/${id}`, () => getPuzzle(id));
|
return useSWR(`puzzles/${id}`, () => getPuzzle({ token, id }));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import axios from 'axios';
|
import fetcher from './fetcher';
|
||||||
|
|
||||||
export const getScores = async (): Promise<Score[]> => {
|
export const getScores = async ({ token }: { token: string }): Promise<Score[]> => {
|
||||||
const { data, status } = await axios.get(`${process.env.NEXT_PUBLIC_API_URL}/leaderboard`, {
|
const { data, status } = await fetcher.get(`/leaderboard`, {
|
||||||
insecureHTTPParser: true
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const scores = data;
|
const scores = data;
|
||||||
|
@ -24,4 +26,5 @@ export type Score = {
|
||||||
completions: number;
|
completions: number;
|
||||||
pseudo: string;
|
pseudo: string;
|
||||||
group: string;
|
group: string;
|
||||||
|
avatar: string;
|
||||||
};
|
};
|
||||||
|
|
41
lib/players.ts
Normal file
41
lib/players.ts
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import fetcher from './fetcher';
|
||||||
|
|
||||||
|
export const getPlayer = async ({
|
||||||
|
token,
|
||||||
|
username = ''
|
||||||
|
}: {
|
||||||
|
token: string;
|
||||||
|
username?: string;
|
||||||
|
}): Promise<Player | null> => {
|
||||||
|
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[];
|
||||||
|
};
|
|
@ -1,8 +1,10 @@
|
||||||
import axios from 'axios';
|
import fetcher from './fetcher';
|
||||||
|
|
||||||
export const getChapters = async (): Promise<Chapter[]> => {
|
export const getChapters = async ({ token }: { token: string }): Promise<Chapter[]> => {
|
||||||
const { data, status } = await axios.get(`${process.env.NEXT_PUBLIC_API_URL}/chapters`, {
|
const { data, status } = await fetcher.get(`/chapters`, {
|
||||||
insecureHTTPParser: true
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let chapters = data;
|
let chapters = data;
|
||||||
|
@ -20,36 +22,44 @@ export const getChapters = async (): Promise<Chapter[]> => {
|
||||||
return chapters as Chapter[];
|
return chapters as Chapter[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getPuzzlesByChapter = async (chapitre: number): Promise<Chapter | null> => {
|
export const getChapter = async ({
|
||||||
const { data, status } = await axios.get(
|
token,
|
||||||
`${process.env.NEXT_PUBLIC_API_URL}/chapter/${chapitre}`,
|
id
|
||||||
{ insecureHTTPParser: true }
|
}: {
|
||||||
);
|
token: string;
|
||||||
|
id: number;
|
||||||
|
}): Promise<Chapter | null> => {
|
||||||
|
const { data, status } = await fetcher.get(`/chapter/${id}`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const { puzzles, name, id } = data;
|
const chapter = data;
|
||||||
|
|
||||||
if (status !== 200) {
|
if (status !== 200) {
|
||||||
throw new Error('Failed to fetch puzzles');
|
throw new Error('Failed to fetch puzzles');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!puzzles) {
|
if (!chapter) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return chapter as Chapter;
|
||||||
name,
|
|
||||||
id,
|
|
||||||
puzzles
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getPuzzles = async (): Promise<{ chapters: Chapter[]; puzzles: Puzzle[] }> => {
|
export const getPuzzles = async ({
|
||||||
const chapters = await getChapters();
|
token
|
||||||
let puzzles: Puzzle[] = [];
|
}: {
|
||||||
|
token: string;
|
||||||
|
}): Promise<{ chapters: Chapter[]; puzzles: Puzzle[] }> => {
|
||||||
|
const chapters = await getChapters({ token });
|
||||||
|
const puzzles: Puzzle[] = [];
|
||||||
|
|
||||||
for (const chapter of chapters) {
|
for (const chapter of chapters) {
|
||||||
const puzzlesByChapter = await getPuzzlesByChapter(chapter.id);
|
const puzzlesByChapter = await getChapter({ token, id: chapter.id });
|
||||||
puzzles = [...puzzles, ...puzzlesByChapter!.puzzles];
|
if (!puzzlesByChapter?.puzzles) continue;
|
||||||
|
puzzles.push(...puzzlesByChapter!.puzzles);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -58,9 +68,11 @@ export const getPuzzles = async (): Promise<{ chapters: Chapter[]; puzzles: Puzz
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getPuzzle = async (id: number): Promise<Puzzle> => {
|
export const getPuzzle = async ({ token, id }: { token: string; id: number }): Promise<Puzzle> => {
|
||||||
const { data, status } = await axios.get(`${process.env.NEXT_PUBLIC_API_URL}/puzzle/${id}`, {
|
const { data, status } = await fetcher.get(`/puzzle/${id}`, {
|
||||||
insecureHTTPParser: true
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const puzzle = data;
|
const puzzle = data;
|
||||||
|
@ -77,8 +89,8 @@ export const getPuzzle = async (id: number): Promise<Puzzle> => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Puzzle = {
|
export type Puzzle = {
|
||||||
name: string;
|
|
||||||
id: number;
|
id: number;
|
||||||
|
name: string;
|
||||||
content: string;
|
content: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/Peer-at-Code/peer-at-code#readme",
|
"homepage": "https://github.com/Peer-at-Code/peer-at-code#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@radix-ui/react-popover": "^1.0.5",
|
||||||
"axios": "^1.3.4",
|
"axios": "^1.3.4",
|
||||||
"boring-avatars": "^1.7.0",
|
"boring-avatars": "^1.7.0",
|
||||||
"clsx": "^1.2.1",
|
"clsx": "^1.2.1",
|
||||||
|
|
393
pnpm-lock.yaml
generated
393
pnpm-lock.yaml
generated
|
@ -1,6 +1,7 @@
|
||||||
lockfileVersion: 5.4
|
lockfileVersion: 5.4
|
||||||
|
|
||||||
specifiers:
|
specifiers:
|
||||||
|
'@radix-ui/react-popover': ^1.0.5
|
||||||
'@tailwindcss/forms': ^0.5.3
|
'@tailwindcss/forms': ^0.5.3
|
||||||
'@types/js-cookie': ^3.0.3
|
'@types/js-cookie': ^3.0.3
|
||||||
'@types/node': 18.11.18
|
'@types/node': 18.11.18
|
||||||
|
@ -33,6 +34,7 @@ specifiers:
|
||||||
zod: ^3.20.2
|
zod: ^3.20.2
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@radix-ui/react-popover': 1.0.5_5ndqzdd6t4rivxsukjv3i3ak2q
|
||||||
axios: 1.3.4
|
axios: 1.3.4
|
||||||
boring-avatars: 1.7.0
|
boring-avatars: 1.7.0
|
||||||
clsx: 1.2.1
|
clsx: 1.2.1
|
||||||
|
@ -73,7 +75,6 @@ packages:
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
dependencies:
|
dependencies:
|
||||||
regenerator-runtime: 0.13.11
|
regenerator-runtime: 0.13.11
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@eslint/eslintrc/1.4.1:
|
/@eslint/eslintrc/1.4.1:
|
||||||
resolution: {integrity: sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==}
|
resolution: {integrity: sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==}
|
||||||
|
@ -92,6 +93,30 @@ packages:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
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:
|
/@humanwhocodes/config-array/0.11.8:
|
||||||
resolution: {integrity: sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==}
|
resolution: {integrity: sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==}
|
||||||
engines: {node: '>=10.10.0'}
|
engines: {node: '>=10.10.0'}
|
||||||
|
@ -272,6 +297,253 @@ packages:
|
||||||
tslib: 2.5.0
|
tslib: 2.5.0
|
||||||
dev: true
|
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:
|
/@rushstack/eslint-patch/1.2.0:
|
||||||
resolution: {integrity: sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==}
|
resolution: {integrity: sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==}
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -556,6 +828,13 @@ packages:
|
||||||
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
|
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
|
||||||
dev: true
|
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:
|
/aria-query/5.1.3:
|
||||||
resolution: {integrity: sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==}
|
resolution: {integrity: sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -885,6 +1164,10 @@ packages:
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/detect-node-es/1.1.0:
|
||||||
|
resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/detective/5.2.1:
|
/detective/5.2.1:
|
||||||
resolution: {integrity: sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==}
|
resolution: {integrity: sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==}
|
||||||
engines: {node: '>=0.8.0'}
|
engines: {node: '>=0.8.0'}
|
||||||
|
@ -1494,6 +1777,11 @@ packages:
|
||||||
has-symbols: 1.0.3
|
has-symbols: 1.0.3
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/get-nonce/1.0.1:
|
||||||
|
resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/get-symbol-description/1.0.0:
|
/get-symbol-description/1.0.0:
|
||||||
resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==}
|
resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
@ -1686,6 +1974,12 @@ packages:
|
||||||
side-channel: 1.0.4
|
side-channel: 1.0.4
|
||||||
dev: true
|
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:
|
/is-arguments/1.1.1:
|
||||||
resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==}
|
resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
@ -2701,6 +2995,58 @@ packages:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: false
|
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:
|
/react/18.2.0:
|
||||||
resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==}
|
resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
@ -2723,7 +3069,6 @@ packages:
|
||||||
|
|
||||||
/regenerator-runtime/0.13.11:
|
/regenerator-runtime/0.13.11:
|
||||||
resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==}
|
resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/regexp.prototype.flags/1.4.3:
|
/regexp.prototype.flags/1.4.3:
|
||||||
resolution: {integrity: sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==}
|
resolution: {integrity: sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==}
|
||||||
|
@ -3175,6 +3520,50 @@ packages:
|
||||||
punycode: 2.3.0
|
punycode: 2.3.0
|
||||||
dev: true
|
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:
|
/use-sync-external-store/1.2.0_react@18.2.0:
|
||||||
resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==}
|
resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
|
|
@ -6,4 +6,16 @@
|
||||||
.console {
|
.console {
|
||||||
@apply relative top-0.5 inline-block;
|
@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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,46 @@
|
||||||
import BoringAvatar from 'boring-avatars';
|
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 <BoringAvatar name={name} variant="beam" size={size} />;
|
return <BoringAvatar name={name} variant="beam" size={size} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function Base64Avatar({
|
||||||
|
name,
|
||||||
|
src,
|
||||||
|
className
|
||||||
|
}: {
|
||||||
|
name: string;
|
||||||
|
src: string;
|
||||||
|
className?: string;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Image
|
||||||
|
src={`data:image;base64,${src}`}
|
||||||
|
className={cn('rounded-full object-cover', className)}
|
||||||
|
width="0"
|
||||||
|
height="0"
|
||||||
|
alt={name}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function AvatarComponent({
|
||||||
|
name,
|
||||||
|
src,
|
||||||
|
size = 36,
|
||||||
|
className
|
||||||
|
}: {
|
||||||
|
name: string;
|
||||||
|
src: string;
|
||||||
|
size?: number;
|
||||||
|
className?: string;
|
||||||
|
}) {
|
||||||
|
return src ? (
|
||||||
|
<Base64Avatar name={name} src={src} className={className} />
|
||||||
|
) : (
|
||||||
|
<Avatar name={name} size={size} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -4,6 +4,12 @@ import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
export type Difficulty = 'easy' | 'medium' | 'hard';
|
export type Difficulty = 'easy' | 'medium' | 'hard';
|
||||||
|
|
||||||
|
export const DIFFICULTY = {
|
||||||
|
1: 'easy',
|
||||||
|
2: 'medium',
|
||||||
|
3: 'hard'
|
||||||
|
}
|
||||||
|
|
||||||
export default function Badge({
|
export default function Badge({
|
||||||
title,
|
title,
|
||||||
path,
|
path,
|
||||||
|
|
|
@ -10,11 +10,12 @@ const Button = forwardRef<
|
||||||
<button
|
<button
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
'inline-flex items-center justify-center rounded-md border-0 px-5 py-2.5 text-center text-sm font-medium outline-none transition-colors focus:outline-none focus:ring-0 disabled:opacity-50',
|
'inline-flex items-center justify-center rounded-md border-0 px-5 py-2.5 text-center text-sm font-medium outline-none transition-colors focus:outline-none focus:ring-0 disabled:cursor-not-allowed disabled:opacity-50',
|
||||||
{
|
{
|
||||||
'bg-highlight-primary hover:bg-highlight-primary/60': kind === 'default',
|
'bg-highlight-primary hover:bg-highlight-primary/60': kind === 'default',
|
||||||
'bg-error hover:bg-error/60': kind === 'danger',
|
'bg-error hover:bg-error/60': kind === 'danger',
|
||||||
'bg-gradient-to-tl from-brand to-brand-accent hover:bg-opacity-80': kind === 'brand'
|
'bg-gradient-to-tl from-brand to-brand-accent transition-opacity hover:opacity-90':
|
||||||
|
kind === 'brand'
|
||||||
},
|
},
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
|
|
27
ui/Card.tsx
27
ui/Card.tsx
|
@ -1,12 +1,33 @@
|
||||||
import Icon from './Icon';
|
import Icon from './Icon';
|
||||||
|
|
||||||
export default function Card({ icon, title, data }: { icon: string; title: string; data: string }) {
|
export default function Card({
|
||||||
|
isLoading,
|
||||||
|
icon,
|
||||||
|
title,
|
||||||
|
data
|
||||||
|
}: {
|
||||||
|
isLoading: boolean;
|
||||||
|
icon: string;
|
||||||
|
title: string;
|
||||||
|
data: any;
|
||||||
|
}) {
|
||||||
|
if (isLoading)
|
||||||
|
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">
|
||||||
|
<Icon name={icon} className="text-2xl text-muted" />
|
||||||
|
<div className="flex flex-col space-y-4">
|
||||||
|
<span className="h-4 w-32 animate-pulse rounded bg-highlight-primary" />
|
||||||
|
<span className="h-4 w-24 animate-pulse rounded bg-highlight-primary" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
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 flex-col">
|
||||||
<h3 className="text-xl font-semibold">{title}</h3>
|
<h3 className="text-xl font-semibold">{data}</h3>
|
||||||
<p className="text-muted">{data}</p>
|
<p className="text-muted">{title}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { forwardRef } from 'react';
|
import { forwardRef } from 'react';
|
||||||
import ErrorMessage from './ErrorMessage';
|
import ErrorMessage from './ErrorMessage';
|
||||||
|
import Icon from './Icon';
|
||||||
import Label from './Label';
|
import Label from './Label';
|
||||||
|
|
||||||
const Input = forwardRef<
|
const Input = forwardRef<
|
||||||
|
@ -14,7 +15,8 @@ const Input = forwardRef<
|
||||||
<Label label={label} description={description} required={props.required} className={className}>
|
<Label label={label} description={description} required={props.required} className={className}>
|
||||||
<input
|
<input
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className="w-full rounded-md border border-primary-600 bg-highlight-primary px-5 py-2.5 text-sm font-medium focus:border-brand focus:bg-primary-800 focus:outline-none disabled:opacity-50"
|
className="w-full rounded-md border-primary-600 bg-highlight-primary px-5 py-2.5 text-sm font-medium outline-0 focus:border-brand focus:bg-primary-800 focus:outline-none focus:ring-1 focus:ring-brand disabled:opacity-50"
|
||||||
|
autoComplete="off"
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
</Label>
|
</Label>
|
||||||
|
|
|
@ -2,52 +2,78 @@
|
||||||
|
|
||||||
import { useLeaderboard } from '@/lib/hooks/use-leaderboard';
|
import { useLeaderboard } from '@/lib/hooks/use-leaderboard';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import Avatar from './Avatar';
|
import { useMemo, useState } from 'react';
|
||||||
|
import AvatarComponent from './Avatar';
|
||||||
import Select from './Select';
|
import Select from './Select';
|
||||||
|
|
||||||
// TODO: Generate this later
|
|
||||||
const scoreColors = ['text-yellow-400', 'text-gray-400', 'text-orange-400'];
|
const scoreColors = ['text-yellow-400', 'text-gray-400', 'text-orange-400'];
|
||||||
|
|
||||||
// TODO: Generate this later
|
export default function Leaderboard({ token }: { token: string }) {
|
||||||
export const options = [
|
const { data, isLoading } = useLeaderboard({ token });
|
||||||
{ value: '1i1', title: '1I1' },
|
|
||||||
{ value: '1i2', title: '1I2' },
|
const [filter, setFilter] = useState('');
|
||||||
{ value: '1i3', title: '1I3' },
|
|
||||||
{ value: '1i4', title: '1I4' },
|
let options;
|
||||||
{ value: '1i5', title: '1I5' },
|
|
||||||
{ value: '1i6', title: '1I6' },
|
if (data) {
|
||||||
{ value: '1i7', title: '1I7' },
|
options = data
|
||||||
{ value: '1i8', title: '1I8' }
|
.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 (
|
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="sticky flex items-center justify-between">
|
||||||
<section className="flex flex-col space-y-4">
|
|
||||||
<header className="flex items-center justify-between">
|
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-xl font-semibold">Tableau des scores</h3>
|
<h3 className="text-xl font-semibold">Tableau des scores</h3>
|
||||||
<p className="hidden text-muted sm:block">
|
<p className="hidden text-muted sm:block">Suivez la progression des élèves en direct</p>
|
||||||
Suivez la progression des élèves en direct
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<Select className="w-28" options={options} />
|
{(filteredData && (
|
||||||
|
<Select
|
||||||
|
className="w-32"
|
||||||
|
options={options || []}
|
||||||
|
value={filter}
|
||||||
|
onChange={(event) => setFilter(event.target.value)}
|
||||||
|
/>
|
||||||
|
)) || (
|
||||||
|
<span
|
||||||
|
className="inline-block h-12 w-32 animate-pulse rounded-lg bg-primary-600"
|
||||||
|
style={{
|
||||||
|
animationDuration: '1s'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</header>
|
</header>
|
||||||
<main className="flex flex-col justify-between space-x-0 space-y-4 pb-4">
|
<main className="flex flex-col justify-between space-x-0 space-y-4 pb-4">
|
||||||
|
<ul className="flex flex-col space-y-2">
|
||||||
{(!isLoading &&
|
{(!isLoading &&
|
||||||
data?.map((score, key) => (
|
filteredData?.map((score, key) => (
|
||||||
<div key={key} className="flex flex-col space-y-2">
|
<li key={key} className="flex justify-between space-x-2">
|
||||||
<div 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', scoreColors[key])}>{key + 1}</span>
|
<span className={cn('font-semibold', scoreColors[key])}>{key + 1}</span>
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<Avatar name={score.pseudo} />
|
<AvatarComponent name={score.pseudo} src={score.avatar} className="h-9 w-9" />
|
||||||
|
<div className="flex flex-col gap-x-2 sm:flex-row sm:items-center">
|
||||||
<span className="text-lg">{score.pseudo}</span>
|
<span className="text-lg">{score.pseudo}</span>
|
||||||
<span className="text-sm text-muted">{score.group}</span>
|
<span className="text-sm text-muted">{score.group}</span>
|
||||||
</div>
|
</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">Puzzles</span>
|
<span className="text-sm font-semibold">Puzzles</span>
|
||||||
|
@ -58,8 +84,7 @@ export default function Leaderboard() {
|
||||||
<span className="text-lg text-muted">{score.score}</span>
|
<span className="text-lg text-muted">{score.score}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</li>
|
||||||
</div>
|
|
||||||
))) ||
|
))) ||
|
||||||
[...Array(20).keys()].map((i) => (
|
[...Array(20).keys()].map((i) => (
|
||||||
<span
|
<span
|
||||||
|
@ -71,9 +96,8 @@ export default function Leaderboard() {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
</ul>
|
||||||
</main>
|
</main>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
34
ui/Popover.tsx
Normal file
34
ui/Popover.tsx
Normal file
|
@ -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 (
|
||||||
|
<PopoverPrimitive.Root open={open} onOpenChange={onOpenChange}>
|
||||||
|
{/* {tooltip ? (
|
||||||
|
<Tooltip label={tooltip}>
|
||||||
|
<PopoverPrimitive.Trigger asChild>{trigger}</PopoverPrimitive.Trigger>
|
||||||
|
</Tooltip>
|
||||||
|
) : ( */}
|
||||||
|
<PopoverPrimitive.Trigger asChild>{trigger}</PopoverPrimitive.Trigger>
|
||||||
|
{/* )} */}
|
||||||
|
<PopoverPrimitive.Portal>
|
||||||
|
<PopoverPrimitive.Content
|
||||||
|
className="data-open:animate-fadeIn data-closed:animate-fadeOut z-20 rounded border border-highlight-primary bg-secondary"
|
||||||
|
sideOffset={5}
|
||||||
|
collisionPadding={8}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<PopoverPrimitive.Arrow className=" fill-white" />
|
||||||
|
</PopoverPrimitive.Content>
|
||||||
|
</PopoverPrimitive.Portal>
|
||||||
|
</PopoverPrimitive.Root>
|
||||||
|
);
|
||||||
|
}
|
|
@ -73,12 +73,14 @@ export default function Puzzle({ puzzle }: { puzzle: PuzzleType }) {
|
||||||
label="Réponse"
|
label="Réponse"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="12"
|
placeholder="12"
|
||||||
|
required
|
||||||
{...register('answer')}
|
{...register('answer')}
|
||||||
/>
|
/>
|
||||||
<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"
|
||||||
|
required
|
||||||
accept=".py,.js,.ts,.java,.rust,.c"
|
accept=".py,.js,.ts,.java,.rust,.c"
|
||||||
{...register('code_file')}
|
{...register('code_file')}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -4,8 +4,9 @@ import { usePuzzles } from '@/lib/hooks/use-puzzles';
|
||||||
import AppLink from './AppLink';
|
import AppLink from './AppLink';
|
||||||
import Icon from './Icon';
|
import Icon from './Icon';
|
||||||
|
|
||||||
export default function Puzzles() {
|
export default function Puzzles({ token }: { token: string }) {
|
||||||
const { data, isLoading } = usePuzzles();
|
const { data, isLoading } = usePuzzles({ token });
|
||||||
|
console.log(data);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{(!isLoading &&
|
{(!isLoading &&
|
||||||
|
@ -48,7 +49,7 @@ export default function Puzzles() {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<ul className="flex flex-col space-y-4">
|
<ul className="flex flex-col space-y-4">
|
||||||
{[...Array(7).keys()].map((j) => (
|
{[...Array(6).keys()].map((j) => (
|
||||||
<span
|
<span
|
||||||
key={j}
|
key={j}
|
||||||
className="inline-block h-14 animate-pulse rounded-lg bg-primary-600"
|
className="inline-block h-14 animate-pulse rounded-lg bg-primary-600"
|
||||||
|
|
|
@ -10,7 +10,8 @@ const Select = forwardRef<
|
||||||
error?: React.ReactNode;
|
error?: React.ReactNode;
|
||||||
description?: string;
|
description?: string;
|
||||||
options: { value: string; title: string }[];
|
options: { value: string; title: string }[];
|
||||||
} & Partial<ReturnType<UseFormRegister<any>>>
|
}
|
||||||
|
//& Partial<ReturnType<UseFormRegister<any>>></HTMLSelectElement>
|
||||||
>(({ options, className, label, description, error, ...props }, ref) => (
|
>(({ options, className, label, description, error, ...props }, ref) => (
|
||||||
<>
|
<>
|
||||||
<Label label={label} description={description} required={props.required} className={className}>
|
<Label label={label} description={description} required={props.required} className={className}>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import cookies from 'js-cookie';
|
import cookies from 'js-cookie';
|
||||||
import { usePathname, useRouter } from 'next/navigation';
|
import { usePathname, useRouter } from 'next/navigation';
|
||||||
import { useEffect } from 'react';
|
import { useState } from 'react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import AppLink from './AppLink';
|
import AppLink from './AppLink';
|
||||||
import Button from './Button';
|
import Button from './Button';
|
||||||
|
@ -37,13 +37,14 @@ export default function UserAuthForm() {
|
||||||
avatar: ''
|
avatar: ''
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const pathname = usePathname()!;
|
const pathname = usePathname()!;
|
||||||
const isSignIn = pathname.includes('sign-in');
|
const isSignIn = pathname.includes('sign-in');
|
||||||
const token = cookies.get('token');
|
|
||||||
|
|
||||||
async function onSubmit(data: FormData) {
|
async function onSubmit(data: FormData) {
|
||||||
|
setIsLoading(true);
|
||||||
|
|
||||||
const res = await fetch(
|
const res = await fetch(
|
||||||
`${process.env.NEXT_PUBLIC_API_URL}/${isSignIn ? 'login' : 'register'}`,
|
`${process.env.NEXT_PUBLIC_API_URL}/${isSignIn ? 'login' : 'register'}`,
|
||||||
{
|
{
|
||||||
|
@ -59,18 +60,21 @@ export default function UserAuthForm() {
|
||||||
type: 'manual',
|
type: 'manual',
|
||||||
message: "Nom d'utilisateur indisponible"
|
message: "Nom d'utilisateur indisponible"
|
||||||
});
|
});
|
||||||
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
if (!email_valid) {
|
if (!email_valid) {
|
||||||
setError('email', {
|
setError('email', {
|
||||||
type: 'manual',
|
type: 'manual',
|
||||||
message: 'Email déjà utilisé'
|
message: 'Email déjà utilisé'
|
||||||
});
|
});
|
||||||
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const token = res.headers.get('Authorization')?.split(' ')[1];
|
const token = res.headers.get('Authorization')?.split(' ')[1];
|
||||||
if (token) cookies.set('token', token);
|
if (token) cookies.set('token', token);
|
||||||
|
router.refresh();
|
||||||
} else {
|
} else {
|
||||||
setError('passwd', {
|
setError('passwd', {
|
||||||
type: 'manual',
|
type: 'manual',
|
||||||
|
@ -79,10 +83,6 @@ export default function UserAuthForm() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (token) router.push('/dashboard');
|
|
||||||
}, [token]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<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"
|
||||||
|
@ -100,14 +100,14 @@ export default function UserAuthForm() {
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
label="Nom"
|
label="Nom"
|
||||||
type="lastname"
|
type="text"
|
||||||
placeholder="Doe"
|
placeholder="Doe"
|
||||||
error={errors.lastname?.message}
|
error={errors.lastname?.message}
|
||||||
{...register('lastname')}
|
{...register('lastname')}
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
label="Prénom"
|
label="Prénom"
|
||||||
type="firstname"
|
type="text"
|
||||||
placeholder="John"
|
placeholder="John"
|
||||||
error={errors.firstname?.message}
|
error={errors.firstname?.message}
|
||||||
{...register('firstname')}
|
{...register('firstname')}
|
||||||
|
@ -130,7 +130,7 @@ export default function UserAuthForm() {
|
||||||
error={errors.passwd?.message}
|
error={errors.passwd?.message}
|
||||||
{...register('passwd')}
|
{...register('passwd')}
|
||||||
/>
|
/>
|
||||||
<Button type="submit" kind="brand">
|
<Button type="submit" kind="brand" disabled={isLoading}>
|
||||||
{isSignIn ? 'Se connecter' : "S'inscrire"}
|
{isSignIn ? 'Se connecter' : "S'inscrire"}
|
||||||
</Button>
|
</Button>
|
||||||
<div className="flex flex-col text-center">
|
<div className="flex flex-col text-center">
|
||||||
|
|
|
@ -1,11 +1,34 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import { useMe } from '@/lib/hooks/use-players';
|
||||||
import { titleCase } from '@/lib/utils';
|
import { titleCase } from '@/lib/utils';
|
||||||
import { useSelectedLayoutSegment } from 'next/navigation';
|
import cookies from 'js-cookie';
|
||||||
|
import { useRouter, useSelectedLayoutSegment } from 'next/navigation';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import AvatarComponent from '../Avatar';
|
||||||
import Icon from '../Icon';
|
import Icon from '../Icon';
|
||||||
|
import Popover from '../Popover';
|
||||||
|
|
||||||
export default function Usernav({ isOpen, toggle }: { isOpen: boolean; toggle: () => void }) {
|
export default function Usernav({ isOpen, toggle }: { isOpen: boolean; toggle: () => void }) {
|
||||||
|
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||||
|
const router = useRouter();
|
||||||
const segment = useSelectedLayoutSegment();
|
const segment = useSelectedLayoutSegment();
|
||||||
|
|
||||||
|
const token = cookies.get('token');
|
||||||
|
|
||||||
|
const { data: me, isLoading } = useMe({ token: token! });
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isOpen) {
|
||||||
|
setIsMenuOpen(false);
|
||||||
|
}
|
||||||
|
}, [isOpen]);
|
||||||
|
|
||||||
|
async function handleLogout() {
|
||||||
|
cookies.remove('token');
|
||||||
|
router.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="z-50 flex w-full flex-row items-center justify-between border-b border-solid border-highlight-primary bg-secondary py-4 px-8">
|
<div className="z-50 flex w-full flex-row items-center justify-between border-b border-solid border-highlight-primary bg-secondary py-4 px-8">
|
||||||
<div className="flex flex-row items-center space-x-2 sm:space-x-0">
|
<div className="flex flex-row items-center space-x-2 sm:space-x-0">
|
||||||
|
@ -21,12 +44,34 @@ export default function Usernav({ isOpen, toggle }: { isOpen: boolean; toggle: (
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row items-center space-x-4">
|
<div className="flex flex-row items-center space-x-4">
|
||||||
<button className="flex items-center text-2xl text-error">
|
{!isLoading && me ? (
|
||||||
<Icon name="flag-line" />
|
<Popover
|
||||||
|
open={isMenuOpen}
|
||||||
|
onOpenChange={setIsMenuOpen}
|
||||||
|
trigger={
|
||||||
|
<button className="mx-auto flex items-center gap-2">
|
||||||
|
<AvatarComponent name={me.pseudo} src={me.avatar} className="h-9 w-9" />
|
||||||
|
<span>{me?.pseudo}</span>
|
||||||
</button>
|
</button>
|
||||||
<button className="flex items-center justify-center rounded-full border border-primary-400 bg-tertiary px-4 py-2">
|
}
|
||||||
T
|
>
|
||||||
|
<nav className="flex w-32 flex-col gap-2">
|
||||||
|
<button
|
||||||
|
className="flex items-center gap-1 p-2 text-error hover:bg-error/10"
|
||||||
|
onClick={() => handleLogout()}
|
||||||
|
>
|
||||||
|
Se déconnecter
|
||||||
</button>
|
</button>
|
||||||
|
</nav>
|
||||||
|
</Popover>
|
||||||
|
) : (
|
||||||
|
<div className="animate-pulse">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="h-9 w-9 rounded-full bg-highlight-primary" />
|
||||||
|
<div className="h-4 w-14 rounded-full bg-highlight-primary" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
Loading…
Add table
Reference in a new issue