From 48b247e92096927e8c169a767b663c1f3f0f5182 Mon Sep 17 00:00:00 2001 From: jhervoch Date: Thu, 5 Feb 2026 16:12:33 +0100 Subject: [PATCH 1/9] feat(FRONT): start react tournament --- srcs/nginx/src/App.tsx | 28 ++++-- srcs/nginx/src/api/auth-api.ts | 21 ++++- .../src/components/atoms/CircleButton.tsx | 30 +++++++ .../src/components/atoms/TournamentList.tsx | 87 +++++++++++++++++++ .../components/organisms/PageContainer.tsx | 2 +- srcs/nginx/src/locales/en/common.json | 6 ++ srcs/nginx/src/locales/fr/common.json | 6 ++ srcs/nginx/src/locales/tf/common.json | 6 ++ srcs/nginx/src/pages/TournamentPage.tsx | 51 +++++++++++ srcs/nginx/src/providers/AuthProvider.tsx | 53 ++++++----- srcs/nginx/src/types/react-types.ts | 2 + 11 files changed, 260 insertions(+), 32 deletions(-) create mode 100644 srcs/nginx/src/components/atoms/CircleButton.tsx create mode 100644 srcs/nginx/src/components/atoms/TournamentList.tsx create mode 100644 srcs/nginx/src/pages/TournamentPage.tsx diff --git a/srcs/nginx/src/App.tsx b/srcs/nginx/src/App.tsx index cadd17cc..370986b7 100644 --- a/srcs/nginx/src/App.tsx +++ b/srcs/nginx/src/App.tsx @@ -3,20 +3,33 @@ import { ProfilePage } from './pages/ProfilePage'; import { LoginPage } from './pages/LoginRegisterPage'; import { useAuth } from './providers/AuthProvider'; import { AnimationPage } from './pages/AnimationPage'; +import { TournamentPage } from './pages/TournamentPage'; const GuestRoute = ({ children }: { children: React.ReactNode }) => { - const { user, isLoggedIn } = useAuth(); - if (user && isLoggedIn) { + const { user, isLoggedIn, isAuthChecked } = useAuth(); + + if (!isAuthChecked) { + return null; // ou loader + } + + if (isLoggedIn && user?.username) { return ; } + return children; }; - const MeRedirect = () => { - const { user } = useAuth(); - // if (isLoading) return
Loading ...
; - if (!user) return ; - return ; + const { user, isAuthChecked } = useAuth(); + + if (!isAuthChecked) { + return null; // ou loader + } + + if (!user || !user.username) { + return ; + } + + return ; }; export const App = () => { @@ -42,6 +55,7 @@ export const App = () => { /> }> }> + }> ); diff --git a/srcs/nginx/src/api/auth-api.ts b/srcs/nginx/src/api/auth-api.ts index 73073857..05ed92b5 100644 --- a/srcs/nginx/src/api/auth-api.ts +++ b/srcs/nginx/src/api/auth-api.ts @@ -54,8 +54,23 @@ export const authApi = { return data?.user?.username; }, + // me: async (): Promise => { + // usernameSchema.parse(username); + // // const response = await api.get(`/auth/me/`); + // const response = { + // data: { + // authId: 1, + // email: 'toto@mail.com', + // username: 'Toto', + // }, + // message: 'OK', + // }; + // return response.data; + // }, me: async (): Promise => { - const response = await api.get(`/auth/me/`); - return response.data; - }, + const { data } = await api.get('/auth/me', { + withCredentials: true, + }); + return data; + }:w }; diff --git a/srcs/nginx/src/components/atoms/CircleButton.tsx b/srcs/nginx/src/components/atoms/CircleButton.tsx new file mode 100644 index 00000000..615f53e7 --- /dev/null +++ b/srcs/nginx/src/components/atoms/CircleButton.tsx @@ -0,0 +1,30 @@ +import { motion } from 'framer-motion'; + +interface CircleButtonProps { + children?: React.ReactNode; +} + +const dropdownStyle = 'shadow-[0_10px_10px_1px_rgba(205,205,205,0.4)] '; + +export const CircleButton = ({ children }: CircleButtonProps) => ( + +

{children}

+
+); diff --git a/srcs/nginx/src/components/atoms/TournamentList.tsx b/srcs/nginx/src/components/atoms/TournamentList.tsx new file mode 100644 index 00000000..c902c792 --- /dev/null +++ b/srcs/nginx/src/components/atoms/TournamentList.tsx @@ -0,0 +1,87 @@ +export type Tournament = { + id: string; + name: string; + players: number; + maxPlayers: number; + status: 'WAITING' | 'IN_PROGRESS' | 'FINISHED'; + createdAt: string; +}; + +type Props = { + tournaments: Tournament[]; + onJoin: (id: string) => void; +}; + +const statusLabel: Record = { + WAITING: 'En attente', + IN_PROGRESS: 'En cours', + FINISHED: 'Terminé', +}; + +const statusColor: Record = { + WAITING: 'text-emerald-600', + IN_PROGRESS: 'text-amber-600', + FINISHED: 'text-gray-500', +}; + +export function TournamentList({ tournaments, onJoin }: Props) { + return ( +
+
+

+ Tournois disponibles +

+ +
+ + + + + + + + + + + + {tournaments.map((t) => ( + + + + + + + + + + ))} + + {tournaments.length === 0 && ( + + + + )} + +
NomJoueursStatutAction
{t.name} + {t.players} / {t.maxPlayers} + + {statusLabel[t.status]} + + {t.status === 'WAITING' ? ( + + ) : ( + Indisponible + )} +
+ Aucun tournoi disponible +
+
+
+
+ ); +} diff --git a/srcs/nginx/src/components/organisms/PageContainer.tsx b/srcs/nginx/src/components/organisms/PageContainer.tsx index fabe8efa..cbda734a 100644 --- a/srcs/nginx/src/components/organisms/PageContainer.tsx +++ b/srcs/nginx/src/components/organisms/PageContainer.tsx @@ -28,7 +28,7 @@ export const Page = ({ children, title, className }: PageProps) => { } - {title &&

{title}

} + {title &&

{title}

} {children}
diff --git a/srcs/nginx/src/locales/en/common.json b/srcs/nginx/src/locales/en/common.json index e5f2d079..5f96caaf 100644 --- a/srcs/nginx/src/locales/en/common.json +++ b/srcs/nginx/src/locales/en/common.json @@ -66,5 +66,11 @@ "copyright": "© 2026 . All rights reserved.", "privacyPolicy": "Privacy Policy", "termsOfService": "Terms of Service" + }, + "game": { + "tournament": "Tournament", + "game": "Game", + "participate": "Participate", + "create": "Create" } } diff --git a/srcs/nginx/src/locales/fr/common.json b/srcs/nginx/src/locales/fr/common.json index c2f69834..dad9ae64 100644 --- a/srcs/nginx/src/locales/fr/common.json +++ b/srcs/nginx/src/locales/fr/common.json @@ -66,5 +66,11 @@ "copyright": "© 2026 Tous droits réservés.", "privacyPolicy": "Politique de confidentialité", "termsOfService": "Conditions d'utilisation" + }, + "game": { + "tournament": "tournoi", + "game": "jeu", + "participate": "Participer", + "create": "Créer" } } diff --git a/srcs/nginx/src/locales/tf/common.json b/srcs/nginx/src/locales/tf/common.json index ff94361f..6e833cc8 100644 --- a/srcs/nginx/src/locales/tf/common.json +++ b/srcs/nginx/src/locales/tf/common.json @@ -66,5 +66,11 @@ "copyright": "© 2026 – C'est à nous.", "privacyPolicy": "La loi du milieu (on balance rien, t'es en sécurité)", "termsOfService": "Les règles du jeu (lis ça, sinon t’es un rigolo)" + }, + "game": { + "tournament": "Vendetta", + "game": "La mort ou la vie", + "participate": "Tu cherches des embrouilles!", + "create": "Je vous mets au défi!" } } diff --git a/srcs/nginx/src/pages/TournamentPage.tsx b/srcs/nginx/src/pages/TournamentPage.tsx new file mode 100644 index 00000000..9b44fc2e --- /dev/null +++ b/srcs/nginx/src/pages/TournamentPage.tsx @@ -0,0 +1,51 @@ +import { CircleButton } from '../components/atoms/CircleButton'; +import { Page } from '../components/organisms/PageContainer'; +import { useTranslation } from 'react-i18next'; +import { useEffect, useState } from 'react'; +import { TournamentList, Tournament } from '../components/atoms/TournamentList'; + +const MOCK_TOURNAMENTS: Tournament[] = [ + { + id: '1', + name: 'Spin Cup #42', + players: 2, + maxPlayers: 4, + status: 'WAITING', + createdAt: '2026-02-01', + }, + { + id: '2', + name: 'Weekly Pong', + players: 4, + maxPlayers: 4, + status: 'IN_PROGRESS', + createdAt: '2026-02-03', + }, +]; +interface LoginRegisterPageProps { + isRegister: boolean; +} + +// const login = async () => {}; + +export const TournamentPage = () => { + // const [currentUser, formAction, isPending] = useActionState(login, {}); + const { t } = useTranslation(); + const title = t('game.tournament').toUpperCase(); + const participate = t('game.participate'); + const create = t('game.create'); + return ( + +
+
+ {participate} + {create} +
+
+ console.log('Join tournament', id)} + /> +
+ ); +}; diff --git a/srcs/nginx/src/providers/AuthProvider.tsx b/srcs/nginx/src/providers/AuthProvider.tsx index 55da3c48..2b5e99a0 100644 --- a/srcs/nginx/src/providers/AuthProvider.tsx +++ b/srcs/nginx/src/providers/AuthProvider.tsx @@ -1,44 +1,58 @@ import { ProfileSimpleDTO } from '@transcendence/core'; -import { createContext, useContext, useMemo, useState } from 'react'; +import { createContext, useContext, useEffect, useMemo, useState } from 'react'; import { AuthContextType, AuthProviderProps } from '../types/react-types'; - -// from https://dev.to/joodi/useauth-hook-in-react-1bp3 +import { authApi } from '../api/auth-api'; export const AuthContext = createContext(null); export const AuthProvider = ({ children }: AuthProviderProps) => { - const [user, setUser] = useState(() => { - const storedUser = localStorage.getItem('user'); - return storedUser ? JSON.parse(storedUser) : null; - }); + const [user, setUser] = useState(null); + const [isAuthChecked, setIsAuthChecked] = useState(false); + + /** + * Vérification réelle de session au montage + */ + useEffect(() => { + const checkAuth = async () => { + try { + const me = await authApi.me(); + const profile: ProfileSimpleDTO = { + username: me.username, + avatarUrl: null, // ou me.avatarUrl si dispo plus tard + }; + setUser(profile); + } catch { + setUser(null); + } finally { + setIsAuthChecked(true); + } + }; + + checkAuth(); + }, []); const login = (user: ProfileSimpleDTO) => { setUser(user); - localStorage.setItem('user', JSON.stringify(user)); }; - const logout = () => { + const logout = async () => { setUser(null); - localStorage.removeItem('user'); }; const updateUser = (newUser: ProfileSimpleDTO) => { - setUser((prevUser) => { - const updated = { ...prevUser, ...newUser }; - localStorage.setItem('user', JSON.stringify(updated)); - return updated as ProfileSimpleDTO; - }); + setUser((prev) => (prev ? { ...prev, ...newUser } : prev)); }; - // memoize to avoid re-render const contextValue = useMemo( () => ({ user, + isAuthChecked, + isLoggedIn: isAuthChecked && user !== null, login, logout, updateUser, }), - [user], + [user, isAuthChecked], ); return {children}; @@ -49,8 +63,5 @@ export const useAuth = () => { if (!context) { throw new Error('useAuth must be used within an AuthProvider'); } - - const isLoggedIn = Boolean(context.user); - - return { ...context, isLoggedIn }; + return context; }; diff --git a/srcs/nginx/src/types/react-types.ts b/srcs/nginx/src/types/react-types.ts index 2bac82a9..83b42b8d 100644 --- a/srcs/nginx/src/types/react-types.ts +++ b/srcs/nginx/src/types/react-types.ts @@ -18,6 +18,8 @@ export enum Roles { export interface AuthContextType { user: ProfileSimpleDTO | null; + isLoggedIn: boolean; + isAuthChecked: boolean; login: (user: ProfileSimpleDTO) => void; logout: () => void; updateUser: (newUser: ProfileSimpleDTO) => void; From f62ba67c03c61aa35aed34fb46940e319dbfbef1 Mon Sep 17 00:00:00 2001 From: jhervoch Date: Mon, 9 Feb 2026 09:16:24 +0100 Subject: [PATCH 2/9] feat(front): add tournament pages srcs/nginx/src/components/atoms/BracketLines.tsx srcs/nginx/src/components/atoms/MatchNode.tsx srcs/nginx/src/components/atoms/PlayerCapsule.tsx srcs/nginx/src/components/molecules/TournamentBracket.tsx srcs/nginx/src/pages/TournamentPage.tsx srcs/nginx/src/types/types.ts --- .../src/components/atoms/BracketLines.tsx | 17 ++++++++ srcs/nginx/src/components/atoms/MatchNode.tsx | 13 ++++++ .../src/components/atoms/PlayerCapsule.tsx | 11 +++++ .../molecules/TournamentBracket.tsx | 42 +++++++++++++++++++ srcs/nginx/src/pages/TournamentPage.tsx | 10 +++++ srcs/nginx/src/types/types.ts | 6 +++ 6 files changed, 99 insertions(+) create mode 100644 srcs/nginx/src/components/atoms/BracketLines.tsx create mode 100644 srcs/nginx/src/components/atoms/MatchNode.tsx create mode 100644 srcs/nginx/src/components/atoms/PlayerCapsule.tsx create mode 100644 srcs/nginx/src/components/molecules/TournamentBracket.tsx diff --git a/srcs/nginx/src/components/atoms/BracketLines.tsx b/srcs/nginx/src/components/atoms/BracketLines.tsx new file mode 100644 index 00000000..d6178c2d --- /dev/null +++ b/srcs/nginx/src/components/atoms/BracketLines.tsx @@ -0,0 +1,17 @@ +export function BracketLines() { + return ( + + {/* LEFT TO CENTER */} + + + + {/* RIGHT TO CENTER */} + + + + ); +} diff --git a/srcs/nginx/src/components/atoms/MatchNode.tsx b/srcs/nginx/src/components/atoms/MatchNode.tsx new file mode 100644 index 00000000..dffa02bc --- /dev/null +++ b/srcs/nginx/src/components/atoms/MatchNode.tsx @@ -0,0 +1,13 @@ +export function MatchNode({ label, highlight = false }: { label: string; highlight?: boolean }) { + return ( +
+ {label} +
+ ); +} diff --git a/srcs/nginx/src/components/atoms/PlayerCapsule.tsx b/srcs/nginx/src/components/atoms/PlayerCapsule.tsx new file mode 100644 index 00000000..05b5ae52 --- /dev/null +++ b/srcs/nginx/src/components/atoms/PlayerCapsule.tsx @@ -0,0 +1,11 @@ +import { Player } from '../../types/types'; +import Avatar from './Avatar'; + +export function PlayerCapsule({ player }: { player: Player }) { + return ( +
+ + {player.name} +
+ ); +} diff --git a/srcs/nginx/src/components/molecules/TournamentBracket.tsx b/srcs/nginx/src/components/molecules/TournamentBracket.tsx new file mode 100644 index 00000000..bcf6e2a0 --- /dev/null +++ b/srcs/nginx/src/components/molecules/TournamentBracket.tsx @@ -0,0 +1,42 @@ +import { Player } from '../../types/types'; +import { BracketLines } from '../atoms/BracketLines'; +import { PlayerCapsule } from '../atoms/PlayerCapsule'; +import { MatchNode } from '../atoms/MatchNode'; + +interface TournamentBracketProps { + players: [Player, Player, Player, Player]; +} +import Avatar from '../atoms/Avatar'; + +export function TournamentBracket({ players }: TournamentBracketProps) { + const [p1, p2, p3, p4] = players; + + return ( +
+ {/* SVG LINES */} + + + {/* CONTENT */} +
+ {/* LEFT */} +
+ + +
+ + {/* CENTER */} +
+ + + +
+ + {/* RIGHT */} +
+ + +
+
+
+ ); +} diff --git a/srcs/nginx/src/pages/TournamentPage.tsx b/srcs/nginx/src/pages/TournamentPage.tsx index 9b44fc2e..214cf104 100644 --- a/srcs/nginx/src/pages/TournamentPage.tsx +++ b/srcs/nginx/src/pages/TournamentPage.tsx @@ -3,6 +3,8 @@ import { Page } from '../components/organisms/PageContainer'; import { useTranslation } from 'react-i18next'; import { useEffect, useState } from 'react'; import { TournamentList, Tournament } from '../components/atoms/TournamentList'; +import { TournamentBracket } from '../components/molecules/TournamentBracket'; +import { Player } from '../types/types'; const MOCK_TOURNAMENTS: Tournament[] = [ { @@ -26,6 +28,13 @@ interface LoginRegisterPageProps { isRegister: boolean; } +const MOCK_PLAYERS: [Player, Player, Player, Player] = [ + { id: '1', name: 'johnny', avatar: null }, + { id: '2', name: 'eddy', avatar: null }, + { id: '3', name: 'khaled', avatar: null }, + { id: '4', name: 'danny', avatar: null }, +] as const; + // const login = async () => {}; export const TournamentPage = () => { @@ -46,6 +55,7 @@ export const TournamentPage = () => { tournaments={MOCK_TOURNAMENTS} onJoin={(id) => console.log('Join tournament', id)} /> + ); }; diff --git a/srcs/nginx/src/types/types.ts b/srcs/nginx/src/types/types.ts index 0a12659d..1dbc590c 100644 --- a/srcs/nginx/src/types/types.ts +++ b/srcs/nginx/src/types/types.ts @@ -32,3 +32,9 @@ export interface ClientMessage { paddle?: 'left' | 'right'; direction?: 'up' | 'down' | 'stop'; } + +export type Player = { + id: string; + name: string; + avatar: string | null; +}; From acc7adeabc14d856ff54cce4b93df2ac4759443f Mon Sep 17 00:00:00 2001 From: jmkko Date: Tue, 10 Feb 2026 08:30:30 +0100 Subject: [PATCH 3/9] feat(front): Add tournament component BREAKING_CHANGES: add components in one page to test layout info: i use mock data for the moment next: connect the component to the backend add navigation logic package-lock.json srcs/nginx/src/components/atoms/BracketLines.tsx srcs/nginx/src/components/atoms/CircleButton.tsx srcs/nginx/src/components/atoms/MatchNode.tsx srcs/nginx/src/components/atoms/NavDropDown.tsx srcs/nginx/src/components/atoms/PlayerCapsule.tsx srcs/nginx/src/components/atoms/TournamentList.tsx srcs/nginx/src/components/molecules/TournamentBracket.tsx srcs/nginx/src/pages/TournamentPage.tsx --- package-lock.json | 112 ++++++++++++++++-- .../src/components/atoms/BracketLines.tsx | 107 +++++++++++++++-- .../src/components/atoms/CircleButton.tsx | 5 +- srcs/nginx/src/components/atoms/MatchNode.tsx | 2 +- .../src/components/atoms/NavDropDown.tsx | 3 +- .../src/components/atoms/PlayerCapsule.tsx | 4 +- .../src/components/atoms/TournamentList.tsx | 49 +++++++- .../molecules/TournamentBracket.tsx | 61 +++++++--- srcs/nginx/src/pages/TournamentPage.tsx | 64 ++++++++-- 9 files changed, 345 insertions(+), 62 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8abe5076..25c0d5d3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -92,6 +92,7 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -227,7 +228,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -237,7 +238,7 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -271,7 +272,7 @@ "version": "7.29.0", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.29.0" @@ -362,7 +363,7 @@ "version": "7.29.0", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -714,7 +715,8 @@ "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.3.15.tgz", "integrity": "sha512-Cj++n1Mekf9ETfdc16TlDi+cDDQF0W7EcbyRHYOAeZdsAe8M/FJg18itDTSwyHfar2WIezawM9o0EKaRGVKygQ==", "devOptional": true, - "license": "Apache-2.0" + "license": "Apache-2.0", + "peer": true }, "node_modules/@electric-sql/pglite-socket": { "version": "0.0.20", @@ -1734,6 +1736,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "fastify-plugin": "^5.0.0", "json-schema-resolver": "^3.0.0", @@ -3739,6 +3742,7 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.11.tgz", "integrity": "sha512-/Af7O8r1frCVgOz0I62jWUtMohJ0/ZQU/ZoketltOJPZpnb17yoNc9BSoVuV9qlaIXJiPNOpsfq4ByFajSArNQ==", "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -3759,6 +3763,7 @@ "integrity": "sha512-KkiJeU6VbYbUOp5ITMIc7kBfqlYkKA5KhEHVrGMmUUMt7NeaZg65ojdPk+FtNrBAOXNVM5QM72jnADjM+XVRAQ==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -3875,6 +3880,7 @@ "integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.54.0", "@typescript-eslint/types": "8.54.0", @@ -4457,6 +4463,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4510,6 +4517,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -5113,6 +5121,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -5708,6 +5717,7 @@ "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", @@ -6557,6 +6567,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -7190,6 +7201,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "@fastify/ajv-compiler": "^4.0.5", "@fastify/error": "^4.0.0", @@ -8069,6 +8081,7 @@ "integrity": "sha512-eVkB/CYCCei7K2WElZW9yYQFWssG0DhaDhVvr7wy5jJ22K+ck8fWW0EsLpB0sITUTvPnc97+rrbQqIr5iqiy9Q==", "devOptional": true, "license": "MIT", + "peer": true, "engines": { "node": ">=16.9.0" } @@ -8150,6 +8163,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.28.4" }, @@ -9622,11 +9636,12 @@ } }, "node_modules/magicast": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz", - "integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==", - "devOptional": true, + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.1.tgz", + "integrity": "sha512-xrHS24IxaLrvuo613F719wvOIv9xPHFWQHuvGUBmPnCA/3MQxKI3b+r7n1jAoDHmsbC5bRhTZYR77invLAxVnw==", + "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", @@ -10252,7 +10267,8 @@ "version": "12.1.3", "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/optionator": { "version": "0.9.4", @@ -10593,6 +10609,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -10683,6 +10700,7 @@ "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -10710,6 +10728,7 @@ "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@prisma/config": "7.3.0", "@prisma/dev": "0.20.0", @@ -11093,6 +11112,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -11102,6 +11122,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -12019,7 +12040,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "devOptional": true, + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -12575,7 +12596,39 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/tinyrainbow": { + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "ode_modules/tinyrainbow": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", @@ -12888,6 +12941,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -13055,6 +13109,7 @@ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -13124,6 +13179,38 @@ } } }, + "node_modules/vite/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/vitest": { "version": "4.0.18", "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.18.tgz", @@ -13686,6 +13773,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/srcs/nginx/src/components/atoms/BracketLines.tsx b/srcs/nginx/src/components/atoms/BracketLines.tsx index d6178c2d..0e3187e1 100644 --- a/srcs/nginx/src/components/atoms/BracketLines.tsx +++ b/srcs/nginx/src/components/atoms/BracketLines.tsx @@ -1,17 +1,98 @@ -export function BracketLines() { +import { RefObject, useEffect, useState } from 'react'; + +export interface BracketConnection { + from: RefObject; + to: RefObject; +} + +interface Point { + x: number; + y: number; +} + +interface ComputedLine { + from: Point; + to: Point; +} + +interface BracketLinesProps { + // référentile des coordonnées + containerRef: RefObject; + connections: BracketConnection[]; +} + +// renvoi les coordonnées du centre de l'objet / div +// car getBoundingClientRect renvoie les coordonnées viewport du rectangle +// cette fontion utilitaire permet aux lignes de partir du centre des capsules +function centerOf(rect: DOMRect): Point { + return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 }; +} + +/* BracketLines est un composant de rendu SVG qui dessine dynamiquement des lignes + * entre des éléments du DOM, en restant synchronisé avec le layout réel + * (responsive, scroll, resize). + */ +export function BracketLines({ containerRef, connections }: BracketLinesProps) { + const [lines, setLines] = useState([]); + + useEffect(() => { + const compute = () => { + const container = containerRef.current; + if (!container) return; + // on récupère les coordonnées du container + const cRect = container.getBoundingClientRect(); + + const computed: ComputedLine[] = []; + + for (const { from, to } of connections) { + const aEl = from.current; + const bEl = to.current; + if (!aEl || !bEl) continue; + + const a = centerOf(aEl.getBoundingClientRect()); + const b = centerOf(bEl.getBoundingClientRect()); + + //convert viewport -> container local coords + computed.push({ + from: { x: a.x - cRect.left, y: a.y - cRect.top }, + to: { x: b.x - cRect.left, y: b.y - cRect.top }, + }); + } + + setLines(computed); + }; + + /*recalcul : au montage, au resize, au scroll (y compris scrolls internes)*/ + compute(); + + window.addEventListener('resize', compute); + window.addEventListener('scroll', compute, true); // capte scroll de conteneurs + // évite les fuite de mémoire + return () => { + window.removeEventListener('resize', compute); + window.removeEventListener('scroll', compute, true); + }; + }, [containerRef, connections]); + return ( - - {/* LEFT TO CENTER */} - - - - {/* RIGHT TO CENTER */} - - + + {lines.map((line, i) => { + // courbe de bézier cubique + const cx = (line.from.x + line.to.x) / 2; + + return ( + + ); + })} ); } diff --git a/srcs/nginx/src/components/atoms/CircleButton.tsx b/srcs/nginx/src/components/atoms/CircleButton.tsx index 615f53e7..270a0a61 100644 --- a/srcs/nginx/src/components/atoms/CircleButton.tsx +++ b/srcs/nginx/src/components/atoms/CircleButton.tsx @@ -21,8 +21,9 @@ export const CircleButton = ({ children }: CircleButtonProps) => ( rounded-full bg-slate-100/80 flex items-center justify-center - font-quantico - z-50 ${dropdownStyle} + font-quantico border border-cyan-300 + text-gray-700 + ${dropdownStyle} `} >

{children}

diff --git a/srcs/nginx/src/components/atoms/MatchNode.tsx b/srcs/nginx/src/components/atoms/MatchNode.tsx index dffa02bc..7a273b07 100644 --- a/srcs/nginx/src/components/atoms/MatchNode.tsx +++ b/srcs/nginx/src/components/atoms/MatchNode.tsx @@ -2,7 +2,7 @@ export function MatchNode({ label, highlight = false }: { label: string; highlig return (
diff --git a/srcs/nginx/src/components/atoms/PlayerCapsule.tsx b/srcs/nginx/src/components/atoms/PlayerCapsule.tsx index 05b5ae52..c76df40b 100644 --- a/srcs/nginx/src/components/atoms/PlayerCapsule.tsx +++ b/srcs/nginx/src/components/atoms/PlayerCapsule.tsx @@ -5,7 +5,9 @@ export function PlayerCapsule({ player }: { player: Player }) { return (
- {player.name} + + {player.name} +
); } diff --git a/srcs/nginx/src/components/atoms/TournamentList.tsx b/srcs/nginx/src/components/atoms/TournamentList.tsx index c902c792..cf730bec 100644 --- a/srcs/nginx/src/components/atoms/TournamentList.tsx +++ b/srcs/nginx/src/components/atoms/TournamentList.tsx @@ -7,7 +7,7 @@ export type Tournament = { createdAt: string; }; -type Props = { +type tournamentsProps = { tournaments: Tournament[]; onJoin: (id: string) => void; }; @@ -24,11 +24,11 @@ const statusColor: Record = { FINISHED: 'text-gray-500', }; -export function TournamentList({ tournaments, onJoin }: Props) { +export function TournamentTableDesktop({ tournaments, onJoin }: tournamentsProps) { return (
-

+

Tournois disponibles

@@ -85,3 +85,46 @@ export function TournamentList({ tournaments, onJoin }: Props) {
); } +/* Version for mobile because table can't have a good small Version + * here i use card + * */ +export function TournamentListMobile({ tournaments, onJoin }: tournamentsProps) { + return ( + <> + {tournaments.map((t) => ( +
+
+ {t.name} + + {t.players} / {t.maxPlayers} + +
+ +
+ + {t.status === 'WAITING' ? 'En attente' : 'En cours'} + + + {t.status === 'WAITING' ? ( + + ) : ( + Indisponible + )} +
+
+ ))} + + ); +} diff --git a/srcs/nginx/src/components/molecules/TournamentBracket.tsx b/srcs/nginx/src/components/molecules/TournamentBracket.tsx index bcf6e2a0..57e709aa 100644 --- a/srcs/nginx/src/components/molecules/TournamentBracket.tsx +++ b/srcs/nginx/src/components/molecules/TournamentBracket.tsx @@ -1,40 +1,71 @@ import { Player } from '../../types/types'; -import { BracketLines } from '../atoms/BracketLines'; +import { BracketConnection, BracketLines } from '../atoms/BracketLines'; import { PlayerCapsule } from '../atoms/PlayerCapsule'; import { MatchNode } from '../atoms/MatchNode'; +import { useRef } from 'react'; interface TournamentBracketProps { players: [Player, Player, Player, Player]; } -import Avatar from '../atoms/Avatar'; export function TournamentBracket({ players }: TournamentBracketProps) { const [p1, p2, p3, p4] = players; + const containerRef = useRef(null); + const p1Ref = useRef(null); + const p2Ref = useRef(null); + const p3Ref = useRef(null); + const p4Ref = useRef(null); + const semiLeftRef = useRef(null); + const semiRightRef = useRef(null); + const finalRef = useRef(null); + + const connections: BracketConnection[] = [ + { from: p1Ref, to: semiLeftRef }, + { from: p2Ref, to: semiLeftRef }, + { from: p3Ref, to: semiRightRef }, + { from: p4Ref, to: semiRightRef }, + { from: semiLeftRef, to: finalRef }, + { from: semiRightRef, to: finalRef }, + ]; return ( -
- {/* SVG LINES */} - +
+ - {/* CONTENT */} -
+
{/* LEFT */}
- - +
+ +
+
+ +
{/* CENTER */} -
- - - +
+
+ +
+ +
+ +
+ +
+ +
{/* RIGHT */}
- - +
+ +
+
+ +
diff --git a/srcs/nginx/src/pages/TournamentPage.tsx b/srcs/nginx/src/pages/TournamentPage.tsx index 214cf104..5bf62c45 100644 --- a/srcs/nginx/src/pages/TournamentPage.tsx +++ b/srcs/nginx/src/pages/TournamentPage.tsx @@ -2,9 +2,16 @@ import { CircleButton } from '../components/atoms/CircleButton'; import { Page } from '../components/organisms/PageContainer'; import { useTranslation } from 'react-i18next'; import { useEffect, useState } from 'react'; -import { TournamentList, Tournament } from '../components/atoms/TournamentList'; +import { + TournamentTableDesktop, + TournamentListMobile, + Tournament, +} from '../components/atoms/TournamentList'; import { TournamentBracket } from '../components/molecules/TournamentBracket'; import { Player } from '../types/types'; +import Background from '../components/atoms/Background'; +import Circle from '../components/atoms/Circle'; +import { NavBar } from '../components/molecules/NavBar'; const MOCK_TOURNAMENTS: Tournament[] = [ { @@ -36,6 +43,10 @@ const MOCK_PLAYERS: [Player, Player, Player, Player] = [ ] as const; // const login = async () => {}; +const colors = { + start: '#00ff9f', + end: '#0088ff', +}; export const TournamentPage = () => { // const [currentUser, formAction, isPending] = useActionState(login, {}); @@ -44,18 +55,45 @@ export const TournamentPage = () => { const participate = t('game.participate'); const create = t('game.create'); return ( - -
-
- {participate} - {create} +
+ + { +
+ +
+ } +
+
+ {participate} + {create} +
+
+ {/* console.log('Join tournament', id)} */} + {/* /> */} + + {/* Desktop */} +
+ console.log('Join tournament', id)} + /> +
+ + {/* Mobile */} +
+ console.log('Join tournament', id)} + />
-
- console.log('Join tournament', id)} - /> - - + +
); }; From 01a2df36095c26501f4a31e6b3fe153638b4f57f Mon Sep 17 00:00:00 2001 From: jhervoch Date: Tue, 10 Feb 2026 15:55:47 +0100 Subject: [PATCH 4/9] feat(front): improve TournamentBracket by adding online status and start adding translation for each element improve layout for mobile srcs/nginx/src/components/atoms/MatchNode.tsx srcs/nginx/src/components/atoms/PlayerCapsule.tsx srcs/nginx/src/components/atoms/TournamentList.tsx srcs/nginx/src/components/molecules/TournamentBracket.tsx srcs/nginx/src/locales/en/common.json srcs/nginx/src/locales/fr/common.json srcs/nginx/src/locales/tf/common.json srcs/nginx/src/pages/TournamentPage.tsx srcs/nginx/src/types/types.ts --- srcs/nginx/src/components/atoms/MatchNode.tsx | 54 +++++++++++++-- .../src/components/atoms/PlayerCapsule.tsx | 16 ++++- .../src/components/atoms/TournamentList.tsx | 66 ++++++++++--------- .../molecules/TournamentBracket.tsx | 23 +++++-- srcs/nginx/src/locales/en/common.json | 16 ++++- srcs/nginx/src/locales/fr/common.json | 20 +++++- srcs/nginx/src/locales/tf/common.json | 16 ++++- srcs/nginx/src/pages/TournamentPage.tsx | 12 ++-- srcs/nginx/src/types/types.ts | 3 + 9 files changed, 173 insertions(+), 53 deletions(-) diff --git a/srcs/nginx/src/components/atoms/MatchNode.tsx b/srcs/nginx/src/components/atoms/MatchNode.tsx index 7a273b07..46ae3f90 100644 --- a/srcs/nginx/src/components/atoms/MatchNode.tsx +++ b/srcs/nginx/src/components/atoms/MatchNode.tsx @@ -1,13 +1,59 @@ -export function MatchNode({ label, highlight = false }: { label: string; highlight?: boolean }) { +// export function MatchNode({ label, highlight = false }: { label: string; highlight?: boolean }) { +// return ( +//
+// {label} +//
+// ); +// } +import { MatchStatus } from '../../types/types'; +import { useTranslation } from 'react-i18next'; + +interface MatchNodeProps { + label: string; + status: MatchStatus; + highlight?: boolean; + onStart?: () => void; +} + +export function MatchNode({ label, status, highlight = false, onStart }: MatchNodeProps) { + const canStart = status === 'ready'; + const { t } = useTranslation(); + return (
- {label} + {/* Label */} + {label} + + {/* Start button */} +
); } diff --git a/srcs/nginx/src/components/atoms/PlayerCapsule.tsx b/srcs/nginx/src/components/atoms/PlayerCapsule.tsx index c76df40b..bdf518ff 100644 --- a/srcs/nginx/src/components/atoms/PlayerCapsule.tsx +++ b/srcs/nginx/src/components/atoms/PlayerCapsule.tsx @@ -2,9 +2,23 @@ import { Player } from '../../types/types'; import Avatar from './Avatar'; export function PlayerCapsule({ player }: { player: Player }) { + const isOnline = player.online === true; return (
- + {/* Avatar + status */} +
+ + + +
+ + {/* Name */} {player.name} diff --git a/srcs/nginx/src/components/atoms/TournamentList.tsx b/srcs/nginx/src/components/atoms/TournamentList.tsx index cf730bec..fd2350c7 100644 --- a/srcs/nginx/src/components/atoms/TournamentList.tsx +++ b/srcs/nginx/src/components/atoms/TournamentList.tsx @@ -1,3 +1,5 @@ +import { useTranslation } from 'react-i18next'; + export type Tournament = { id: string; name: string; @@ -13,9 +15,9 @@ type tournamentsProps = { }; const statusLabel: Record = { - WAITING: 'En attente', - IN_PROGRESS: 'En cours', - FINISHED: 'Terminé', + WAITING: 'game.waiting', + IN_PROGRESS: 'game.in_progress', + FINISHED: 'finished', }; const statusColor: Record = { @@ -25,47 +27,48 @@ const statusColor: Record = { }; export function TournamentTableDesktop({ tournaments, onJoin }: tournamentsProps) { + const { t } = useTranslation(); return ( -
+

- Tournois disponibles + {t('game.tournament_available')}

- - - - + + + + - {tournaments.map((t) => ( - - + {tournaments.map((tour) => ( + + - @@ -74,7 +77,7 @@ export function TournamentTableDesktop({ tournaments, onJoin }: tournamentsProps {tournaments.length === 0 && ( )} @@ -89,38 +92,39 @@ export function TournamentTableDesktop({ tournaments, onJoin }: tournamentsProps * here i use card * */ export function TournamentListMobile({ tournaments, onJoin }: tournamentsProps) { + const { t } = useTranslation(); return ( <> - {tournaments.map((t) => ( + {tournaments.map((tour) => (
- {t.name} + {tour.name} - {t.players} / {t.maxPlayers} + {tour.players} / {tour.maxPlayers}
- {t.status === 'WAITING' ? 'En attente' : 'En cours'} + {tour.status === 'WAITING' ? t('game.waiting') : t('game.in_progress')} - {t.status === 'WAITING' ? ( + {tour.status === 'WAITING' ? ( ) : ( - Indisponible + {t('game.unavailable')} )}
diff --git a/srcs/nginx/src/components/molecules/TournamentBracket.tsx b/srcs/nginx/src/components/molecules/TournamentBracket.tsx index 57e709aa..9cadc3a8 100644 --- a/srcs/nginx/src/components/molecules/TournamentBracket.tsx +++ b/srcs/nginx/src/components/molecules/TournamentBracket.tsx @@ -3,12 +3,14 @@ import { BracketConnection, BracketLines } from '../atoms/BracketLines'; import { PlayerCapsule } from '../atoms/PlayerCapsule'; import { MatchNode } from '../atoms/MatchNode'; import { useRef } from 'react'; +import { useTranslation } from 'react-i18next'; interface TournamentBracketProps { players: [Player, Player, Player, Player]; } export function TournamentBracket({ players }: TournamentBracketProps) { + const { t } = useTranslation(); const [p1, p2, p3, p4] = players; const containerRef = useRef(null); const p1Ref = useRef(null); @@ -29,7 +31,7 @@ export function TournamentBracket({ players }: TournamentBracketProps) { { from: semiRightRef, to: finalRef }, ]; return ( -
+
@@ -46,15 +48,28 @@ export function TournamentBracket({ players }: TournamentBracketProps) { {/* CENTER */}
- + console.log(`startSemiFinal('left')`)} + />
- + console.log(`startFinal()`)} + />
- + console.log(`startSemiFinal('right')`)} + />
diff --git a/srcs/nginx/src/locales/en/common.json b/srcs/nginx/src/locales/en/common.json index 5f96caaf..dcab228b 100644 --- a/srcs/nginx/src/locales/en/common.json +++ b/srcs/nginx/src/locales/en/common.json @@ -71,6 +71,20 @@ "tournament": "Tournament", "game": "Game", "participate": "Participate", - "create": "Create" + "create": "Create", + "tournament_available": "Tournament available", + "name": "Pseudo", + "players": "Nb players", + "status": "Status", + "action": "Action", + "join": "Join", + "unavailable": "Unavailable", + "waiting": "Waiting", + "in_progress": "In progress", + "finished": "Finished", + "no_tournament": "No tournament", + "final": "Final", + "semi_final": "Semi-Final", + "start": "Start" } } diff --git a/srcs/nginx/src/locales/fr/common.json b/srcs/nginx/src/locales/fr/common.json index dad9ae64..59eaced3 100644 --- a/srcs/nginx/src/locales/fr/common.json +++ b/srcs/nginx/src/locales/fr/common.json @@ -68,9 +68,23 @@ "termsOfService": "Conditions d'utilisation" }, "game": { - "tournament": "tournoi", - "game": "jeu", + "tournament": "Tournoi", + "game": "Jeu", "participate": "Participer", - "create": "Créer" + "create": "Créer", + "tournament_available": "Tournois disponibles", + "name": "Pseudo", + "players": "Nb joueurs", + "status": "Statut", + "action": "Action", + "join": "Rejoindre", + "unavailable": "Indisponible", + "waiting": "En attente", + "in_progress": "En cours", + "finished": "Terminé", + "no_tournament": "Aucun tournoi disponible", + "final": "Finale", + "semi_final": "Demi-Finale", + "start": "Jouer" } } diff --git a/srcs/nginx/src/locales/tf/common.json b/srcs/nginx/src/locales/tf/common.json index 6e833cc8..35a21441 100644 --- a/srcs/nginx/src/locales/tf/common.json +++ b/srcs/nginx/src/locales/tf/common.json @@ -71,6 +71,20 @@ "tournament": "Vendetta", "game": "La mort ou la vie", "participate": "Tu cherches des embrouilles!", - "create": "Je vous mets au défi!" + "create": "Je vous mets au défi!", + "tournament_available": "Vendetta disponibles", + "name": "Blaz", + "players": "Nb truands", + "status": "Statut", + "action": "Action", + "join": "Rejoindre", + "unavailable": "Indisponible", + "waiting": "En attente", + "in_progress": "Contrat en cours", + "finished": "Terminé", + "no_tournament": "Aucune vendetta disponible", + "final": "Combat final", + "semi_final": "Pour les petits joueurs", + "start": "C'est parti" } } diff --git a/srcs/nginx/src/pages/TournamentPage.tsx b/srcs/nginx/src/pages/TournamentPage.tsx index 5bf62c45..5a91202a 100644 --- a/srcs/nginx/src/pages/TournamentPage.tsx +++ b/srcs/nginx/src/pages/TournamentPage.tsx @@ -36,10 +36,10 @@ interface LoginRegisterPageProps { } const MOCK_PLAYERS: [Player, Player, Player, Player] = [ - { id: '1', name: 'johnny', avatar: null }, - { id: '2', name: 'eddy', avatar: null }, - { id: '3', name: 'khaled', avatar: null }, - { id: '4', name: 'danny', avatar: null }, + { id: '1', name: 'johnny', avatar: null, online: true }, + { id: '2', name: 'eddy', avatar: null, online: false }, + { id: '3', name: 'khaled', avatar: null, online: true }, + { id: '4', name: 'danny', avatar: null, online: false }, ] as const; // const login = async () => {}; @@ -73,10 +73,6 @@ export const TournamentPage = () => { {create}
- {/* console.log('Join tournament', id)} */} - {/* /> */} {/* Desktop */}
diff --git a/srcs/nginx/src/types/types.ts b/srcs/nginx/src/types/types.ts index 1dbc590c..41311d91 100644 --- a/srcs/nginx/src/types/types.ts +++ b/srcs/nginx/src/types/types.ts @@ -37,4 +37,7 @@ export type Player = { id: string; name: string; avatar: string | null; + online?: boolean; }; + +export type MatchStatus = 'pending' | 'ready' | 'running' | 'finished'; From 8a373a607783e036fb4964790758662a7c06f37b Mon Sep 17 00:00:00 2001 From: jmkko Date: Wed, 11 Feb 2026 07:47:04 +0100 Subject: [PATCH 5/9] feat(front): Add component FriendsList update: checking the translation of all the componets i did checking css layout ans style next: Integrate components into navigation logic package-lock.json srcs/nginx/src/api/auth-api.ts srcs/nginx/src/components/atoms/CircleButton.tsx srcs/nginx/src/components/atoms/MatchNode.tsx srcs/nginx/src/components/atoms/PlayerCapsule.tsx srcs/nginx/src/components/atoms/TournamentList.tsx srcs/nginx/src/components/molecules/FriendsList.tsx srcs/nginx/src/components/molecules/TournamentBracket.tsx srcs/nginx/src/locales/en/common.json srcs/nginx/src/locales/fr/common.json srcs/nginx/src/locales/tf/common.json srcs/nginx/src/pages/TournamentPage.tsx srcs/nginx/src/types/types.ts --- package-lock.json | 76 +++---------------- srcs/nginx/src/api/auth-api.ts | 2 +- .../src/components/atoms/CircleButton.tsx | 5 +- srcs/nginx/src/components/atoms/MatchNode.tsx | 2 +- .../src/components/atoms/PlayerCapsule.tsx | 9 ++- .../src/components/atoms/TournamentList.tsx | 6 +- .../src/components/molecules/FriendsList.tsx | 62 +++++++++++++++ .../molecules/TournamentBracket.tsx | 12 ++- srcs/nginx/src/locales/en/common.json | 4 +- srcs/nginx/src/locales/fr/common.json | 4 +- srcs/nginx/src/locales/tf/common.json | 6 +- srcs/nginx/src/pages/TournamentPage.tsx | 27 +++++-- srcs/nginx/src/types/types.ts | 5 +- 13 files changed, 131 insertions(+), 89 deletions(-) create mode 100644 srcs/nginx/src/components/molecules/FriendsList.tsx diff --git a/package-lock.json b/package-lock.json index 25c0d5d3..dc763e70 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12596,39 +12596,7 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "ode_modules/tinyrainbow": { + "node_modules/tinyrainbow": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", @@ -13179,38 +13147,6 @@ } } }, - "node_modules/vite/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/vite/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/vitest": { "version": "4.0.18", "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.18.tgz", @@ -13791,6 +13727,16 @@ "zod": "^3.25.0 || ^4.0.0" } }, + "ode_modules/tinyrainbow": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", + "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", + "extraneous": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "srcs/auth": { "name": "@transcendence/auth", "version": "1.0.0", diff --git a/srcs/nginx/src/api/auth-api.ts b/srcs/nginx/src/api/auth-api.ts index 05ed92b5..b26c92d9 100644 --- a/srcs/nginx/src/api/auth-api.ts +++ b/srcs/nginx/src/api/auth-api.ts @@ -72,5 +72,5 @@ export const authApi = { withCredentials: true, }); return data; - }:w + }, }; diff --git a/srcs/nginx/src/components/atoms/CircleButton.tsx b/srcs/nginx/src/components/atoms/CircleButton.tsx index 270a0a61..90ed8335 100644 --- a/srcs/nginx/src/components/atoms/CircleButton.tsx +++ b/srcs/nginx/src/components/atoms/CircleButton.tsx @@ -10,10 +10,7 @@ export const CircleButton = ({ children }: CircleButtonProps) => ( +
{/* Avatar + status */}
diff --git a/srcs/nginx/src/components/atoms/TournamentList.tsx b/srcs/nginx/src/components/atoms/TournamentList.tsx index fd2350c7..91f8d4ca 100644 --- a/srcs/nginx/src/components/atoms/TournamentList.tsx +++ b/srcs/nginx/src/components/atoms/TournamentList.tsx @@ -29,8 +29,8 @@ const statusColor: Record = { export function TournamentTableDesktop({ tournaments, onJoin }: tournamentsProps) { const { t } = useTranslation(); return ( -
-
+
+

{t('game.tournament_available')}

@@ -63,7 +63,7 @@ export function TournamentTableDesktop({ tournaments, onJoin }: tournamentsProps {tour.status === 'WAITING' ? ( diff --git a/srcs/nginx/src/components/molecules/FriendsList.tsx b/srcs/nginx/src/components/molecules/FriendsList.tsx new file mode 100644 index 00000000..e34f06d9 --- /dev/null +++ b/srcs/nginx/src/components/molecules/FriendsList.tsx @@ -0,0 +1,62 @@ +import { useState } from 'react'; +import { Player } from 'src/types/types'; +import { PlayerCapsule } from '../atoms/PlayerCapsule'; +import { useTranslation } from 'react-i18next'; + +interface FriendsListProps { + friends: Player[]; +} + +export const FriendsList = ({ friends }: FriendsListProps) => { + const [open, setOpen] = useState(false); + const { t } = useTranslation(); + + return ( +
+
+ {/* Header / Toggle */} + + + {/* List */} + {open && ( +
+ {friends.map((friend) => ( + + ))} +
+ )} +
+
+ ); +}; diff --git a/srcs/nginx/src/components/molecules/TournamentBracket.tsx b/srcs/nginx/src/components/molecules/TournamentBracket.tsx index 9cadc3a8..1cb362fe 100644 --- a/srcs/nginx/src/components/molecules/TournamentBracket.tsx +++ b/srcs/nginx/src/components/molecules/TournamentBracket.tsx @@ -21,6 +21,7 @@ export function TournamentBracket({ players }: TournamentBracketProps) { const semiLeftRef = useRef(null); const semiRightRef = useRef(null); const finalRef = useRef(null); + const littleFinalRef = useRef(null); const connections: BracketConnection[] = [ { from: p1Ref, to: semiLeftRef }, @@ -28,7 +29,8 @@ export function TournamentBracket({ players }: TournamentBracketProps) { { from: p3Ref, to: semiRightRef }, { from: p4Ref, to: semiRightRef }, { from: semiLeftRef, to: finalRef }, - { from: semiRightRef, to: finalRef }, + { from: littleFinalRef, to: finalRef }, + { from: semiRightRef, to: littleFinalRef }, ]; return (
@@ -63,6 +65,14 @@ export function TournamentBracket({ players }: TournamentBracketProps) { onStart={() => console.log(`startFinal()`)} />
+
+ console.log(`startLittleFinal()`)} + /> +
{}; const colors = { @@ -51,6 +54,13 @@ const colors = { export const TournamentPage = () => { // const [currentUser, formAction, isPending] = useActionState(login, {}); const { t } = useTranslation(); + const MOCK_PLAYERS: [Player, Player, Player, Player] = [ + { id: '1', name: 'johnny', avatar: null, online: true, status: 'connected' }, + { id: '2', name: 'eddy', avatar: null, online: false, status: 'connected' }, + { id: '3', name: 'khaled', avatar: null, online: true, status: 'connected' }, + createWaitingPlayer('waiting...'), + // { id: '4', name: 'danny', avatar: null, online: false }, + ] as const; const title = t('game.tournament').toUpperCase(); const participate = t('game.participate'); const create = t('game.create'); @@ -67,6 +77,7 @@ export const TournamentPage = () => {
} + {MOCK_PLAYERS.length > 0 && }
{participate} diff --git a/srcs/nginx/src/types/types.ts b/srcs/nginx/src/types/types.ts index 41311d91..e15a0e15 100644 --- a/srcs/nginx/src/types/types.ts +++ b/srcs/nginx/src/types/types.ts @@ -33,11 +33,14 @@ export interface ClientMessage { direction?: 'up' | 'down' | 'stop'; } +export type PlayerStatus = 'waiting' | 'connected'; + export type Player = { id: string; name: string; avatar: string | null; - online?: boolean; + online: boolean; + status: PlayerStatus; }; export type MatchStatus = 'pending' | 'ready' | 'running' | 'finished'; From 0f61257ce4aad4a07bdf70a30605ddad52edf7aa Mon Sep 17 00:00:00 2001 From: jhervoch Date: Wed, 11 Feb 2026 09:40:41 +0100 Subject: [PATCH 6/9] chore: regenerate clean lockfile --- package-lock.json | 322 +++++++++++++++++++++------------------------- 1 file changed, 150 insertions(+), 172 deletions(-) diff --git a/package-lock.json b/package-lock.json index dc763e70..b4bc4157 100644 --- a/package-lock.json +++ b/package-lock.json @@ -92,7 +92,6 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -228,7 +227,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -238,7 +237,7 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -272,7 +271,7 @@ "version": "7.29.0", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/types": "^7.29.0" @@ -363,7 +362,7 @@ "version": "7.29.0", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -715,8 +714,7 @@ "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.3.15.tgz", "integrity": "sha512-Cj++n1Mekf9ETfdc16TlDi+cDDQF0W7EcbyRHYOAeZdsAe8M/FJg18itDTSwyHfar2WIezawM9o0EKaRGVKygQ==", "devOptional": true, - "license": "Apache-2.0", - "peer": true + "license": "Apache-2.0" }, "node_modules/@electric-sql/pglite-socket": { "version": "0.0.20", @@ -1722,9 +1720,9 @@ } }, "node_modules/@fastify/swagger": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/@fastify/swagger/-/swagger-9.6.1.tgz", - "integrity": "sha512-fKlpJqFMWoi4H3EdUkDaMteEYRCfQMEkK0HJJ0eaf4aRlKd8cbq0pVkOfXDXmtvMTXYcnx3E+l023eFDBsA1HA==", + "version": "9.7.0", + "resolved": "https://registry.npmjs.org/@fastify/swagger/-/swagger-9.7.0.tgz", + "integrity": "sha512-Vp1SC1GC2Hrkd3faFILv86BzUNyFz5N4/xdExqtCgkGASOzn/x+eMe4qXIGq7cdT6wif/P/oa6r1Ruqx19paZA==", "funding": [ { "type": "github", @@ -1736,7 +1734,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "fastify-plugin": "^5.0.0", "json-schema-resolver": "^3.0.0", @@ -1806,9 +1803,9 @@ } }, "node_modules/@fastify/swagger-ui/node_modules/glob": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.1.tgz", - "integrity": "sha512-B7U/vJpE3DkJ5WXTgTpTRN63uV42DseiXXKMwG14LQBXmsdeIoHAPbU/MEo6II0k5ED74uc2ZGTC6MwHFQhF6w==", + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.2.tgz", + "integrity": "sha512-035InabNu/c1lW0tzPhAgapKctblppqsKKG9ZaNzbr+gXwWMjXoiyGSyB9sArzrjG7jY+zntRq5ZSUYemrnWVQ==", "license": "BlueOak-1.0.0", "dependencies": { "minimatch": "^10.1.2", @@ -2563,6 +2560,18 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/@prisma/config/node_modules/magicast": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", + "extraneous": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", + "source-map-js": "^1.2.0" + } + }, "node_modules/@prisma/config/node_modules/readdirp": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", @@ -2708,9 +2717,9 @@ } }, "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.2", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.2.tgz", - "integrity": "sha512-izyXV/v+cHiRfozX62W9htOAvwMo4/bXKDrQ+vom1L1qRuexPock/7VZDAhnpHCLNejd3NJ6hiab+tO0D44Rgw==", + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz", + "integrity": "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==", "dev": true, "license": "MIT" }, @@ -3077,13 +3086,13 @@ } }, "node_modules/@scalar/fastify-api-reference": { - "version": "1.44.14", - "resolved": "https://registry.npmjs.org/@scalar/fastify-api-reference/-/fastify-api-reference-1.44.14.tgz", - "integrity": "sha512-1Gmfh7z9hcvC7dhq4jhIruaaSGZDSdEBk6OLO6LpQklNpzi0PQGBVDFetEbvA4fPLcMfw/j8sXv9lTnVfUc5+w==", + "version": "1.44.16", + "resolved": "https://registry.npmjs.org/@scalar/fastify-api-reference/-/fastify-api-reference-1.44.16.tgz", + "integrity": "sha512-UwKWu4WV6r6ORB/TfcjKLJchWSi+OPgjcK0pXm6gdNhbJuZrsCuzbtPO7mfMYj96mw5IWjLZfatCu/lIHmKoUA==", "license": "MIT", "dependencies": { "@scalar/core": "0.3.37", - "@scalar/openapi-parser": "0.24.8", + "@scalar/openapi-parser": "0.24.9", "@scalar/openapi-types": "0.5.3", "fastify-plugin": "^4.5.1", "github-slugger": "^2.0.0" @@ -3108,9 +3117,9 @@ } }, "node_modules/@scalar/json-magic": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/@scalar/json-magic/-/json-magic-0.10.0.tgz", - "integrity": "sha512-7QCaKjhPCNBe5vieI0zZcxOupI+kk2PoWL/tuxB7ROLbDOIdmeZ8x1FrZqdtamDPi+zsQGx+kc/cvTDz7GdTKw==", + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@scalar/json-magic/-/json-magic-0.11.0.tgz", + "integrity": "sha512-1zBseDDEPkKlAVd9lT1HlK9Nefeh0YEE+pcmyDL3J5derIZn9UYXAFecdkeXMdjDtWDgcrkmWCrHhpoT7zVKdQ==", "license": "MIT", "dependencies": { "@scalar/helpers": "0.2.11", @@ -3121,13 +3130,13 @@ } }, "node_modules/@scalar/openapi-parser": { - "version": "0.24.8", - "resolved": "https://registry.npmjs.org/@scalar/openapi-parser/-/openapi-parser-0.24.8.tgz", - "integrity": "sha512-1ZFC/nNbH/e/rriDgfsXCcr23WphvqHVxX8V90qhR8c9QWXNb0cyNXl3hNjvtTB7Lg7O9aS22BUZ14gabCWvRQ==", + "version": "0.24.9", + "resolved": "https://registry.npmjs.org/@scalar/openapi-parser/-/openapi-parser-0.24.9.tgz", + "integrity": "sha512-uqpwt6ZQJQu4c3CvMsJiXMUj32113yrclsDC31hlL33vEUS5JU9dCYfY27oLSCVoKl8R8KihlnEcbfRnH/O/GA==", "license": "MIT", "dependencies": { "@scalar/helpers": "0.2.11", - "@scalar/json-magic": "0.10.0", + "@scalar/json-magic": "0.11.0", "@scalar/openapi-types": "0.5.3", "@scalar/openapi-upgrader": "0.1.8", "ajv": "^8.17.1", @@ -3738,11 +3747,10 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.10.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.11.tgz", - "integrity": "sha512-/Af7O8r1frCVgOz0I62jWUtMohJ0/ZQU/ZoketltOJPZpnb17yoNc9BSoVuV9qlaIXJiPNOpsfq4ByFajSArNQ==", + "version": "24.10.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.13.tgz", + "integrity": "sha512-oH72nZRfDv9lADUBSo104Aq7gPHpQZc4BTx38r9xf9pg5LfP6EzSyH2n7qFmmxRQXh7YlUXODcYsg6PuTDSxGg==", "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -3763,7 +3771,6 @@ "integrity": "sha512-KkiJeU6VbYbUOp5ITMIc7kBfqlYkKA5KhEHVrGMmUUMt7NeaZg65ojdPk+FtNrBAOXNVM5QM72jnADjM+XVRAQ==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -3836,17 +3843,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.54.0.tgz", - "integrity": "sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.55.0.tgz", + "integrity": "sha512-1y/MVSz0NglV1ijHC8OT49mPJ4qhPYjiK08YUQVbIOyu+5k862LKUHFkpKHWu//zmr7hDR2rhwUm6gnCGNmGBQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.54.0", - "@typescript-eslint/type-utils": "8.54.0", - "@typescript-eslint/utils": "8.54.0", - "@typescript-eslint/visitor-keys": "8.54.0", + "@typescript-eslint/scope-manager": "8.55.0", + "@typescript-eslint/type-utils": "8.55.0", + "@typescript-eslint/utils": "8.55.0", + "@typescript-eslint/visitor-keys": "8.55.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" @@ -3859,7 +3866,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.54.0", + "@typescript-eslint/parser": "^8.55.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } @@ -3875,17 +3882,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.54.0.tgz", - "integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.55.0.tgz", + "integrity": "sha512-4z2nCSBfVIMnbuu8uinj+f0o4qOeggYJLbjpPHka3KH1om7e+H9yLKTYgksTaHcGco+NClhhY2vyO3HsMH1RGw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.54.0", - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/typescript-estree": "8.54.0", - "@typescript-eslint/visitor-keys": "8.54.0", + "@typescript-eslint/scope-manager": "8.55.0", + "@typescript-eslint/types": "8.55.0", + "@typescript-eslint/typescript-estree": "8.55.0", + "@typescript-eslint/visitor-keys": "8.55.0", "debug": "^4.4.3" }, "engines": { @@ -3901,14 +3907,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.54.0.tgz", - "integrity": "sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.55.0.tgz", + "integrity": "sha512-zRcVVPFUYWa3kNnjaZGXSu3xkKV1zXy8M4nO/pElzQhFweb7PPtluDLQtKArEOGmjXoRjnUZ29NjOiF0eCDkcQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.54.0", - "@typescript-eslint/types": "^8.54.0", + "@typescript-eslint/tsconfig-utils": "^8.55.0", + "@typescript-eslint/types": "^8.55.0", "debug": "^4.4.3" }, "engines": { @@ -3923,14 +3929,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.54.0.tgz", - "integrity": "sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.55.0.tgz", + "integrity": "sha512-fVu5Omrd3jeqeQLiB9f1YsuK/iHFOwb04bCtY4BSCLgjNbOD33ZdV6KyEqplHr+IlpgT0QTZ/iJ+wT7hvTx49Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/visitor-keys": "8.54.0" + "@typescript-eslint/types": "8.55.0", + "@typescript-eslint/visitor-keys": "8.55.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3941,9 +3947,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.54.0.tgz", - "integrity": "sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.55.0.tgz", + "integrity": "sha512-1R9cXqY7RQd7WuqSN47PK9EDpgFUK3VqdmbYrvWJZYDd0cavROGn+74ktWBlmJ13NXUQKlZ/iAEQHI/V0kKe0Q==", "dev": true, "license": "MIT", "engines": { @@ -3958,15 +3964,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.54.0.tgz", - "integrity": "sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.55.0.tgz", + "integrity": "sha512-x1iH2unH4qAt6I37I2CGlsNs+B9WGxurP2uyZLRz6UJoZWDBx9cJL1xVN/FiOmHEONEg6RIufdvyT0TEYIgC5g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/typescript-estree": "8.54.0", - "@typescript-eslint/utils": "8.54.0", + "@typescript-eslint/types": "8.55.0", + "@typescript-eslint/typescript-estree": "8.55.0", + "@typescript-eslint/utils": "8.55.0", "debug": "^4.4.3", "ts-api-utils": "^2.4.0" }, @@ -3983,9 +3989,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.54.0.tgz", - "integrity": "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.55.0.tgz", + "integrity": "sha512-ujT0Je8GI5BJWi+/mMoR0wxwVEQaxM+pi30xuMiJETlX80OPovb2p9E8ss87gnSVtYXtJoU9U1Cowcr6w2FE0w==", "dev": true, "license": "MIT", "engines": { @@ -3997,16 +4003,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.54.0.tgz", - "integrity": "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.55.0.tgz", + "integrity": "sha512-EwrH67bSWdx/3aRQhCoxDaHM+CrZjotc2UCCpEDVqfCE+7OjKAGWNY2HsCSTEVvWH2clYQK8pdeLp42EVs+xQw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.54.0", - "@typescript-eslint/tsconfig-utils": "8.54.0", - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/visitor-keys": "8.54.0", + "@typescript-eslint/project-service": "8.55.0", + "@typescript-eslint/tsconfig-utils": "8.55.0", + "@typescript-eslint/types": "8.55.0", + "@typescript-eslint/visitor-keys": "8.55.0", "debug": "^4.4.3", "minimatch": "^9.0.5", "semver": "^7.7.3", @@ -4051,16 +4057,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.54.0.tgz", - "integrity": "sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.55.0.tgz", + "integrity": "sha512-BqZEsnPGdYpgyEIkDC1BadNY8oMwckftxBT+C8W0g1iKPdeqKZBtTfnvcq0nf60u7MkjFO8RBvpRGZBPw4L2ow==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.54.0", - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/typescript-estree": "8.54.0" + "@typescript-eslint/scope-manager": "8.55.0", + "@typescript-eslint/types": "8.55.0", + "@typescript-eslint/typescript-estree": "8.55.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4075,13 +4081,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.54.0.tgz", - "integrity": "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.55.0.tgz", + "integrity": "sha512-AxNRwEie8Nn4eFS1FzDMJWIISMGoXMb037sgCBJ3UR6o0fQTzr2tqN9WT+DkWJPhIdQCfV7T6D387566VtnCJA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/types": "8.55.0", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -4093,16 +4099,16 @@ } }, "node_modules/@vitejs/plugin-react": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.3.tgz", - "integrity": "sha512-NVUnA6gQCl8jfoYqKqQU5Clv0aPw14KkZYCsX6T9Lfu9slI0LOU10OTwFHS/WmptsMMpshNd/1tuWsHQ2Uk+cg==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.4.tgz", + "integrity": "sha512-VIcFLdRi/VYRU8OL/puL7QXMYafHmqOnwTZY50U1JPlCNj30PxCMx65c494b1K9be9hX83KVt0+gTEwTWLqToA==", "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.29.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", - "@rolldown/pluginutils": "1.0.0-rc.2", + "@rolldown/pluginutils": "1.0.0-rc.3", "@types/babel__core": "^7.20.5", "react-refresh": "^0.18.0" }, @@ -4463,7 +4469,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4517,7 +4522,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -4952,13 +4956,13 @@ } }, "node_modules/axios": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.4.tgz", - "integrity": "sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg==", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz", + "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, @@ -5121,7 +5125,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -5717,7 +5720,6 @@ "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", @@ -6567,7 +6569,6 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -7201,7 +7202,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "@fastify/ajv-compiler": "^4.0.5", "@fastify/error": "^4.0.0", @@ -7548,13 +7548,13 @@ } }, "node_modules/framer-motion": { - "version": "12.33.0", - "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.33.0.tgz", - "integrity": "sha512-ca8d+rRPcDP5iIF+MoT3WNc0KHJMjIyFAbtVLvM9eA7joGSpeqDfiNH/kCs1t4CHi04njYvWyj0jS4QlEK/rJQ==", + "version": "12.34.0", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.34.0.tgz", + "integrity": "sha512-+/H49owhzkzQyxtn7nZeF4kdH++I2FWrESQ184Zbcw5cEqNHYkE5yxWxcTLSj5lNx3NWdbIRy5FHqUvetD8FWg==", "dev": true, "license": "MIT", "dependencies": { - "motion-dom": "^12.33.0", + "motion-dom": "^12.34.0", "motion-utils": "^12.29.2", "tslib": "^2.4.0" }, @@ -8076,12 +8076,11 @@ } }, "node_modules/hono": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.8.tgz", - "integrity": "sha512-eVkB/CYCCei7K2WElZW9yYQFWssG0DhaDhVvr7wy5jJ22K+ck8fWW0EsLpB0sITUTvPnc97+rrbQqIr5iqiy9Q==", + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.9.tgz", + "integrity": "sha512-Eaw2YTGM6WOxA6CXbckaEvslr2Ne4NFsKrvc0v97JD5awbmeBLO5w9Ho9L9kmKonrwF9RJlW6BxT1PVv/agBHQ==", "devOptional": true, "license": "MIT", - "peer": true, "engines": { "node": ">=16.9.0" } @@ -8163,7 +8162,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.28.4" }, @@ -8868,9 +8866,9 @@ } }, "node_modules/jackspeak": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.2.1.tgz", - "integrity": "sha512-GPBXyfcZSGujjddPeA+V34bW70ZJT7jzCEbloVasSH4yjiqWqXHX8iZQtZdVbOhc5esSeAIuiSmMutRZQB/olg==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.2.3.tgz", + "integrity": "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==", "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^9.0.0" @@ -9636,12 +9634,11 @@ } }, "node_modules/magicast": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.1.tgz", - "integrity": "sha512-xrHS24IxaLrvuo613F719wvOIv9xPHFWQHuvGUBmPnCA/3MQxKI3b+r7n1jAoDHmsbC5bRhTZYR77invLAxVnw==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz", + "integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", @@ -9847,9 +9844,9 @@ } }, "node_modules/motion-dom": { - "version": "12.33.0", - "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.33.0.tgz", - "integrity": "sha512-XRPebVypsl0UM+7v0Hr8o9UAj0S2djsQWRdHBd5iVouVpMrQqAI0C/rDAT3QaYnXnHuC5hMcwDHCboNeyYjPoQ==", + "version": "12.34.0", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.34.0.tgz", + "integrity": "sha512-Lql3NuEcScRDxTAO6GgUsRHBZOWI/3fnMlkMcH5NftzcN37zJta+bpbMAV9px4Nj057TuvRooMK7QrzMCgtz6Q==", "dev": true, "license": "MIT", "dependencies": { @@ -10267,8 +10264,7 @@ "version": "12.1.3", "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/optionator": { "version": "0.9.4", @@ -10432,9 +10428,9 @@ } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "11.2.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.5.tgz", - "integrity": "sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw==", + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", + "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", "license": "BlueOak-1.0.0", "engines": { "node": "20 || >=22" @@ -10487,9 +10483,9 @@ } }, "node_modules/pino": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/pino/-/pino-10.3.0.tgz", - "integrity": "sha512-0GNPNzHXBKw6U/InGe79A3Crzyk9bcSyObF9/Gfo9DLEf5qj5RF50RSjsu0W1rZ6ZqRGdzDFCRBQvi9/rSGPtA==", + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/pino/-/pino-10.3.1.tgz", + "integrity": "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==", "license": "MIT", "dependencies": { "@pinojs/redact": "^0.4.0", @@ -10609,7 +10605,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -10700,7 +10695,6 @@ "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -10728,7 +10722,6 @@ "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@prisma/config": "7.3.0", "@prisma/dev": "0.20.0", @@ -11112,7 +11105,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -11122,7 +11114,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -12018,9 +12009,9 @@ } }, "node_modules/sonic-boom": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.0.tgz", - "integrity": "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", + "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==", "license": "MIT", "dependencies": { "atomic-sleep": "^1.0.0" @@ -12040,7 +12031,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, + "devOptional": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -12815,9 +12806,9 @@ } }, "node_modules/type-fest": { - "version": "5.4.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.4.3.tgz", - "integrity": "sha512-AXSAQJu79WGc79/3e9/CR77I/KQgeY1AhNvcShIH4PTcGYyC4xv6H4R4AUOwkPS5799KlVDAu8zExeCrkGquiA==", + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.4.4.tgz", + "integrity": "sha512-JnTrzGu+zPV3aXIUhnyWJj4z/wigMsdYajGLIYakqyOW1nPllzXEJee0QQbHj+CTIQtXGlAjuK0UY+2xTyjVAw==", "license": "(MIT OR CC0-1.0)", "dependencies": { "tagged-tag": "^1.0.0" @@ -12909,7 +12900,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -12919,16 +12909,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.54.0.tgz", - "integrity": "sha512-CKsJ+g53QpsNPqbzUsfKVgd3Lny4yKZ1pP4qN3jdMOg/sisIDLGyDMezycquXLE5JsEU0wp3dGNdzig0/fmSVQ==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.55.0.tgz", + "integrity": "sha512-HE4wj+r5lmDVS9gdaN0/+iqNvPZwGfnJ5lZuz7s5vLlg9ODw0bIiiETaios9LvFI1U94/VBXGm3CB2Y5cNFMpw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.54.0", - "@typescript-eslint/parser": "8.54.0", - "@typescript-eslint/typescript-estree": "8.54.0", - "@typescript-eslint/utils": "8.54.0" + "@typescript-eslint/eslint-plugin": "8.55.0", + "@typescript-eslint/parser": "8.55.0", + "@typescript-eslint/typescript-estree": "8.55.0", + "@typescript-eslint/utils": "8.55.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -13077,7 +13067,6 @@ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -13249,9 +13238,9 @@ } }, "node_modules/webpack": { - "version": "5.105.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.0.tgz", - "integrity": "sha512-gX/dMkRQc7QOMzgTe6KsYFM7DxeIONQSui1s0n/0xht36HvrgbxtM1xBlgx596NbpHuQU8P7QpKwrZYwUX48nw==", + "version": "5.105.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.1.tgz", + "integrity": "sha512-Gdj3X74CLJJ8zy4URmK42W7wTZUJrqL+z8nyGEr4dTN0kb3nVs+ZvjbTOqRYPD7qX4tUmwyHL9Q9K6T1seW6Yw==", "dev": true, "license": "MIT", "dependencies": { @@ -13709,7 +13698,6 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } @@ -13727,16 +13715,6 @@ "zod": "^3.25.0 || ^4.0.0" } }, - "ode_modules/tinyrainbow": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", - "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", - "extraneous": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, "srcs/auth": { "name": "@transcendence/auth", "version": "1.0.0", @@ -13896,9 +13874,9 @@ } }, "srcs/game/node_modules/@types/node": { - "version": "20.19.32", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.32.tgz", - "integrity": "sha512-Ez8QE4DMfhjjTsES9K2dwfV258qBui7qxUsoaixZDiTzbde4U12e1pXGNu/ECsUIOi5/zoCxAQxIhQnaUQ2VvA==", + "version": "20.19.33", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.33.tgz", + "integrity": "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==", "dev": true, "license": "MIT", "dependencies": { @@ -14004,14 +13982,14 @@ "postcss": "^8.5.6", "tailwindcss": "^4.1.18", "typescript": "^5.0.0", - "vite": "^7.3.0", + "vite": "^7.3.1", "webpack": "^5.103.0" } }, "srcs/nginx/node_modules/@types/node": { - "version": "20.19.32", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.32.tgz", - "integrity": "sha512-Ez8QE4DMfhjjTsES9K2dwfV258qBui7qxUsoaixZDiTzbde4U12e1pXGNu/ECsUIOi5/zoCxAQxIhQnaUQ2VvA==", + "version": "20.19.33", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.33.tgz", + "integrity": "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==", "dev": true, "license": "MIT", "dependencies": { From 20badb64c7fd814e36708ba7964bf4a9301797e9 Mon Sep 17 00:00:00 2001 From: jhervoch Date: Wed, 11 Feb 2026 13:15:11 +0100 Subject: [PATCH 7/9] feat: transfert home too school --- package-lock.json | 14 +------ srcs/nginx/src/App.tsx | 3 +- .../src/components/atoms/CircleButton.tsx | 5 ++- .../src/components/molecules/FriendsList.tsx | 2 +- .../components/organisms/TournamentLayout.tsx | 37 +++++++++++++++++++ srcs/nginx/src/pages/TournamentPage.tsx | 11 ------ srcs/nginx/src/router/TournamentRoutes.tsx | 19 ++++++++++ 7 files changed, 63 insertions(+), 28 deletions(-) create mode 100644 srcs/nginx/src/components/organisms/TournamentLayout.tsx create mode 100644 srcs/nginx/src/router/TournamentRoutes.tsx diff --git a/package-lock.json b/package-lock.json index b4bc4157..62636209 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2560,18 +2560,6 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/@prisma/config/node_modules/magicast": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", - "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", - "extraneous": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.25.4", - "@babel/types": "^7.25.4", - "source-map-js": "^1.2.0" - } - }, "node_modules/@prisma/config/node_modules/readdirp": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", @@ -13982,7 +13970,7 @@ "postcss": "^8.5.6", "tailwindcss": "^4.1.18", "typescript": "^5.0.0", - "vite": "^7.3.1", + "vite": "^7.3.0", "webpack": "^5.103.0" } }, diff --git a/srcs/nginx/src/App.tsx b/srcs/nginx/src/App.tsx index 370986b7..f2673036 100644 --- a/srcs/nginx/src/App.tsx +++ b/srcs/nginx/src/App.tsx @@ -4,6 +4,7 @@ import { LoginPage } from './pages/LoginRegisterPage'; import { useAuth } from './providers/AuthProvider'; import { AnimationPage } from './pages/AnimationPage'; import { TournamentPage } from './pages/TournamentPage'; +import TournamentRoutes from './router/TournamentRoutes'; const GuestRoute = ({ children }: { children: React.ReactNode }) => { const { user, isLoggedIn, isAuthChecked } = useAuth(); @@ -55,7 +56,7 @@ export const App = () => { /> }> }> - }> + } /> ); diff --git a/srcs/nginx/src/components/atoms/CircleButton.tsx b/srcs/nginx/src/components/atoms/CircleButton.tsx index 90ed8335..3a56aaf9 100644 --- a/srcs/nginx/src/components/atoms/CircleButton.tsx +++ b/srcs/nginx/src/components/atoms/CircleButton.tsx @@ -12,8 +12,9 @@ export const CircleButton = ({ children }: CircleButtonProps) => ( whileTap={{ scale: 0.95, color: '#ff0088' }} transition={{ duration: 0.3 }} className={` - basis-50 - h-56 m-10 p-6 + h-56 p-6 + scale-75 + md:scale-100 md:m-10 aspect-square rounded-full bg-slate-100/80 diff --git a/srcs/nginx/src/components/molecules/FriendsList.tsx b/srcs/nginx/src/components/molecules/FriendsList.tsx index e34f06d9..98ac660f 100644 --- a/srcs/nginx/src/components/molecules/FriendsList.tsx +++ b/srcs/nginx/src/components/molecules/FriendsList.tsx @@ -37,7 +37,7 @@ export const FriendsList = ({ friends }: FriendsListProps) => { text-gray-700 mb-4 " > - {t('game.friends')} + {t('game.friends')}  + + { +
+ +
+ } + {MOCK_PLAYERS.length > 0 && } + + +
+
+ ); +} diff --git a/srcs/nginx/src/pages/TournamentPage.tsx b/srcs/nginx/src/pages/TournamentPage.tsx index ae0dce09..45281d41 100644 --- a/srcs/nginx/src/pages/TournamentPage.tsx +++ b/srcs/nginx/src/pages/TournamentPage.tsx @@ -46,21 +46,10 @@ export function createWaitingPlayer(label: string): Player { } // const login = async () => {}; -const colors = { - start: '#00ff9f', - end: '#0088ff', -}; export const TournamentPage = () => { // const [currentUser, formAction, isPending] = useActionState(login, {}); const { t } = useTranslation(); - const MOCK_PLAYERS: [Player, Player, Player, Player] = [ - { id: '1', name: 'johnny', avatar: null, online: true, status: 'connected' }, - { id: '2', name: 'eddy', avatar: null, online: false, status: 'connected' }, - { id: '3', name: 'khaled', avatar: null, online: true, status: 'connected' }, - createWaitingPlayer('waiting...'), - // { id: '4', name: 'danny', avatar: null, online: false }, - ] as const; const title = t('game.tournament').toUpperCase(); const participate = t('game.participate'); const create = t('game.create'); diff --git a/srcs/nginx/src/router/TournamentRoutes.tsx b/srcs/nginx/src/router/TournamentRoutes.tsx new file mode 100644 index 00000000..438eeda9 --- /dev/null +++ b/srcs/nginx/src/router/TournamentRoutes.tsx @@ -0,0 +1,19 @@ +import { Routes, Route } from 'react-router-dom'; +import TournamentLayout from '../layouts/TournamentLayout'; +import TournamentMenuPage from '../pages/TournamentMenuPage'; +import TournamentsListPage from '../pages/TournamentsListPage'; +import TournamentCreatePage from '../pages/TournamentCreatePage'; +import TournamentPage from '../pages/TournamentPage'; + +export default function TournamentRoutes() { + return ( + + }> + } /> + } /> + } /> + } /> + + + ); +} From 3358ca1c48761f54d8cdb86a9b00450363e1cd05 Mon Sep 17 00:00:00 2001 From: jmkko Date: Thu, 12 Feb 2026 15:48:42 +0100 Subject: [PATCH 8/9] feat(front): Improved visual appearance of components and their adaptation to different screen sizes. Separation of components within their respective pages. Added navigation between tournament pages. package-lock.json srcs/nginx/src/App.tsx srcs/nginx/src/components/atoms/BracketLines.tsx srcs/nginx/src/components/atoms/CircleButton.tsx srcs/nginx/src/components/atoms/MatchNode.tsx srcs/nginx/src/components/atoms/TournamentList.tsx srcs/nginx/src/components/molecules/NavBar.tsx srcs/nginx/src/components/organisms/TournamentLayout.tsx srcs/nginx/src/locales/en/common.json srcs/nginx/src/locales/fr/common.json srcs/nginx/src/locales/tf/common.json srcs/nginx/src/pages/TournamentCreatePage.tsx srcs/nginx/src/pages/TournamentMenuPage.tsx srcs/nginx/src/pages/TournamentPage.tsx srcs/nginx/src/pages/TournamentsListPage.tsx srcs/nginx/src/router/TournamentRoutes.tsx --- package-lock.json | 38 ++++++-- srcs/nginx/src/App.tsx | 1 - .../src/components/atoms/BracketLines.tsx | 9 +- .../src/components/atoms/CircleButton.tsx | 16 ++-- srcs/nginx/src/components/atoms/MatchNode.tsx | 13 --- .../src/components/atoms/TournamentList.tsx | 3 + .../nginx/src/components/molecules/NavBar.tsx | 2 +- .../components/organisms/TournamentLayout.tsx | 8 +- srcs/nginx/src/locales/en/common.json | 4 +- srcs/nginx/src/locales/fr/common.json | 4 +- srcs/nginx/src/locales/tf/common.json | 4 +- srcs/nginx/src/pages/TournamentCreatePage.tsx | 70 ++++++++++++++ srcs/nginx/src/pages/TournamentMenuPage.tsx | 15 +++ srcs/nginx/src/pages/TournamentPage.tsx | 95 +++---------------- srcs/nginx/src/pages/TournamentsListPage.tsx | 50 ++++++++++ srcs/nginx/src/router/TournamentRoutes.tsx | 5 +- 16 files changed, 218 insertions(+), 119 deletions(-) create mode 100644 srcs/nginx/src/pages/TournamentCreatePage.tsx create mode 100644 srcs/nginx/src/pages/TournamentMenuPage.tsx create mode 100644 srcs/nginx/src/pages/TournamentsListPage.tsx diff --git a/package-lock.json b/package-lock.json index 62636209..222ed89e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -92,6 +92,7 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -227,7 +228,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -237,7 +238,7 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -271,7 +272,7 @@ "version": "7.29.0", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.29.0" @@ -362,7 +363,7 @@ "version": "7.29.0", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -714,7 +715,8 @@ "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.3.15.tgz", "integrity": "sha512-Cj++n1Mekf9ETfdc16TlDi+cDDQF0W7EcbyRHYOAeZdsAe8M/FJg18itDTSwyHfar2WIezawM9o0EKaRGVKygQ==", "devOptional": true, - "license": "Apache-2.0" + "license": "Apache-2.0", + "peer": true }, "node_modules/@electric-sql/pglite-socket": { "version": "0.0.20", @@ -1734,6 +1736,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "fastify-plugin": "^5.0.0", "json-schema-resolver": "^3.0.0", @@ -3739,6 +3742,7 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.13.tgz", "integrity": "sha512-oH72nZRfDv9lADUBSo104Aq7gPHpQZc4BTx38r9xf9pg5LfP6EzSyH2n7qFmmxRQXh7YlUXODcYsg6PuTDSxGg==", "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -3759,6 +3763,7 @@ "integrity": "sha512-KkiJeU6VbYbUOp5ITMIc7kBfqlYkKA5KhEHVrGMmUUMt7NeaZg65ojdPk+FtNrBAOXNVM5QM72jnADjM+XVRAQ==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -3875,6 +3880,7 @@ "integrity": "sha512-4z2nCSBfVIMnbuu8uinj+f0o4qOeggYJLbjpPHka3KH1om7e+H9yLKTYgksTaHcGco+NClhhY2vyO3HsMH1RGw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.55.0", "@typescript-eslint/types": "8.55.0", @@ -4457,6 +4463,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4510,6 +4517,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -5113,6 +5121,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -5708,6 +5717,7 @@ "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", @@ -6557,6 +6567,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -7190,6 +7201,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "@fastify/ajv-compiler": "^4.0.5", "@fastify/error": "^4.0.0", @@ -8069,6 +8081,7 @@ "integrity": "sha512-Eaw2YTGM6WOxA6CXbckaEvslr2Ne4NFsKrvc0v97JD5awbmeBLO5w9Ho9L9kmKonrwF9RJlW6BxT1PVv/agBHQ==", "devOptional": true, "license": "MIT", + "peer": true, "engines": { "node": ">=16.9.0" } @@ -8150,6 +8163,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.28.4" }, @@ -9627,6 +9641,7 @@ "integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", @@ -10252,7 +10267,8 @@ "version": "12.1.3", "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/optionator": { "version": "0.9.4", @@ -10593,6 +10609,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -10683,6 +10700,7 @@ "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -10710,6 +10728,7 @@ "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@prisma/config": "7.3.0", "@prisma/dev": "0.20.0", @@ -11093,6 +11112,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -11102,6 +11122,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -12019,7 +12040,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "devOptional": true, + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -12888,6 +12909,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -13055,6 +13077,7 @@ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -13686,6 +13709,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/srcs/nginx/src/App.tsx b/srcs/nginx/src/App.tsx index f2673036..dfb14392 100644 --- a/srcs/nginx/src/App.tsx +++ b/srcs/nginx/src/App.tsx @@ -3,7 +3,6 @@ import { ProfilePage } from './pages/ProfilePage'; import { LoginPage } from './pages/LoginRegisterPage'; import { useAuth } from './providers/AuthProvider'; import { AnimationPage } from './pages/AnimationPage'; -import { TournamentPage } from './pages/TournamentPage'; import TournamentRoutes from './router/TournamentRoutes'; const GuestRoute = ({ children }: { children: React.ReactNode }) => { diff --git a/srcs/nginx/src/components/atoms/BracketLines.tsx b/srcs/nginx/src/components/atoms/BracketLines.tsx index 0e3187e1..27e627b9 100644 --- a/srcs/nginx/src/components/atoms/BracketLines.tsx +++ b/srcs/nginx/src/components/atoms/BracketLines.tsx @@ -16,14 +16,15 @@ interface ComputedLine { } interface BracketLinesProps { - // référentile des coordonnées + // coordinate reference containerRef: RefObject; connections: BracketConnection[]; } -// renvoi les coordonnées du centre de l'objet / div -// car getBoundingClientRect renvoie les coordonnées viewport du rectangle -// cette fontion utilitaire permet aux lignes de partir du centre des capsules +/* renvoi les coordonnées du centre de l'objet / div + * car getBoundingClientRect renvoie les coordonnées viewport du rectangle + * cette fontion utilitaire permet aux lignes de partir du centre des capsules + */ function centerOf(rect: DOMRect): Point { return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 }; } diff --git a/srcs/nginx/src/components/atoms/CircleButton.tsx b/srcs/nginx/src/components/atoms/CircleButton.tsx index 3a56aaf9..f82d29a1 100644 --- a/srcs/nginx/src/components/atoms/CircleButton.tsx +++ b/srcs/nginx/src/components/atoms/CircleButton.tsx @@ -1,17 +1,18 @@ import { motion } from 'framer-motion'; +import React from 'react'; -interface CircleButtonProps { +interface CircleButtonProps extends React.ButtonHTMLAttributes { children?: React.ReactNode; } const dropdownStyle = 'shadow-[0_10px_10px_1px_rgba(205,205,205,0.4)] '; -export const CircleButton = ({ children }: CircleButtonProps) => ( - ( + ); diff --git a/srcs/nginx/src/components/atoms/MatchNode.tsx b/srcs/nginx/src/components/atoms/MatchNode.tsx index 063280dc..67853c90 100644 --- a/srcs/nginx/src/components/atoms/MatchNode.tsx +++ b/srcs/nginx/src/components/atoms/MatchNode.tsx @@ -1,16 +1,3 @@ -// export function MatchNode({ label, highlight = false }: { label: string; highlight?: boolean }) { -// return ( -//
-// {label} -//
-// ); -// } import { MatchStatus } from '../../types/types'; import { useTranslation } from 'react-i18next'; diff --git a/srcs/nginx/src/components/atoms/TournamentList.tsx b/srcs/nginx/src/components/atoms/TournamentList.tsx index 91f8d4ca..66e114ef 100644 --- a/srcs/nginx/src/components/atoms/TournamentList.tsx +++ b/srcs/nginx/src/components/atoms/TournamentList.tsx @@ -26,6 +26,9 @@ const statusColor: Record = { FINISHED: 'text-gray-500', }; +/* Version for computer screen + * with a html table + */ export function TournamentTableDesktop({ tournaments, onJoin }: tournamentsProps) { const { t } = useTranslation(); return ( diff --git a/srcs/nginx/src/components/molecules/NavBar.tsx b/srcs/nginx/src/components/molecules/NavBar.tsx index 390c8528..c4995546 100644 --- a/srcs/nginx/src/components/molecules/NavBar.tsx +++ b/srcs/nginx/src/components/molecules/NavBar.tsx @@ -20,7 +20,7 @@ export const NavBar = () => { const playItems = [ { label: t('navbar.play_friend'), to: '/friends' }, { label: t('navbar.play_ai'), to: '/ai' }, - { label: t('navbar.play_tournament'), to: '/tournament' }, + { label: t('navbar.play_tournament'), to: '/tournaments' }, ]; const statsItems = [ diff --git a/srcs/nginx/src/components/organisms/TournamentLayout.tsx b/srcs/nginx/src/components/organisms/TournamentLayout.tsx index 2f7d673f..7bcca19b 100644 --- a/srcs/nginx/src/components/organisms/TournamentLayout.tsx +++ b/srcs/nginx/src/components/organisms/TournamentLayout.tsx @@ -8,6 +8,8 @@ const colors = { start: '#00ff9f', end: '#0088ff', }; + +/*This component is the architecture of all tournament pages.*/ export default function TournamentLayout() { const MOCK_PLAYERS: [Player, Player, Player, Player] = [ { id: '1', name: 'johnny', avatar: null, online: true, status: 'connected' }, @@ -28,9 +30,11 @@ export default function TournamentLayout() {
} - {MOCK_PLAYERS.length > 0 && } +
+ {MOCK_PLAYERS.length > 0 && } - + +
); diff --git a/srcs/nginx/src/locales/en/common.json b/srcs/nginx/src/locales/en/common.json index 1efb8fa7..be3207ed 100644 --- a/srcs/nginx/src/locales/en/common.json +++ b/srcs/nginx/src/locales/en/common.json @@ -87,6 +87,8 @@ "semi_final": "Semi-Final", "little_final": "Little-Finale", "start": "Start", - "friends": "Friends" + "friends": "Friends", + "waitiing": "Waiting...", + "creating": "Creating tournament..." } } diff --git a/srcs/nginx/src/locales/fr/common.json b/srcs/nginx/src/locales/fr/common.json index 820e4cc1..00879694 100644 --- a/srcs/nginx/src/locales/fr/common.json +++ b/srcs/nginx/src/locales/fr/common.json @@ -87,6 +87,8 @@ "semi_final": "Demi-Finale", "little_final": "Petite-Finale", "start": "Jouer", - "friends": "Amis" + "friends": "Amis", + "waitiing": "Attente...", + "creating": "Création du tournoi..." } } diff --git a/srcs/nginx/src/locales/tf/common.json b/srcs/nginx/src/locales/tf/common.json index e79a5fc7..90cf9465 100644 --- a/srcs/nginx/src/locales/tf/common.json +++ b/srcs/nginx/src/locales/tf/common.json @@ -87,6 +87,8 @@ "semi_final": "On prends du Galon", "little_final": "Les loosers c'est ici", "start": "C'est parti", - "friends": "Les potes" + "friends": "Les potes", + "waitiing": "On t'attend...", + "creating": "Création de la vendetta..." } } diff --git a/srcs/nginx/src/pages/TournamentCreatePage.tsx b/srcs/nginx/src/pages/TournamentCreatePage.tsx new file mode 100644 index 00000000..351b52ad --- /dev/null +++ b/srcs/nginx/src/pages/TournamentCreatePage.tsx @@ -0,0 +1,70 @@ +import { useEffect, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { useTranslation } from 'react-i18next'; + +/* This component displays a loader until the tournament is created by the backend. + * Use `useEffect` is used to redirect the user. + */ +export default function TournamentCreatePage() { + const navigate = useNavigate(); + const [error, setError] = useState(null); + const { t } = useTranslation(); + + useEffect(() => { + async function createTournament() { + try { + // A remplacer par le vrai appel API + const res = await fakeCreateTournament(); + + navigate(`/tournaments/${res.id}`); + } catch (err) { + setError('Failed to create tournament'); + } + } + + createTournament(); + }, [navigate]); + + return ( +
+ {!error ? ( + <> + +

{t('game.creating')}

+ + ) : ( +
{error}
+ )} +
+ ); +} + +/* + * Fake API simulation + * This function should be replaced by a proper call to the backend. + */ + +async function fakeCreateTournament() { + return new Promise<{ id: string }>((resolve) => { + setTimeout(() => { + resolve({ id: '42' }); + }, 1500); + }); +} + +/* Tailwindcss allows you to create animations very quickly and easily + */ +function SpinLoader() { + return ( +
+ {/* Outer animated ring */} +
+ + {/* Inner pulse */} +
+ + {/* Glow effect */} +
+
+ ); +} diff --git a/srcs/nginx/src/pages/TournamentMenuPage.tsx b/srcs/nginx/src/pages/TournamentMenuPage.tsx new file mode 100644 index 00000000..aa7bc0fc --- /dev/null +++ b/srcs/nginx/src/pages/TournamentMenuPage.tsx @@ -0,0 +1,15 @@ +import { CircleButton } from '../components/atoms/CircleButton'; +import { useNavigate } from 'react-router-dom'; +import { useTranslation } from 'react-i18next'; + +export default function TournamentMenuPage() { + const navigate = useNavigate(); + const { t } = useTranslation(); + + return ( +
+ navigate('list')}>{t('game.participate')} + navigate('create')}>{t('game.create')} +
+ ); +} diff --git a/srcs/nginx/src/pages/TournamentPage.tsx b/srcs/nginx/src/pages/TournamentPage.tsx index 45281d41..ba816d0b 100644 --- a/srcs/nginx/src/pages/TournamentPage.tsx +++ b/srcs/nginx/src/pages/TournamentPage.tsx @@ -1,40 +1,13 @@ -import { CircleButton } from '../components/atoms/CircleButton'; -import { Page } from '../components/organisms/PageContainer'; import { useTranslation } from 'react-i18next'; -import { useEffect, useState } from 'react'; -import { - TournamentTableDesktop, - TournamentListMobile, - Tournament, -} from '../components/atoms/TournamentList'; import { TournamentBracket } from '../components/molecules/TournamentBracket'; -import Background from '../components/atoms/Background'; -import Circle from '../components/atoms/Circle'; -import { NavBar } from '../components/molecules/NavBar'; import { Player } from '../types/types'; -import { FriendsList } from '../components/molecules/FriendsList'; -const MOCK_TOURNAMENTS: Tournament[] = [ - { - id: '1', - name: 'Spin Cup #42', - players: 2, - maxPlayers: 4, - status: 'WAITING', - createdAt: '2026-02-01', - }, - { - id: '2', - name: 'Weekly Pong', - players: 4, - maxPlayers: 4, - status: 'IN_PROGRESS', - createdAt: '2026-02-03', - }, -]; -interface LoginRegisterPageProps { - isRegister: boolean; -} +/* The principle of the tournament page: + * The creator is displayed first, then the players join sequentially. + * The three other slots are tournament participation slots; + * they are initially initialized by this function, + * and the "waiting" status corresponds to that slot. + */ export function createWaitingPlayer(label: string): Player { return { id: 'waiting', @@ -45,51 +18,13 @@ export function createWaitingPlayer(label: string): Player { }; } -// const login = async () => {}; - -export const TournamentPage = () => { - // const [currentUser, formAction, isPending] = useActionState(login, {}); +export default function TournamentPage() { const { t } = useTranslation(); - const title = t('game.tournament').toUpperCase(); - const participate = t('game.participate'); - const create = t('game.create'); - return ( -
- - { -
- -
- } - {MOCK_PLAYERS.length > 0 && } -
-
- {participate} - {create} -
-
- - {/* Desktop */} -
- console.log('Join tournament', id)} - /> -
- - {/* Mobile */} -
- console.log('Join tournament', id)} - /> -
-
-
- ); -}; + const MOCK_PLAYERS: [Player, Player, Player, Player] = [ + { id: '1', name: 'johnny', avatar: null, online: true, status: 'connected' }, + { id: '2', name: 'eddy', avatar: null, online: false, status: 'connected' }, + { id: '3', name: 'khaled', avatar: null, online: true, status: 'connected' }, + createWaitingPlayer(t('game.waitiing')), + ] as const; + return ; +} diff --git a/srcs/nginx/src/pages/TournamentsListPage.tsx b/srcs/nginx/src/pages/TournamentsListPage.tsx new file mode 100644 index 00000000..b2984e67 --- /dev/null +++ b/srcs/nginx/src/pages/TournamentsListPage.tsx @@ -0,0 +1,50 @@ +import { + TournamentTableDesktop, + TournamentListMobile, + Tournament, +} from '../components/atoms/TournamentList'; +import { useNavigate } from 'react-router-dom'; + +const MOCK_TOURNAMENTS: Tournament[] = [ + { + id: '1', + name: 'Spin Cup #42', + players: 2, + maxPlayers: 4, + status: 'WAITING', + createdAt: '2026-02-01', + }, + { + id: '2', + name: 'Weekly Pong', + players: 4, + maxPlayers: 4, + status: 'IN_PROGRESS', + createdAt: '2026-02-03', + }, +]; + +/* + * This component links to two other components depending on the media because + * tables do not display correctly on mobile devices. + */ +export default function TournamentsListPage() { + const navigate = useNavigate(); + return ( + <> +
+ navigate(`/tournaments/${id}`)} + /> +
+ +
+ navigate(`/tournaments/${id}`)} + /> +
+ + ); +} diff --git a/srcs/nginx/src/router/TournamentRoutes.tsx b/srcs/nginx/src/router/TournamentRoutes.tsx index 438eeda9..33bb881b 100644 --- a/srcs/nginx/src/router/TournamentRoutes.tsx +++ b/srcs/nginx/src/router/TournamentRoutes.tsx @@ -1,10 +1,13 @@ import { Routes, Route } from 'react-router-dom'; -import TournamentLayout from '../layouts/TournamentLayout'; +import TournamentLayout from '../components/organisms/TournamentLayout'; import TournamentMenuPage from '../pages/TournamentMenuPage'; import TournamentsListPage from '../pages/TournamentsListPage'; import TournamentCreatePage from '../pages/TournamentCreatePage'; import TournamentPage from '../pages/TournamentPage'; +/* + * simplified page management with React Routes + */ export default function TournamentRoutes() { return ( From 270aca4ed680b4d170184e91974a278af463a023 Mon Sep 17 00:00:00 2001 From: jmtth Date: Fri, 13 Feb 2026 18:09:20 +0100 Subject: [PATCH 9/9] feat(game): Add SQLite database BREAKING_CHANGES: add three table in game.db match, tournament, tournament_player add request for adding and udating database next: add route and controller to interact with database package-lock.json srcs/.env.game.example srcs/game/package.json srcs/game/src/controllers/game.controller.ts srcs/game/src/core/game.database.ts srcs/game/src/routes/game.routes.ts srcs/game/src/types/game.dto.ts --- package-lock.json | 2 + srcs/.env.game.example | 2 + srcs/game/package.json | 2 + srcs/game/src/controllers/game.controller.ts | 5 + srcs/game/src/core/game.database.ts | 140 +++++++++++++++++++ srcs/game/src/routes/game.routes.ts | 2 + srcs/game/src/types/game.dto.ts | 14 ++ 7 files changed, 167 insertions(+) create mode 100644 srcs/game/src/core/game.database.ts create mode 100644 srcs/game/src/types/game.dto.ts diff --git a/package-lock.json b/package-lock.json index 222ed89e..1ab35e17 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13874,11 +13874,13 @@ "dependencies": { "@fastify/websocket": "^11.0.0", "alea": "^1.0.1", + "better-sqlite3": "^12.4.1", "fastify": "^5.6.2", "simplex-noise": "^4.0.3", "ws": "^8.18.0" }, "devDependencies": { + "@types/better-sqlite3": "^7.6.13", "@types/node": "^20.14.10", "@types/ws": "^8.5.12", "tsx": "^4.16.2", diff --git a/srcs/.env.game.example b/srcs/.env.game.example index e69de29b..7f783e02 100644 --- a/srcs/.env.game.example +++ b/srcs/.env.game.example @@ -0,0 +1,2 @@ + +GAME_DB_PATH=/data/game.db diff --git a/srcs/game/package.json b/srcs/game/package.json index 52e48870..97634a0c 100644 --- a/srcs/game/package.json +++ b/srcs/game/package.json @@ -9,6 +9,7 @@ }, "dependencies": { "alea": "^1.0.1", + "better-sqlite3": "^12.4.1", "fastify": "^5.6.2", "@fastify/websocket": "^11.0.0", "simplex-noise": "^4.0.3", @@ -16,6 +17,7 @@ }, "devDependencies": { "@types/node": "^20.14.10", + "@types/better-sqlite3": "^7.6.13", "@types/ws": "^8.5.12", "tsx": "^4.16.2", "typescript": "^5.5.3" diff --git a/srcs/game/src/controllers/game.controller.ts b/srcs/game/src/controllers/game.controller.ts index ec626af8..6a8ed03f 100644 --- a/srcs/game/src/controllers/game.controller.ts +++ b/srcs/game/src/controllers/game.controller.ts @@ -117,3 +117,8 @@ export async function webSocketConnect( // } handleClientMessage.call(this, socket, sessionId); } + +export async function newTournament() { + // const tournament_id = createTournament(); + // return reply.code(200).send(tournament_id); +} diff --git a/srcs/game/src/core/game.database.ts b/srcs/game/src/core/game.database.ts new file mode 100644 index 00000000..11e4e3af --- /dev/null +++ b/srcs/game/src/core/game.database.ts @@ -0,0 +1,140 @@ +import Database from 'better-sqlite3'; +import fs from 'fs'; +import path from 'path'; +import { MatchDTO } from '../types/game.dto.js'; + +// DB path +const DEFAULT_DIR = path.join(process.cwd(), 'data'); +const DB_PATH = process.env.GAME_DB_PATH || path.join(DEFAULT_DIR, 'game.db'); + +// Check dir +try { + fs.mkdirSync(path.dirname(DB_PATH), { recursive: true }); +} catch (err) { + const e: any = new Error( + `Failed to ensure DB directory: ${(err as any)?.message || String(err)}`, + ); + throw e; +} + +export const db = new Database(DB_PATH); +console.log('Using SQLite file:', DB_PATH); + +// Create table +try { + db.exec(` +CREATE TABLE IF NOT EXISTS match( + id INTEGER PRIMARY KEY AUTOINCREMENT, + tournament_id INTEGER, -- NULL if free match + player1 INTEGER NOT NULL, + player2 INTEGER NOT NULL, + score_player1 INTEGER NOT NULL DEFAULT 0, + score_player2 INTEGER NOT NULL DEFAULT 0, + winner_id INTEGER NOT NULL, + round TEXT, --NULL | SEMI_1 | SEMI_2 | LITTLE_FINAL | FINAL + created_at INTEGER NOT NULL +); + +CREATE TABLE IF NOT EXISTS tournament( + id INTEGER PRIMARY KEY AUTOINCREMENT, + creator_id INTEGER NOT NULL, + status TEXT NOT NULL DEFAULT 'PENDING', -- PENDING | STARTED | FINISHED + created_at INTEGER NOT NULL +); + +CREATE TABLE IF NOT EXISTS tournament_player( + tournament_id INTEGER NOT NULL, + player_id INTEGER NOT NULL, + final_position INTEGER, + PRIMARY KEY (tournament_id, player_id) +); + +CREATE INDEX IF NOT EXISTS idx_match_tournament +ON match(tournament_id); + +CREATE INDEX IF NOT EXISTS idx_tournament_player_tid +ON tournament_player(tournament_id); +`); +} catch (err) { + const e: any = new Error( + `Failed to initialize DB schema: ${(err as any)?.message || String(err)}`, + ); + throw e; +} + +const addMatchStmt = db.prepare(` +INSERT INTO match(tournament_id, player1, player2, score_player1, score_player2, winner_id, created_at) +VALUES (?,?,?,?,?,?,?) +`); + +const createTournamentStmt = db.prepare(` +INSERT INTO tournament(creator_id, created_at) +VALUES (?,?) +`); + +const addPlayerTournamentStmt = db.prepare(` +INSERT INTO tournament_player(player_id, tournament_id) +VALUES(?,?) +`); + +const addPlayerPositionTournamentStmt = db.prepare(` +UPDATE tournament_player +SET + final_position = ? +WHERE tournament_id = ? and player_id = ? + `); + +export function addMatch(match: MatchDTO): number { + try { + const idmatch = addMatchStmt.run( + match.tournament_id, + match.player1, + match.player2, + match.score_player1, + match.score_player2, + match.winner_id, + match.created_at, + ); + return Number(idmatch.lastInsertRowid); + } catch (err: any) { + const error: any = new Error(`Error during Match storage: ${err?.message || err}`); + error.code = 'DB_INSERT_MATCH_ERR'; + throw error; + } +} + +export function createTournament(player: number): number { + try { + const idtournament = createTournamentStmt.run(player, Date.now()); + return Number(idtournament.lastInsertRowid); + } catch (err: unknown) { + const message = err instanceof Error ? err.message : String(err); + const error = new Error(`Tournament creation failed: ${message}`) as Error & { code: string }; + error.code = 'DB_INSERT_TOURNAMENT_ERR'; + throw error; + } +} + +export function addPlayerTournament(player: number, tournament: number) { + try { + addPlayerTournamentStmt.run(player, tournament); + } catch (err: unknown) { + const message = err instanceof Error ? err.message : String(err); + const error = new Error(`Add Player to a tournament failed: ${message}`) as Error & { + code: string; + }; + error.code = 'DB_UPDATE_TOURNAMENT_ERROR'; + throw error; + } +} + +export function addPlayerPositionTournament(player: number, position: number, tournament: number) { + try { + addPlayerPositionTournamentStmt.run(position, tournament, player); + } catch (err: unknown) { + const message = err instanceof Error ? err.message : String(err); + const error = new Error(`Add player position failed: ${message}`) as Error & { code: string }; + error.code = 'DB_UPDATE_PLAYER_POSITION'; + throw error; + } +} diff --git a/srcs/game/src/routes/game.routes.ts b/srcs/game/src/routes/game.routes.ts index 9cef7404..172e8ea6 100644 --- a/srcs/game/src/routes/game.routes.ts +++ b/srcs/game/src/routes/game.routes.ts @@ -5,6 +5,7 @@ import { newGameSession, healthCheck, gameSettings, + newTournament, } from '../controllers/game.controller.js'; export async function gameRoutes(app: FastifyInstance) { @@ -13,4 +14,5 @@ export async function gameRoutes(app: FastifyInstance) { app.post('/create-session', newGameSession); app.get('/health', healthCheck); app.get('/:sessionId', { websocket: true }, webSocketConnect); + app.get('/create-tournament', newTournament); } diff --git a/srcs/game/src/types/game.dto.ts b/srcs/game/src/types/game.dto.ts new file mode 100644 index 00000000..a4b0298f --- /dev/null +++ b/srcs/game/src/types/game.dto.ts @@ -0,0 +1,14 @@ +/* =========================== + * Match DB + * =========================== */ +export interface MatchDTO { + id: number; + tournament_id: number | null; + player1: number; + player2: number; + score_player1: number; + score_player2: number; + winner_id: number; + round: string | null; + created_at: number; +}
NomJoueursStatutAction{t('game.name')}{t('game.players')}{t('game.status')}{t('game.action')}
{t.name}
{tour.name} - {t.players} / {t.maxPlayers} + {tour.players} / {tour.maxPlayers} - {statusLabel[t.status]} + + {t(statusLabel[tour.status])} - {t.status === 'WAITING' ? ( + {tour.status === 'WAITING' ? ( ) : ( - Indisponible + {t('game.unavailable')} )}
- Aucun tournoi disponible + {t('game.no_tournament')}