@@ -18,15 +18,19 @@ export default function Page() {
-
-
-
+ {me?.badges ? (
+ me?.badges.map((badge, i) => (
+
+ ))
+ ) : (
+
Aucun badge
+ )}
diff --git a/app/dashboard/layout.tsx b/app/dashboard/layout.tsx
index 7dd6000..8721e6b 100644
--- a/app/dashboard/layout.tsx
+++ b/app/dashboard/layout.tsx
@@ -1,11 +1,16 @@
import { type ReactNode } from 'react';
+import { UserProvider } from '@/context/user';
import Wrapper from '@/ui/dashboard/Wrapper';
+import { cookies } from 'next/headers';
-export default function Layout({ children }: { children: ReactNode }) {
+export default async function Layout({ children }: { children: ReactNode }) {
+ const token = cookies().get('token')!.value;
return (
- {children}
+
+ {children}
+
);
}
diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx
index 5d39a7a..2c721fe 100644
--- a/app/dashboard/page.tsx
+++ b/app/dashboard/page.tsx
@@ -1,12 +1,11 @@
'use client';
-import { useMe } from '@/lib/hooks/use-players';
+import { UserContext } from '@/context/user';
import Card from '@/ui/Card';
-import cookies from 'js-cookie';
+import { useContext } from 'react';
export default function Page() {
- const token = cookies.get('token');
- const { data: me, isLoading } = useMe({ token: token! });
+ const { data: me, isLoading } = useContext(UserContext);
return (
@@ -19,21 +18,16 @@ export default function Page() {
-
+
diff --git a/context/user.tsx b/context/user.tsx
new file mode 100644
index 0000000..d9c1bca
--- /dev/null
+++ b/context/user.tsx
@@ -0,0 +1,20 @@
+'use client';
+
+import { useMe } from '@/lib/hooks/use-players';
+import type { Player } from '@/lib/players';
+import { createContext, type ReactNode } from 'react';
+
+export const UserContext = createContext<{
+ data: Player | null | undefined;
+ isLoading: boolean;
+ error: Error | null;
+}>({
+ data: null,
+ isLoading: true,
+ error: null
+});
+
+export const UserProvider = ({ token, children }: { token: string; children: ReactNode }) => {
+ const { data, isLoading, error } = useMe({ token });
+ return
{children} ;
+};
diff --git a/lib/hooks/use-players.ts b/lib/hooks/use-players.ts
index 22bcd73..96c6d5f 100644
--- a/lib/hooks/use-players.ts
+++ b/lib/hooks/use-players.ts
@@ -2,9 +2,7 @@ import useSWR from 'swr';
import { getPlayer } from '../players';
export function useMe({ token }: { token: string }) {
- return useSWR('me', () => getPlayer({ token }), {
- revalidateOnReconnect: false
- });
+ return useSWR('me', () => getPlayer({ token }));
}
export function usePlayer({ token, username }: { token: string; username: string }) {
diff --git a/lib/leaderboard.ts b/lib/leaderboard.ts
index 54d79f7..7f32d3b 100644
--- a/lib/leaderboard.ts
+++ b/lib/leaderboard.ts
@@ -1,4 +1,5 @@
import fetcher from './fetcher';
+import type { Group } from './players';
export const getScores = async ({ token }: { token: string }): Promise
=> {
const { data, status } = await fetcher.get(`/leaderboard`, {
@@ -25,6 +26,7 @@ export type Score = {
tries: number;
completions: number;
pseudo: string;
- group: string;
+ groups: Group[];
avatar: string;
+ rank: number;
};
diff --git a/lib/nav-items.ts b/lib/nav-items.ts
index c2d7558..1e8cdb7 100644
--- a/lib/nav-items.ts
+++ b/lib/nav-items.ts
@@ -48,6 +48,6 @@ export const navItems: NavItem[] = [
name: 'Paramètres',
slug: 'settings',
icon: 'equalizer-line',
- disabled: false
+ disabled: true
}
];
diff --git a/lib/players.ts b/lib/players.ts
index afdc3be..3e3a584 100644
--- a/lib/players.ts
+++ b/lib/players.ts
@@ -28,14 +28,26 @@ export const getPlayer = async ({
export type Player = {
email: string;
+ pseudo: string;
firstnames: string;
lastname: string;
description: string;
avatar: string;
- group: string;
+ groups: Group[];
score: number;
tries: number;
completions: number;
- pseudo: string;
- badges: any[];
+ rank: number;
+ badges: Badge[] | null;
+};
+
+export type Badge = {
+ name: string;
+ level: number;
+ logo?: string;
+};
+
+export type Group = {
+ name: string;
+ chapter?: number;
};
diff --git a/lib/puzzles.ts b/lib/puzzles.ts
index 1187721..e2371ec 100644
--- a/lib/puzzles.ts
+++ b/lib/puzzles.ts
@@ -7,7 +7,7 @@ export const getChapters = async ({ token }: { token: string }): Promise chapter.id !== 0);
-
return chapters as Chapter[];
};
@@ -48,24 +46,17 @@ export const getChapter = async ({
return chapter as Chapter;
};
-export const getPuzzles = async ({
- token
-}: {
- token: string;
-}): Promise<{ chapters: Chapter[]; puzzles: Puzzle[] }> => {
+export const getPuzzles = async ({ token }: { token: string }): Promise => {
const chapters = await getChapters({ token });
- const puzzles: Puzzle[] = [];
- for (const chapter of chapters) {
- const puzzlesByChapter = await getChapter({ token, id: chapter.id });
- if (!puzzlesByChapter?.puzzles) continue;
- puzzles.push(...puzzlesByChapter!.puzzles);
+ for (let i = 0; i < chapters.length; i++) {
+ const chapter = chapters[i];
+ const chapterData = await getChapter({ token, id: chapter.id });
+ if (!chapterData) continue;
+ chapters[i].puzzles = chapterData.puzzles;
}
- return {
- chapters: chapters as Chapter[],
- puzzles: puzzles as Puzzle[]
- };
+ return chapters as Chapter[];
};
export const getPuzzle = async ({ token, id }: { token: string; id: number }): Promise => {
@@ -92,10 +83,17 @@ export type Puzzle = {
id: number;
name: string;
content: string;
+ tags: Tag[] | null;
};
export type Chapter = {
- name: string;
id: number;
+ name: string;
puzzles: Puzzle[];
+ startDay?: string;
+ endDay?: string;
+};
+
+export type Tag = {
+ name: string;
};
diff --git a/middleware.ts b/middleware.ts
index 3c074e8..6d1f355 100644
--- a/middleware.ts
+++ b/middleware.ts
@@ -9,10 +9,6 @@ import { getURL } from './lib/utils';
export async function middleware(req: NextRequest) {
const res = NextResponse.next();
- // on donne accès à l'API depuis n'importe quelle origine
- res.headers.set('Access-Control-Allow-Origin', '*');
- res.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
-
const token = req.cookies.get('token')?.value;
if (req.nextUrl.pathname.includes('dashboard') && !token)
diff --git a/package.json b/package.json
index 2b806c1..9b21acf 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,5 @@
{
"name": "peer-at-code",
- "version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
diff --git a/public/assets/brand/peerat.png b/public/assets/brand/peerat.png
index d1b2385..f0086c9 100644
Binary files a/public/assets/brand/peerat.png and b/public/assets/brand/peerat.png differ
diff --git a/tailwind.config.js b/tailwind.config.js
index b160c52..cf483cb 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -1,3 +1,4 @@
+// eslint-disable-next-line @typescript-eslint/no-var-requires
const defaultTheme = require('tailwindcss/defaultTheme');
/** @type {import('tailwindcss').Config} */
diff --git a/ui/Badge.tsx b/ui/Badge.tsx
index 943afc5..284f42d 100644
--- a/ui/Badge.tsx
+++ b/ui/Badge.tsx
@@ -2,42 +2,37 @@ import Image from 'next/image';
import { cn } from '@/lib/utils';
-export type Difficulty = 'easy' | 'medium' | 'hard';
-
-export const DIFFICULTY = {
- 1: 'easy',
- 2: 'medium',
- 3: 'hard'
-}
+export const DIFFICULTY_COLOR = {
+ 1: 'green',
+ 2: 'yellow',
+ 3: 'red'
+};
export default function Badge({
- title,
- path,
+ name,
+ src,
alt,
- type = 'easy',
- earned = false
+ level
}: {
- title: string;
- path: string;
+ name: string;
+ src: string;
alt: string;
- type?: Difficulty;
- earned?: boolean;
+ level: number;
}) {
return (
- {earned ? title : '****'}
+ {name}
);
}
diff --git a/ui/Input.tsx b/ui/Input.tsx
index cdede32..cab7c6b 100644
--- a/ui/Input.tsx
+++ b/ui/Input.tsx
@@ -1,6 +1,5 @@
import { forwardRef } from 'react';
import ErrorMessage from './ErrorMessage';
-import Icon from './Icon';
import Label from './Label';
const Input = forwardRef<
diff --git a/ui/Leaderboard.tsx b/ui/Leaderboard.tsx
index 23ab4e0..ea42532 100644
--- a/ui/Leaderboard.tsx
+++ b/ui/Leaderboard.tsx
@@ -6,22 +6,23 @@ import { useMemo, useState } from 'react';
import AvatarComponent from './Avatar';
import Select from './Select';
-const scoreColors = ['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 }) {
const { data, isLoading } = useLeaderboard({ token });
const [filter, setFilter] = useState('');
- let options;
+ let options = [] as { value: string; title: string }[];
if (data) {
options = data
- .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 }));
+ .filter((score) => score.groups && score.groups.length > 0)
+ .map((score) => score.groups.map((group) => ({ value: group.name, title: group.name })))
+ .flat()
+ .filter((group, index, self) => self.findIndex((g) => g.value === group.value) === index)
+ .sort((a, b) => a.title.localeCompare(b.title));
+
options.unshift({ value: '', title: 'Tous' });
options.push({ value: 'no-group', title: 'Sans groupe' });
}
@@ -29,9 +30,9 @@ export default function Leaderboard({ token }: { token: string }) {
const filteredData = useMemo(() => {
if (filter) {
if (filter === 'no-group') {
- return data?.filter((score) => score.group === '');
+ return data?.filter((score) => !score.groups || score.groups.length === 0);
}
- return data?.filter((score) => score.group === filter);
+ return data?.filter((score) => score.groups?.find((group) => group.name === filter));
}
return data;
}, [data, filter]);
@@ -65,12 +66,16 @@ export default function Leaderboard({ token }: { token: string }) {
filteredData?.map((score, key) => (
-
{key + 1}
+
+ {score.rank}
+
{score.pseudo}
- {score.group}
+
+ {score.groups?.map((g) => g.name).join(', ')}
+
diff --git a/ui/Puzzles.tsx b/ui/Puzzles.tsx
index ce68e88..b3d8c3c 100644
--- a/ui/Puzzles.tsx
+++ b/ui/Puzzles.tsx
@@ -1,37 +1,89 @@
'use client';
import { usePuzzles } from '@/lib/hooks/use-puzzles';
+import { type Chapter } from '@/lib/puzzles';
+import { cn } from '@/lib/utils';
import AppLink from './AppLink';
import Icon from './Icon';
export default function Puzzles({ token }: { token: string }) {
const { data, isLoading } = usePuzzles({ token });
- console.log(data);
+
+ // SOme chapters have a start date and a end date (for example, the first chapter is only available for 2 weeks), I want to want to lock the chapter if the current date is not between the start and end date
+ // I want to display a message to the user if the chapter is locked
+
+ function isChapterLocked(chapter: Chapter) {
+ return (
+ chapter.startDay &&
+ chapter.endDay &&
+ new Date() > new Date(chapter.startDay) &&
+ new Date() < new Date(chapter.endDay)
+ );
+ }
+
return (
<>
{(!isLoading &&
- data?.chapters?.map((chapter) => (
+ data?.map((chapter) => (
- Chapitre {chapter.id} - {chapter.name}
+ Chapitre {chapter.id} - {chapter.name}{' '}
-
- {data?.puzzles.map((puzzle) => (
-
-
- {puzzle.name}
-
-
-
- ))}
+
+ {chapter.puzzles &&
+ chapter.puzzles.map((puzzle) => (
+
+ tag.name.toLowerCase())
+ .includes('easy'),
+ 'border-yellow-600/30': puzzle.tags
+ ?.map((tag) => tag.name.toLowerCase())
+ .includes('medium'),
+ 'border-red-600/30': puzzle.tags
+ ?.map((tag) => tag.name.toLowerCase())
+ .includes('hard'),
+ 'border-highlight-secondary/30': !puzzle.tags?.length
+ }
+ )}
+ >
+
+
{puzzle.name}
+ {puzzle.tags?.length && (
+
+ {puzzle.tags
+ .filter((tag) => !['easy', 'medium', 'hard'].includes(tag.name))
+ .map((tag, i) => (
+
+ {tag.name}
+
+ ))}
+
+ )}
+
+
+
+
+ ))}
))) || (
diff --git a/ui/Select.tsx b/ui/Select.tsx
index 85f3275..da9a6eb 100644
--- a/ui/Select.tsx
+++ b/ui/Select.tsx
@@ -1,5 +1,4 @@
import { forwardRef } from 'react';
-import type { UseFormRegister } from 'react-hook-form';
import ErrorMessage from './ErrorMessage';
import Label from './Label';
@@ -17,7 +16,7 @@ const Select = forwardRef<
-
-
- Peer-at Code
+
-
+
@@ -76,7 +77,7 @@ function NavItem({
className={cn('flex justify-center rounded-md px-3 py-3 text-sm lg:justify-start', {
'text-muted hover:text-secondary': !isActive,
'bg-highlight-primary text-secondary': isActive,
- 'text-gray-600 hover:text-gray-600': item.disabled,
+ 'cursor-not-allowed text-gray-600 hover:text-gray-600': item.disabled,
'justify-center lg:justify-start': isOpen,
'justify-start sm:justify-center': !isOpen
})}
diff --git a/ui/dashboard/Usernav.tsx b/ui/dashboard/Usernav.tsx
index ed8a0b6..50e1f8f 100644
--- a/ui/dashboard/Usernav.tsx
+++ b/ui/dashboard/Usernav.tsx
@@ -1,10 +1,10 @@
'use client';
-import { useMe } from '@/lib/hooks/use-players';
+import { UserContext } from '@/context/user';
import { titleCase } from '@/lib/utils';
import cookies from 'js-cookie';
import { useRouter, useSelectedLayoutSegment } from 'next/navigation';
-import { useEffect, useState } from 'react';
+import { useContext, useEffect, useState } from 'react';
import AvatarComponent from '../Avatar';
import Icon from '../Icon';
import Popover from '../Popover';
@@ -14,9 +14,7 @@ export default function Usernav({ isOpen, toggle }: { isOpen: boolean; toggle: (
const router = useRouter();
const segment = useSelectedLayoutSegment();
- const token = cookies.get('token');
-
- const { data: me, isLoading } = useMe({ token: token! });
+ const { data: me, isLoading } = useContext(UserContext);
useEffect(() => {
if (isOpen) {