diff --git a/lib/fetcher.ts b/lib/fetcher.ts
new file mode 100644
index 0000000..1803a9c
--- /dev/null
+++ b/lib/fetcher.ts
@@ -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;
diff --git a/lib/hooks/use-leaderboard.ts b/lib/hooks/use-leaderboard.ts
index fb11515..b2c684f 100644
--- a/lib/hooks/use-leaderboard.ts
+++ b/lib/hooks/use-leaderboard.ts
@@ -1,6 +1,6 @@
import useSWR from 'swr';
import { getScores } from '../leaderboard';
-export function useLeaderboard() {
- return useSWR('leaderboard', () => getScores());
+export function useLeaderboard({ token }: { token: string }) {
+ return useSWR('leaderboard', () => getScores({ token }));
}
diff --git a/lib/hooks/use-players.ts b/lib/hooks/use-players.ts
new file mode 100644
index 0000000..22bcd73
--- /dev/null
+++ b/lib/hooks/use-players.ts
@@ -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 }));
+}
diff --git a/lib/hooks/use-puzzles.ts b/lib/hooks/use-puzzles.ts
index 88a77e0..9a4ab81 100644
--- a/lib/hooks/use-puzzles.ts
+++ b/lib/hooks/use-puzzles.ts
@@ -2,14 +2,14 @@ import useSWR from 'swr';
import { getChapters, getPuzzle, getPuzzles } from '../puzzles';
-export function useChapters() {
- return useSWR('chapters', () => getChapters());
+export function useChapters({ token }: { token: string }) {
+ return useSWR('chapters', () => getChapters({ token }));
}
-export function usePuzzles() {
- return useSWR('puzzles', () => getPuzzles());
+export function usePuzzles({ token }: { token: string }) {
+ return useSWR('puzzles', () => getPuzzles({ token }));
}
-export function usePuzzle(id: number) {
- return useSWR(`puzzles/${id}`, () => getPuzzle(id));
+export function usePuzzle({ token, id }: { token: string; id: number }) {
+ return useSWR(`puzzles/${id}`, () => getPuzzle({ token, id }));
}
diff --git a/lib/leaderboard.ts b/lib/leaderboard.ts
index 0f3d387..54d79f7 100644
--- a/lib/leaderboard.ts
+++ b/lib/leaderboard.ts
@@ -1,8 +1,10 @@
-import axios from 'axios';
+import fetcher from './fetcher';
-export const getScores = async (): Promise
=> {
- const { data, status } = await axios.get(`${process.env.NEXT_PUBLIC_API_URL}/leaderboard`, {
- insecureHTTPParser: true
+export const getScores = async ({ token }: { token: string }): Promise => {
+ const { data, status } = await fetcher.get(`/leaderboard`, {
+ headers: {
+ Authorization: `Bearer ${token}`
+ }
});
const scores = data;
@@ -24,4 +26,5 @@ export type Score = {
completions: number;
pseudo: string;
group: string;
+ avatar: string;
};
diff --git a/lib/players.ts b/lib/players.ts
new file mode 100644
index 0000000..afdc3be
--- /dev/null
+++ b/lib/players.ts
@@ -0,0 +1,41 @@
+import fetcher from './fetcher';
+
+export const getPlayer = async ({
+ token,
+ username = ''
+}: {
+ token: string;
+ username?: string;
+}): Promise => {
+ 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[];
+};
diff --git a/lib/puzzles.ts b/lib/puzzles.ts
index 5b374d7..1187721 100644
--- a/lib/puzzles.ts
+++ b/lib/puzzles.ts
@@ -1,8 +1,10 @@
-import axios from 'axios';
+import fetcher from './fetcher';
-export const getChapters = async (): Promise => {
- const { data, status } = await axios.get(`${process.env.NEXT_PUBLIC_API_URL}/chapters`, {
- insecureHTTPParser: true
+export const getChapters = async ({ token }: { token: string }): Promise => {
+ const { data, status } = await fetcher.get(`/chapters`, {
+ headers: {
+ Authorization: `Bearer ${token}`
+ }
});
let chapters = data;
@@ -20,36 +22,44 @@ export const getChapters = async (): Promise => {
return chapters as Chapter[];
};
-export const getPuzzlesByChapter = async (chapitre: number): Promise => {
- const { data, status } = await axios.get(
- `${process.env.NEXT_PUBLIC_API_URL}/chapter/${chapitre}`,
- { insecureHTTPParser: true }
- );
+export const getChapter = async ({
+ token,
+ id
+}: {
+ token: string;
+ id: number;
+}): Promise => {
+ const { data, status } = await fetcher.get(`/chapter/${id}`, {
+ headers: {
+ Authorization: `Bearer ${token}`
+ }
+ });
- const { puzzles, name, id } = data;
+ const chapter = data;
if (status !== 200) {
throw new Error('Failed to fetch puzzles');
}
- if (!puzzles) {
+ if (!chapter) {
return null;
}
- return {
- name,
- id,
- puzzles
- };
+ return chapter as Chapter;
};
-export const getPuzzles = async (): Promise<{ chapters: Chapter[]; puzzles: Puzzle[] }> => {
- const chapters = await getChapters();
- let puzzles: Puzzle[] = [];
+export const getPuzzles = async ({
+ token
+}: {
+ token: string;
+}): Promise<{ chapters: Chapter[]; puzzles: Puzzle[] }> => {
+ const chapters = await getChapters({ token });
+ const puzzles: Puzzle[] = [];
for (const chapter of chapters) {
- const puzzlesByChapter = await getPuzzlesByChapter(chapter.id);
- puzzles = [...puzzles, ...puzzlesByChapter!.puzzles];
+ const puzzlesByChapter = await getChapter({ token, id: chapter.id });
+ if (!puzzlesByChapter?.puzzles) continue;
+ puzzles.push(...puzzlesByChapter!.puzzles);
}
return {
@@ -58,9 +68,11 @@ export const getPuzzles = async (): Promise<{ chapters: Chapter[]; puzzles: Puzz
};
};
-export const getPuzzle = async (id: number): Promise => {
- const { data, status } = await axios.get(`${process.env.NEXT_PUBLIC_API_URL}/puzzle/${id}`, {
- insecureHTTPParser: true
+export const getPuzzle = async ({ token, id }: { token: string; id: number }): Promise => {
+ const { data, status } = await fetcher.get(`/puzzle/${id}`, {
+ headers: {
+ Authorization: `Bearer ${token}`
+ }
});
const puzzle = data;
@@ -77,8 +89,8 @@ export const getPuzzle = async (id: number): Promise => {
};
export type Puzzle = {
- name: string;
id: number;
+ name: string;
content: string;
};
diff --git a/package.json b/package.json
index 595d716..2b806c1 100644
--- a/package.json
+++ b/package.json
@@ -21,6 +21,7 @@
},
"homepage": "https://github.com/Peer-at-Code/peer-at-code#readme",
"dependencies": {
+ "@radix-ui/react-popover": "^1.0.5",
"axios": "^1.3.4",
"boring-avatars": "^1.7.0",
"clsx": "^1.2.1",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index d623388..94a4003 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1,6 +1,7 @@
lockfileVersion: 5.4
specifiers:
+ '@radix-ui/react-popover': ^1.0.5
'@tailwindcss/forms': ^0.5.3
'@types/js-cookie': ^3.0.3
'@types/node': 18.11.18
@@ -33,6 +34,7 @@ specifiers:
zod: ^3.20.2
dependencies:
+ '@radix-ui/react-popover': 1.0.5_5ndqzdd6t4rivxsukjv3i3ak2q
axios: 1.3.4
boring-avatars: 1.7.0
clsx: 1.2.1
@@ -73,7 +75,6 @@ packages:
engines: {node: '>=6.9.0'}
dependencies:
regenerator-runtime: 0.13.11
- dev: true
/@eslint/eslintrc/1.4.1:
resolution: {integrity: sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==}
@@ -92,6 +93,30 @@ packages:
- supports-color
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:
resolution: {integrity: sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==}
engines: {node: '>=10.10.0'}
@@ -272,6 +297,253 @@ packages:
tslib: 2.5.0
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:
resolution: {integrity: sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==}
dev: true
@@ -556,6 +828,13 @@ packages:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
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:
resolution: {integrity: sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==}
dependencies:
@@ -885,6 +1164,10 @@ packages:
engines: {node: '>=6'}
dev: false
+ /detect-node-es/1.1.0:
+ resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==}
+ dev: false
+
/detective/5.2.1:
resolution: {integrity: sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==}
engines: {node: '>=0.8.0'}
@@ -1494,6 +1777,11 @@ packages:
has-symbols: 1.0.3
dev: true
+ /get-nonce/1.0.1:
+ resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==}
+ engines: {node: '>=6'}
+ dev: false
+
/get-symbol-description/1.0.0:
resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==}
engines: {node: '>= 0.4'}
@@ -1686,6 +1974,12 @@ packages:
side-channel: 1.0.4
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:
resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==}
engines: {node: '>= 0.4'}
@@ -2701,6 +2995,58 @@ packages:
- supports-color
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:
resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==}
engines: {node: '>=0.10.0'}
@@ -2723,7 +3069,6 @@ packages:
/regenerator-runtime/0.13.11:
resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==}
- dev: true
/regexp.prototype.flags/1.4.3:
resolution: {integrity: sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==}
@@ -3175,6 +3520,50 @@ packages:
punycode: 2.3.0
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:
resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==}
peerDependencies:
diff --git a/styles/globals.css b/styles/globals.css
index fe01a95..5cc833f 100644
--- a/styles/globals.css
+++ b/styles/globals.css
@@ -6,4 +6,16 @@
.console {
@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;
+ }
}
diff --git a/ui/Avatar.tsx b/ui/Avatar.tsx
index 6589c0b..2df4c63 100644
--- a/ui/Avatar.tsx
+++ b/ui/Avatar.tsx
@@ -1,5 +1,46 @@
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 ;
}
+
+export function Base64Avatar({
+ name,
+ src,
+ className
+}: {
+ name: string;
+ src: string;
+ className?: string;
+}) {
+ return (
+
+ );
+}
+
+export default function AvatarComponent({
+ name,
+ src,
+ size = 36,
+ className
+}: {
+ name: string;
+ src: string;
+ size?: number;
+ className?: string;
+}) {
+ return src ? (
+
+ ) : (
+
+ );
+}
diff --git a/ui/Badge.tsx b/ui/Badge.tsx
index a50ee3e..943afc5 100644
--- a/ui/Badge.tsx
+++ b/ui/Badge.tsx
@@ -4,6 +4,12 @@ import { cn } from '@/lib/utils';
export type Difficulty = 'easy' | 'medium' | 'hard';
+export const DIFFICULTY = {
+ 1: 'easy',
+ 2: 'medium',
+ 3: 'hard'
+}
+
export default function Badge({
title,
path,
diff --git a/ui/Button.tsx b/ui/Button.tsx
index 270ce0f..8e003ea 100644
--- a/ui/Button.tsx
+++ b/ui/Button.tsx
@@ -10,11 +10,12 @@ const Button = forwardRef<
+ );
+
return (
-
{title}
-
{data}
+
{data}
+
{title}
);
diff --git a/ui/Input.tsx b/ui/Input.tsx
index dd4f0d5..cdede32 100644
--- a/ui/Input.tsx
+++ b/ui/Input.tsx
@@ -1,5 +1,6 @@
import { forwardRef } from 'react';
import ErrorMessage from './ErrorMessage';
+import Icon from './Icon';
import Label from './Label';
const Input = forwardRef<
@@ -14,7 +15,8 @@ const Input = forwardRef<
diff --git a/ui/Leaderboard.tsx b/ui/Leaderboard.tsx
index 504f4a1..23ab4e0 100644
--- a/ui/Leaderboard.tsx
+++ b/ui/Leaderboard.tsx
@@ -2,78 +2,102 @@
import { useLeaderboard } from '@/lib/hooks/use-leaderboard';
import { cn } from '@/lib/utils';
-import Avatar from './Avatar';
+import { useMemo, useState } from 'react';
+import AvatarComponent 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({ token }: { token: string }) {
+ const { data, isLoading } = useLeaderboard({ token });
+
+ const [filter, setFilter] = useState('');
+
+ let options;
+
+ 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 }));
+ 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 (
-
-
-
-
-
- {(!isLoading &&
- data?.map((score, key) => (
-
-
-
-
{key + 1}
-
-
-
{score.pseudo}
-
{score.group}
-
-
-
-
- Puzzles
- {score.completions}
-
-
- Score
- {score.score}
-
+
+
+
+
+ {(!isLoading &&
+ filteredData?.map((score, key) => (
+ -
+
+
{key + 1}
+
+
+
+ {score.pseudo}
+ {score.group}
- ))) ||
- [...Array(20).keys()].map((i) => (
-
- ))}
-
-
-
-
+
+
+ Puzzles
+ {score.completions}
+
+
+ Score
+ {score.score}
+
+
+
+ ))) ||
+ [...Array(20).keys()].map((i) => (
+
+ ))}
+
+
+
);
}
diff --git a/ui/Popover.tsx b/ui/Popover.tsx
new file mode 100644
index 0000000..2d91d20
--- /dev/null
+++ b/ui/Popover.tsx
@@ -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 (
+
+ {/* {tooltip ? (
+
+ {trigger}
+
+ ) : ( */}
+ {trigger}
+ {/* )} */}
+
+
+ {children}
+
+
+
+
+ );
+}
diff --git a/ui/Puzzle.tsx b/ui/Puzzle.tsx
index ae66e31..5832bba 100644
--- a/ui/Puzzle.tsx
+++ b/ui/Puzzle.tsx
@@ -73,12 +73,14 @@ export default function Puzzle({ puzzle }: { puzzle: PuzzleType }) {
label="Réponse"
type="text"
placeholder="12"
+ required
{...register('answer')}
/>
diff --git a/ui/Puzzles.tsx b/ui/Puzzles.tsx
index 42982a2..ce68e88 100644
--- a/ui/Puzzles.tsx
+++ b/ui/Puzzles.tsx
@@ -4,8 +4,9 @@ import { usePuzzles } from '@/lib/hooks/use-puzzles';
import AppLink from './AppLink';
import Icon from './Icon';
-export default function Puzzles() {
- const { data, isLoading } = usePuzzles();
+export default function Puzzles({ token }: { token: string }) {
+ const { data, isLoading } = usePuzzles({ token });
+ console.log(data);
return (
<>
{(!isLoading &&
@@ -48,7 +49,7 @@ export default function Puzzles() {
/>
- {[...Array(7).keys()].map((j) => (
+ {[...Array(6).keys()].map((j) => (
>>
+ }
+ //& Partial>>
>(({ options, className, label, description, error, ...props }, ref) => (
<>