added somes ISR & loading stuff
This commit is contained in:
parent
e96a44608b
commit
4ca1c599fd
14 changed files with 158 additions and 107 deletions
|
@ -1,22 +1,4 @@
|
|||
import { getScores } from '@/lib/leaderboard';
|
||||
import { cn } from '@/lib/utils';
|
||||
import Avatar from '@/ui/Avatar';
|
||||
import Select from '@/ui/Select';
|
||||
|
||||
// TODO: Generate this later
|
||||
const scoreColors = ['text-yellow-400', 'text-gray-400', 'text-orange-400'];
|
||||
|
||||
// TODO: Generate this later
|
||||
const options = [
|
||||
{ value: '1i1', title: '1I1' },
|
||||
{ value: '1i2', title: '1I2' },
|
||||
{ value: '1i3', title: '1I3' },
|
||||
{ value: '1i4', title: '1I4' },
|
||||
{ value: '1i5', title: '1I5' },
|
||||
{ value: '1i6', title: '1I6' },
|
||||
{ value: '1i7', title: '1I7' },
|
||||
{ value: '1i8', title: '1I8' }
|
||||
];
|
||||
import Leaderboard from '@/ui/Leaderboard';
|
||||
|
||||
export const metadata = {
|
||||
title: 'Tableau des scores - Peer-at Code',
|
||||
|
@ -24,49 +6,5 @@ export const metadata = {
|
|||
};
|
||||
|
||||
export default async function Page() {
|
||||
// TODO: CSR fetch data for leaderboard (useSWR) to make it more reactive
|
||||
const data = await getScores();
|
||||
return (
|
||||
<div className="flex h-full w-full flex-col space-y-4">
|
||||
<div className="w-full">
|
||||
<section className="flex flex-col space-y-4">
|
||||
<header className="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 className="text-xl font-semibold">Tableau des scores</h3>
|
||||
<p className="hidden text-muted sm:block">
|
||||
Suivez la progression des élèves en direct
|
||||
</p>
|
||||
</div>
|
||||
<Select className="w-28" options={options} />
|
||||
</header>
|
||||
<main className="flex flex-col justify-between space-x-0 space-y-4">
|
||||
{data.map((score, key) => (
|
||||
<div key={key} className="flex flex-col space-y-2">
|
||||
<div className="flex justify-between space-x-2">
|
||||
<div className="flex items-center space-x-4">
|
||||
<span className={cn('font-semibold', scoreColors[key])}>{key + 1}</span>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Avatar name={score.pseudo} />
|
||||
<span className="text-lg">{score.pseudo}</span>
|
||||
<span className="text-sm text-muted">{score.group}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="flex flex-col">
|
||||
<span className="text-sm font-semibold">Puzzles</span>
|
||||
<span className="text-lg text-muted">{score.completions}</span>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<span className="text-sm font-semibold">Score</span>
|
||||
<span className="text-lg text-muted">{score.score}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</main>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return <Leaderboard />;
|
||||
}
|
||||
|
|
|
@ -23,8 +23,10 @@ export default async function Page({ params }: { params: { id: number } }) {
|
|||
return <Puzzle puzzle={puzzle} />;
|
||||
}
|
||||
|
||||
// export const dynamicParams = true;
|
||||
|
||||
// export async function generateStaticParams() {
|
||||
// const { puzzles } = await getPuzzles();
|
||||
// // every id is a number, but we need to return a string
|
||||
// return puzzles.map((puzzle) => ({ id: puzzle.id.toString() }));
|
||||
// return puzzles.flatMap((puzzle) => ({ id: puzzle.id.toString() }));
|
||||
// }
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { getPuzzles } from '@/lib/puzzles';
|
||||
import Puzzles from '@/ui/Puzzles';
|
||||
|
||||
export const metadata = {
|
||||
|
@ -6,10 +5,9 @@ export const metadata = {
|
|||
};
|
||||
|
||||
export default async function Page() {
|
||||
const data = await getPuzzles();
|
||||
return (
|
||||
<div className="flex flex-col space-y-6">
|
||||
<Puzzles data={data} />
|
||||
<Puzzles />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
6
lib/hooks/use-leaderboard.ts
Normal file
6
lib/hooks/use-leaderboard.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
import useSWR from 'swr';
|
||||
import { getScores } from '../leaderboard';
|
||||
|
||||
export function useLeaderboard() {
|
||||
return useSWR('leaderboard', () => getScores());
|
||||
}
|
|
@ -17,8 +17,6 @@ export const getChapters = async (): Promise<Chapter[]> => {
|
|||
|
||||
chapters = chapters.filter((chapter: Chapter) => chapter.id !== 0);
|
||||
|
||||
console.log(chapters);
|
||||
|
||||
return chapters as Chapter[];
|
||||
};
|
||||
|
||||
|
|
|
@ -20,8 +20,6 @@ export async function middleware(req: NextRequest) {
|
|||
else if (req.nextUrl.pathname.includes('sign') && token)
|
||||
return NextResponse.redirect(getURL('/dashboard'));
|
||||
|
||||
res.headers.set('Authorization', `Bearer ${token}`);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,5 @@
|
|||
@layer components {
|
||||
.console {
|
||||
@apply relative top-0.5 inline-block;
|
||||
/* make it hidden then visible every seconde */
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,8 +29,8 @@ module.exports = {
|
|||
0: 'hsl(258deg 8% 100%)'
|
||||
},
|
||||
brand: {
|
||||
DEFAULT: '#1c56cb',
|
||||
accent: '#236bfe'
|
||||
DEFAULT: '#5049ca',
|
||||
accent: '#913fb6'
|
||||
},
|
||||
success: {
|
||||
DEFAULT: 'hsl(104deg 39% 59%)',
|
||||
|
@ -55,11 +55,6 @@ module.exports = {
|
|||
highlight: {
|
||||
primary: 'hsl(258deg 15% 17%)',
|
||||
secondary: 'hsl(258deg 10% 46%)'
|
||||
},
|
||||
product: {
|
||||
ignite: 'hsl(8deg 89% 57%)',
|
||||
pipe: 'hsl(214deg 100% 58%)',
|
||||
channels: 'hsl(46deg 74% 51%)'
|
||||
}
|
||||
},
|
||||
backgroundColor: {
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"downlevelIteration": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
|
|
|
@ -14,7 +14,7 @@ const Button = forwardRef<
|
|||
{
|
||||
'bg-highlight-primary hover:bg-highlight-primary/60': kind === 'default',
|
||||
'bg-error hover:bg-error/60': kind === 'danger',
|
||||
'bg-brand hover:bg-brand/60': kind === 'brand'
|
||||
'bg-gradient-to-tl from-brand to-brand-accent hover:bg-opacity-80': kind === 'brand'
|
||||
},
|
||||
className
|
||||
)}
|
||||
|
|
|
@ -14,7 +14,7 @@ const Input = forwardRef<
|
|||
<Label label={label} description={description} required={props.required} className={className}>
|
||||
<input
|
||||
ref={ref}
|
||||
className="w-full rounded-md border border-primary-600 bg-highlight-primary px-5 py-2.5 text-sm font-medium outline-none focus:border-brand focus:bg-primary-800 disabled:opacity-50"
|
||||
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"
|
||||
{...props}
|
||||
/>
|
||||
</Label>
|
||||
|
|
79
ui/Leaderboard.tsx
Normal file
79
ui/Leaderboard.tsx
Normal file
|
@ -0,0 +1,79 @@
|
|||
'use client';
|
||||
|
||||
import { useLeaderboard } from '@/lib/hooks/use-leaderboard';
|
||||
import { cn } from '@/lib/utils';
|
||||
import Avatar from './Avatar';
|
||||
import Select from './Select';
|
||||
|
||||
// TODO: Generate this later
|
||||
const scoreColors = ['text-yellow-400', 'text-gray-400', 'text-orange-400'];
|
||||
|
||||
// TODO: Generate this later
|
||||
export const options = [
|
||||
{ value: '1i1', title: '1I1' },
|
||||
{ value: '1i2', title: '1I2' },
|
||||
{ value: '1i3', title: '1I3' },
|
||||
{ value: '1i4', title: '1I4' },
|
||||
{ value: '1i5', title: '1I5' },
|
||||
{ value: '1i6', title: '1I6' },
|
||||
{ value: '1i7', title: '1I7' },
|
||||
{ value: '1i8', title: '1I8' }
|
||||
];
|
||||
|
||||
export default function Leaderboard() {
|
||||
const { data, isLoading } = useLeaderboard();
|
||||
return (
|
||||
<div className="flex h-full w-full flex-col space-y-4">
|
||||
<div className="w-full">
|
||||
<section className="flex flex-col space-y-4">
|
||||
<header className="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 className="text-xl font-semibold">Tableau des scores</h3>
|
||||
<p className="hidden text-muted sm:block">
|
||||
Suivez la progression des élèves en direct
|
||||
</p>
|
||||
</div>
|
||||
<Select className="w-28" options={options} />
|
||||
</header>
|
||||
<main className="flex flex-col justify-between space-x-0 space-y-4 pb-4">
|
||||
{(!isLoading &&
|
||||
data?.map((score, key) => (
|
||||
<div key={key} className="flex flex-col space-y-2">
|
||||
<div className="flex justify-between space-x-2">
|
||||
<div className="flex items-center space-x-4">
|
||||
<span className={cn('font-semibold', scoreColors[key])}>{key + 1}</span>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Avatar name={score.pseudo} />
|
||||
<span className="text-lg">{score.pseudo}</span>
|
||||
<span className="text-sm text-muted">{score.group}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="flex flex-col">
|
||||
<span className="text-sm font-semibold">Puzzles</span>
|
||||
<span className="text-lg text-muted">{score.completions}</span>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<span className="text-sm font-semibold">Score</span>
|
||||
<span className="text-lg text-muted">{score.score}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))) ||
|
||||
[...Array(20).keys()].map((i) => (
|
||||
<span
|
||||
key={i}
|
||||
className="inline-block h-12 animate-pulse rounded-lg bg-primary-600"
|
||||
style={{
|
||||
animationDelay: `${i * 0.05}s`,
|
||||
animationDuration: '1s'
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</main>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -3,6 +3,7 @@
|
|||
import type { Puzzle as PuzzleType } from '@/lib/puzzles';
|
||||
import { notFound } from 'next/navigation';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import cookies from 'js-cookie';
|
||||
|
||||
import Button from './Button';
|
||||
import Input from './Input';
|
||||
|
@ -10,6 +11,7 @@ import ToHTML from './ToHTML';
|
|||
|
||||
type PuzzleData = {
|
||||
answer: string;
|
||||
filename: string;
|
||||
code_file: File[];
|
||||
};
|
||||
|
||||
|
@ -26,6 +28,7 @@ export default function Puzzle({ puzzle }: { puzzle: PuzzleType }) {
|
|||
} = useForm<PuzzleData>({
|
||||
defaultValues: {
|
||||
answer: '',
|
||||
filename: '',
|
||||
code_file: undefined
|
||||
}
|
||||
});
|
||||
|
@ -34,11 +37,15 @@ export default function Puzzle({ puzzle }: { puzzle: PuzzleType }) {
|
|||
const formData = new FormData();
|
||||
|
||||
formData.append('answer', data.answer);
|
||||
formData.append('filename', data.code_file[0].name);
|
||||
formData.append('code_file', data.code_file[0]);
|
||||
|
||||
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/puzzleResponse/${puzzle.id}`, {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
body: formData,
|
||||
headers: {
|
||||
Authorization: `Bearer ${cookies.get('token')}}`
|
||||
}
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
|
|
|
@ -1,38 +1,68 @@
|
|||
'use client';
|
||||
|
||||
import { Chapter, Puzzle } from '@/lib/puzzles';
|
||||
import { usePuzzles } from '@/lib/hooks/use-puzzles';
|
||||
import AppLink from './AppLink';
|
||||
import Icon from './Icon';
|
||||
|
||||
export default function Puzzles({ data }: { data: { chapters: Chapter[]; puzzles: Puzzle[] } }) {
|
||||
// const { data, isLoading } = usePuzzles();
|
||||
export default function Puzzles() {
|
||||
const { data, isLoading } = usePuzzles();
|
||||
return (
|
||||
<>
|
||||
{data?.chapters?.map((chapter) => (
|
||||
<div key={chapter.id} className="flex flex-col space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-2xl font-semibold">
|
||||
Chapitre {chapter.id} - {chapter.name}
|
||||
</h3>
|
||||
<div className="h-1 w-1/4 rounded-lg bg-gray-200">
|
||||
<div className="h-1 w-1/2 rounded-lg bg-brand" />
|
||||
{(!isLoading &&
|
||||
data?.chapters?.map((chapter) => (
|
||||
<div key={chapter.id} className="flex flex-col space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-xl font-semibold sm:text-2xl">
|
||||
Chapitre {chapter.id} - {chapter.name}
|
||||
</h3>
|
||||
<div className="h-1 w-1/4 rounded-lg bg-gray-200">
|
||||
<div className="h-1 w-1/2 rounded-lg bg-gradient-to-tl from-brand to-brand-accent" />
|
||||
</div>
|
||||
</div>
|
||||
<ul className="flex flex-col space-y-4">
|
||||
{data?.puzzles.map((puzzle) => (
|
||||
<AppLink key={puzzle.id} href={`/dashboard/puzzles/${puzzle.id}`}>
|
||||
<li className="group flex items-center justify-between rounded-md bg-primary-700 p-4 font-code hover:bg-primary-600">
|
||||
<span className="text-base font-semibold">{puzzle.name}</span>
|
||||
<Icon
|
||||
className="-translate-x-2 transform-gpu duration-300 group-hover:translate-x-0"
|
||||
name="arrow-right-line"
|
||||
/>
|
||||
</li>
|
||||
</AppLink>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<ul className="flex flex-col space-y-4">
|
||||
{data?.puzzles.map((puzzle) => (
|
||||
<AppLink key={puzzle.id} href={`/dashboard/puzzles/${puzzle.id}`}>
|
||||
<li className="group flex justify-between rounded-md bg-primary-700 p-4 font-code hover:bg-primary-600">
|
||||
<span className="font-semibold">{puzzle.name}</span>
|
||||
<Icon
|
||||
className="-translate-x-2 transform-gpu duration-300 group-hover:translate-x-0"
|
||||
name="arrow-right-line"
|
||||
))) || (
|
||||
<div className="flex flex-col space-y-6">
|
||||
{[...Array(3).keys()].map((i) => (
|
||||
<div key={i} className="flex flex-col space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="inline-block h-8 w-1/2 rounded-lg bg-primary-600" />
|
||||
<span
|
||||
className="inline-block h-1 w-1/4 animate-pulse rounded-lg bg-primary-600"
|
||||
style={{
|
||||
animationDelay: `${i * 0.05}s`,
|
||||
animationDuration: '1s'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<ul className="flex flex-col space-y-4">
|
||||
{[...Array(7).keys()].map((j) => (
|
||||
<span
|
||||
key={j}
|
||||
className="inline-block h-14 animate-pulse rounded-lg bg-primary-600"
|
||||
style={{
|
||||
animationDelay: `${j * 0.05}s`,
|
||||
animationDuration: '1s'
|
||||
}}
|
||||
/>
|
||||
</li>
|
||||
</AppLink>
|
||||
))}
|
||||
</ul>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue