Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
294 changes: 159 additions & 135 deletions package-lock.json

Large diffs are not rendered by default.

28 changes: 21 additions & 7 deletions srcs/nginx/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 TournamentRoutes from './router/TournamentRoutes';

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 <Navigate to={`/profile/${user.username}`} replace />;
}

return children;
};

const MeRedirect = () => {
const { user } = useAuth();
// if (isLoading) return <div>Loading ...</div>;
if (!user) return <Navigate to="/" replace />;
return <Navigate to={`/profile/${user.username}`}></Navigate>;
const { user, isAuthChecked } = useAuth();

if (!isAuthChecked) {
return null; // ou loader
}

if (!user || !user.username) {
return <Navigate to="/" replace />;
}

return <Navigate to={`/profile/${user.username}`} replace />;
};

export const App = () => {
Expand All @@ -42,6 +55,7 @@ export const App = () => {
/>
<Route path="/me" element={<MeRedirect />}></Route>
<Route path="/profile/:username" element={<ProfilePage />}></Route>
<Route path="/tournaments/*" element={<TournamentRoutes />} />
</Routes>
</main>
);
Expand Down
19 changes: 17 additions & 2 deletions srcs/nginx/src/api/auth-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,23 @@ export const authApi = {
return data?.user?.username;
},

// me: async (): Promise<UserDTO> => {
// 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;
// },
Comment on lines +57 to +69
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tu penses qu’on peut supprimer ce commentaire ou il apporte quelque chose pour le mock ?

me: async (): Promise<UserDTO> => {
const response = await api.get(`/auth/me/`);
return response.data;
const { data } = await api.get('/auth/me', {
withCredentials: true,
});
return data;
},
};
99 changes: 99 additions & 0 deletions srcs/nginx/src/components/atoms/BracketLines.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { RefObject, useEffect, useState } from 'react';

export interface BracketConnection {
from: RefObject<HTMLElement | null>;
to: RefObject<HTMLElement | null>;
}

interface Point {
x: number;
y: number;
}

interface ComputedLine {
from: Point;
to: Point;
}

interface BracketLinesProps {
// coordinate reference
containerRef: RefObject<HTMLElement | null>;
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<ComputedLine[]>([]);

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 (
<svg className="absolute inset-0 w-full h-full pointer-events-none">
{lines.map((line, i) => {
// courbe de bézier cubique
const cx = (line.from.x + line.to.x) / 2;

return (
<path
key={i}
d={`M ${line.from.x} ${line.from.y}
C ${cx} ${line.from.y},
${cx} ${line.to.y},
${line.to.x} ${line.to.y}`}
stroke="rgba(125, 211, 252, 0.9)"
strokeWidth="2"
fill="none"
/>
);
})}
</svg>
);
}
31 changes: 31 additions & 0 deletions srcs/nginx/src/components/atoms/CircleButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { motion } from 'framer-motion';
import React from 'react';

interface CircleButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
children?: React.ReactNode;
}

const dropdownStyle = 'shadow-[0_10px_10px_1px_rgba(205,205,205,0.4)] ';

export const CircleButton = ({ children, className = '', ...props }: CircleButtonProps) => (
<button
className={`
transition-all
hover:scale-110 hover:text-green-900
active:scale-95 active:text-red-800
h-56 p-6
scale-75
md:scale-100 md:m-10
aspect-square
rounded-full
bg-slate-100/80
flex items-center justify-center
font-quantico border border-cyan-300
text-gray-700
${dropdownStyle}
`}
{...props}
>
<p className="text-2xl text-center whitespace-nowrap">{children}</p>
</button>
);
46 changes: 46 additions & 0 deletions srcs/nginx/src/components/atoms/MatchNode.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
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 (
<div
className={`
flex items-center gap-4 px-6 py-3 rounded-full
text-sm font-medium font-quantico
backdrop-blur border border-cyan-200
${highlight ? 'bg-cyan-500 text-white shadow-lg' : 'bg-white/70 text-gray-600'}
`}
>
{/* Label */}
<span>{label}</span>

{/* Start button */}
<button
onClick={onStart}
disabled={!canStart}
className={`
px-4 py-1.5 rounded-full text-xs font-semibold
transition-all
${
canStart
? 'bg-emerald-500 text-white hover:bg-emerald-600 hover:scale-105 active:scale-100'
: 'bg-gray-300 text-gray-500 cursor-not-allowed'
}
`}
aria-disabled={!canStart}
>
{t('game.start')}
</button>
</div>
);
}
3 changes: 1 addition & 2 deletions srcs/nginx/src/components/atoms/NavDropDown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,10 @@ export const NavDropdown = ({ isOpen, children, yTranslate = 0 }: NavDropdownPro
absolute top-full left-1/2
mt-5 w-64 pt-6 pb-10 px-4
bg-slate-100/80 backdrop-blur-xl
border-t-0 border-b-0 border-x-0
rounded-t-none
rounded-b-[8rem]
flex flex-col items-center justify-start
z-50 ${dropdownStyle}
z-15 ${dropdownStyle}
`}
>
<div className="absolute top-0 left-0 w-full bg-linear-to-r from-transparent via-white/10 to-transparent" />
Expand Down
34 changes: 34 additions & 0 deletions srcs/nginx/src/components/atoms/PlayerCapsule.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Player } from '../../types/types';
import Avatar from './Avatar';

export function PlayerCapsule({ player }: { player: Player }) {
const isOnline = player.online === true;
const isWaiting = player.status === 'waiting';
return (
<div
className={`
flex items-center gap-3 px-4 py-2 rounded-full
border backdrop-blur overflow-hidden
${isWaiting ? 'border-gray-400 bg-white/10 opacity-95' : 'border-cyan-300 bg-white/80'}
`}
>
{/* Avatar + status */}
<div className="relative">
<Avatar src={player.avatar} size="sm" />

<span
className={[
'absolute bottom-0 right-0 w-3 h-3 rounded-full border-2 border-white',
isOnline ? 'bg-emerald-500' : 'bg-gray-400',
].join(' ')}
aria-label={isOnline ? 'online' : 'offline'}
/>
</div>

{/* Name */}
<span className="text-sm font-medium text-gray-700 font-quantico whitespace-nowrap">
{player.name}
</span>
</div>
);
}
Loading
Loading