Spoiler changes & puzzle update
This commit is contained in:
parent
cc1fb080b5
commit
5d1aec2c16
5 changed files with 68 additions and 38 deletions
|
@ -85,6 +85,7 @@ export type Puzzle = {
|
||||||
content: string;
|
content: string;
|
||||||
scoreMax: number;
|
scoreMax: number;
|
||||||
tags: Tag[] | null;
|
tags: Tag[] | null;
|
||||||
|
tries?: number;
|
||||||
score?: number;
|
score?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -5,46 +5,67 @@ import { cn } from '@/lib/utils';
|
||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import AvatarComponent from './Avatar';
|
import AvatarComponent from './Avatar';
|
||||||
import Select from './Select';
|
import Select from './Select';
|
||||||
|
import { type ScoreEvent } from '@/lib/leaderboard';
|
||||||
|
import useSWRSubscription, { type SWRSubscription } from 'swr/subscription';
|
||||||
|
import { useGroups } from '@/lib/hooks/use-groups';
|
||||||
|
|
||||||
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({ token }: { token: string }) {
|
export default function Leaderboard({ token }: { token: string }) {
|
||||||
const { data, isLoading } = useLeaderboard({ token });
|
// const { data, isLoading } = useLeaderboard({ token });
|
||||||
|
|
||||||
|
const subscription: SWRSubscription<string, ScoreEvent, Error> = (key, { next }) => {
|
||||||
|
const socket = new WebSocket(key);
|
||||||
|
|
||||||
|
socket.addEventListener('message', (event) => {
|
||||||
|
next(null, JSON.parse(event.data));
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.addEventListener('error', (event) => {
|
||||||
|
console.error(event);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => socket.close();
|
||||||
|
};
|
||||||
|
|
||||||
|
const { data } = useSWRSubscription(
|
||||||
|
`wss://${process.env.NEXT_PUBLIC_API_URL?.split('//')[1]}/rleaderboard`,
|
||||||
|
subscription
|
||||||
|
);
|
||||||
|
|
||||||
const [filter, setFilter] = useState('');
|
const [filter, setFilter] = useState('');
|
||||||
|
|
||||||
let options = [] as { value: string; title: string }[];
|
let options = [] as { value: string; title: string }[];
|
||||||
|
|
||||||
if (data) {
|
const { data: groups } = useGroups({ token });
|
||||||
options = data
|
|
||||||
.filter((score) => score.groups && score.groups.length > 0)
|
if (groups) {
|
||||||
.map((score) => score.groups.map((group) => ({ value: group.name, title: group.name })))
|
options = groups
|
||||||
.flat()
|
.filter((group) => !group.chapter)
|
||||||
|
.map((group) => ({ value: group.name, title: group.name }))
|
||||||
.filter((group, index, self) => self.findIndex((g) => g.value === group.value) === index)
|
.filter((group, index, self) => self.findIndex((g) => g.value === group.value) === index)
|
||||||
.sort((a, b) => a.title.localeCompare(b.title));
|
.sort((a, b) => a.title.localeCompare(b.title));
|
||||||
|
|
||||||
options.unshift({ value: '', title: 'Tous' });
|
options.unshift({ value: '', title: 'Tous' });
|
||||||
options.push({ value: 'no-group', title: 'Sans groupe' });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const filteredData = useMemo(() => {
|
const filteredData = useMemo(() => {
|
||||||
if (filter) {
|
if (filter) {
|
||||||
if (filter === 'no-group') {
|
return data?.groups.filter((group) => group.name === filter);
|
||||||
return data?.filter((score) => !score.groups || score.groups.length === 0);
|
|
||||||
}
|
|
||||||
return data?.filter((score) => score.groups?.find((group) => group.name === filter));
|
|
||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
}, [data, filter]);
|
}, [data, filter]);
|
||||||
|
|
||||||
|
console.log(filteredData);
|
||||||
|
|
||||||
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 className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<h1 className="text-xl font-semibold">Tableau des scores</h1>
|
<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="text-muted">Suivez la progression des élèves en direct</p>
|
||||||
</div>
|
</div>
|
||||||
{(filteredData && (
|
{/* {(filteredData && (
|
||||||
<Select
|
<Select
|
||||||
className="w-32"
|
className="w-32"
|
||||||
options={options || []}
|
options={options || []}
|
||||||
|
@ -58,12 +79,13 @@ export default function Leaderboard({ token }: { token: string }) {
|
||||||
animationDuration: '1s'
|
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">
|
Waiting for the leaderboard to be implemented (Xavier WSS)
|
||||||
|
{/* <ul className="flex flex-col space-y-2">
|
||||||
{(!isLoading &&
|
{(!isLoading &&
|
||||||
filteredData?.map((score, key) => (
|
filteredData((group, key) => (
|
||||||
<li key={key} className="flex justify-between space-x-2">
|
<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-4">
|
||||||
<span className={cn('font-semibold', SCORE_COLORS[score.rank - 1])}>
|
<span className={cn('font-semibold', SCORE_COLORS[score.rank - 1])}>
|
||||||
|
@ -104,7 +126,7 @@ export default function Leaderboard({ token }: { token: string }) {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul> */}
|
||||||
</main>
|
</main>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
|
|
@ -59,8 +59,9 @@ export default function Puzzle({ token, id }: { token: string; id: number }) {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.ok || res.status === 406) {
|
if (res.ok || res.status === 406) {
|
||||||
mutate(`puzzles/${puzzle?.id}`);
|
const data = (await res.json()) as { tries: number; score?: number };
|
||||||
setGranted(await res.json());
|
if (data.score) mutate(`puzzles/${puzzle?.id}`);
|
||||||
|
else setGranted(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,7 +90,7 @@ export default function Puzzle({ token, id }: { token: string; id: number }) {
|
||||||
className="w-full"
|
className="w-full"
|
||||||
label="Réponse"
|
label="Réponse"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="12"
|
placeholder="CAPTAIN LOOK !"
|
||||||
required
|
required
|
||||||
{...register('answer')}
|
{...register('answer')}
|
||||||
/>
|
/>
|
||||||
|
@ -114,11 +115,19 @@ export default function Puzzle({ token, id }: { token: string; id: number }) {
|
||||||
</form>
|
</form>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<p className="text-sm text-highlight-secondary">
|
<div className=" items-center gap-x-2">
|
||||||
Score : {puzzle.score} (Score maximum: {puzzle.scoreMax})
|
<p className="text-sm">
|
||||||
</p>
|
Tentatives : <span className="text-brand">{puzzle.tries}</span>
|
||||||
<AppLink href="/dashboard/puzzles" className="text-sm text-highlight-secondary">
|
</p>
|
||||||
Retour aux puzzles
|
<p className="text-sm">
|
||||||
|
Score : <span className="text-brand">{puzzle.score}</span> (Score maximum:{' '}
|
||||||
|
{puzzle.scoreMax})
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<AppLink href="/dashboard/puzzles">
|
||||||
|
<Button kind="brand" type="button">
|
||||||
|
Retour aux puzzles
|
||||||
|
</Button>
|
||||||
</AppLink>
|
</AppLink>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -44,7 +44,7 @@ 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">
|
||||||
<h1 className="text-xl font-semibold sm:text-2xl">{chapter.name}</h1>
|
<h1 className="text-xl font-semibold">{chapter.name}</h1>
|
||||||
{!isInEventGroup(chapter) && (
|
{!isInEventGroup(chapter) && (
|
||||||
<Dialog
|
<Dialog
|
||||||
key={chapter.id}
|
key={chapter.id}
|
||||||
|
@ -167,11 +167,9 @@ function PuzzleProp({ puzzle, chapter }: { puzzle: Puzzle; chapter: Chapter }) {
|
||||||
className={cn(
|
className={cn(
|
||||||
'group relative flex h-full w-full rounded-md border-2 bg-primary-700 font-code hover:bg-primary-600',
|
'group relative flex h-full w-full rounded-md border-2 bg-primary-700 font-code hover:bg-primary-600',
|
||||||
{
|
{
|
||||||
'border-green-600/30': puzzle.tags?.map((tag) => tag.name.toLowerCase()).includes('easy'),
|
'border-green-600/30': puzzle.tags?.find((tag) => tag.name.toLowerCase() === 'easy'),
|
||||||
'border-yellow-600/30': puzzle.tags
|
'border-yellow-600/30': puzzle.tags?.find((tag) => tag.name.toLowerCase() === 'medium'),
|
||||||
?.map((tag) => tag.name.toLowerCase())
|
'border-red-600/30': puzzle.tags?.find((tag) => tag.name.toLowerCase() === 'hard'),
|
||||||
.includes('medium'),
|
|
||||||
'border-red-600/30': puzzle.tags?.map((tag) => tag.name.toLowerCase()).includes('hard'),
|
|
||||||
'border-highlight-primary': !puzzle.tags?.length,
|
'border-highlight-primary': !puzzle.tags?.length,
|
||||||
'cursor-not-allowed': !isPuzzleAvailable(chapter)
|
'cursor-not-allowed': !isPuzzleAvailable(chapter)
|
||||||
}
|
}
|
||||||
|
@ -183,7 +181,7 @@ function PuzzleProp({ puzzle, chapter }: { puzzle: Puzzle; chapter: Chapter }) {
|
||||||
key={puzzle.id}
|
key={puzzle.id}
|
||||||
href={`/dashboard/puzzles/${puzzle.id}`}
|
href={`/dashboard/puzzles/${puzzle.id}`}
|
||||||
>
|
>
|
||||||
<div className="flex gap-x-2">
|
<div className="flex w-10/12 flex-col gap-2 md:w-full md:flex-row">
|
||||||
<span className="text-base font-semibold">{puzzle.name}</span>
|
<span className="text-base font-semibold">{puzzle.name}</span>
|
||||||
{puzzle.tags?.length && (
|
{puzzle.tags?.length && (
|
||||||
<div className="flex gap-x-2 text-sm text-muted">
|
<div className="flex gap-x-2 text-sm text-muted">
|
||||||
|
@ -323,7 +321,7 @@ function GroupForm({ chapter, token }: { chapter: Chapter; token: string }) {
|
||||||
<>
|
<>
|
||||||
<Select
|
<Select
|
||||||
className="w-full"
|
className="w-full"
|
||||||
label="Groupes"
|
label="Groupes disponibles"
|
||||||
required
|
required
|
||||||
{...register('name')}
|
{...register('name')}
|
||||||
options={
|
options={
|
||||||
|
|
|
@ -10,19 +10,19 @@ export default function ToHTML({ data, className }: { data: string; className?:
|
||||||
<div className={cn(className)}>
|
<div className={cn(className)}>
|
||||||
<Mardown
|
<Mardown
|
||||||
components={{
|
components={{
|
||||||
a: ({ node, ...props }) => (
|
a: ({ ...props }) => (
|
||||||
<a
|
<a
|
||||||
{...props}
|
{...props}
|
||||||
className="text-brand hover:text-brand/80 hover:underline"
|
className="text-brand hover:text-brand/80 hover:underline"
|
||||||
download={node}
|
// MAKE thIS SHIT DOWNLOAD
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noopener"
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
code: ({ ...props }) => (
|
code: ({ ...props }) => (
|
||||||
<span
|
<code
|
||||||
{...props}
|
{...props}
|
||||||
className="cursor-pointer select-none rounded bg-black p-0.5 text-black hover:bg-transparent hover:text-white"
|
className="cursor-pointer select-none text-transparent hover:text-highlight-secondary"
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
|
|
Loading…
Add table
Reference in a new issue