Skip to content

Commit 7b0f104

Browse files
authored
Merge pull request #159 from CapituloJaverianoACM/Feat/contestScreen
Feat/contest screen
2 parents 75cd9d6 + bc8e390 commit 7b0f104

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+789
-313
lines changed

messages/en.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,5 +190,21 @@
190190
"subtitle": "How do I submit a problem?"
191191
}
192192
}
193+
},
194+
"Match": {
195+
"instructions": [
196+
"When you're ready, press the 'Ready' button.",
197+
"If you are no longer ready, you can select 'Not ready'.",
198+
"When both contestants are ready, a Codeforces problem will be assigned.",
199+
"Once you successfully submit the problem, press verify so our system can check your submission.",
200+
"Note that your Codeforces username must match the one used in the submission.",
201+
"If the verdict is 'ACCEPTED', a 5-second window will be created in case the opponent also gets the same verdict."
202+
],
203+
"title": "Instructions",
204+
"wins": "of win ratio.",
205+
"ready": "Ready!",
206+
"not_ready": "Not ready.",
207+
"waiting_oponent": "Waiting oponent...",
208+
"check": "Check"
193209
}
194210
}

messages/es.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,5 +190,21 @@
190190
"subtitle": "¿Cómo envío un problema?"
191191
}
192192
}
193+
},
194+
"Match": {
195+
"instructions": [
196+
"Cuando estés listo, presiona el botón de 'Listo'.",
197+
"Si dejas de estar listo, puedes seleccionar 'No estoy listo'.",
198+
"Cuando los dos contrincantes estén listos, se asignará un problema de Codeforces.",
199+
"Cuando envíes el problema satisfactoriamente, presiona verificar para que nuestro sistema pueda verificar tu envío.",
200+
"Ten en cuenta que tu usuario de Codeforces debe coincidir con la cuenta del envío.",
201+
"Si el veredicto es 'ACCEPTED', se creará una ventana de 5 segundos en caso de que el oponente tenga el mismo veredicto."
202+
],
203+
"title": "Instrucciones",
204+
"wins": "porcentaje de victorias.",
205+
"ready": "¡Listo!",
206+
"not_ready": "No estoy listo",
207+
"waiting_oponent": "Esperando oponente...",
208+
"check": "Verificar"
193209
}
194210
}

next.config.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { NextConfig } from "next";
2+
import createNextIntlPlugin from "next-intl/plugin";
23

34
const nextConfig: NextConfig = {
45
/* config options here */
@@ -19,4 +20,5 @@ const nextConfig: NextConfig = {
1920
},
2021
};
2122

22-
export default nextConfig;
23+
const withNextIntl = createNextIntlPlugin();
24+
export default withNextIntl(nextConfig);

public/Codeforces.svg

Lines changed: 1 addition & 0 deletions
Loading

src/app/inactive-members/page.tsx

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,11 @@
11
"use client";
22
import InactiveMembers from "@/components/members/inactive-members";
33
import { CursorWrapper } from "@/components/home/ui/cursor-wrapper";
4-
import MainNavbar, { NavLink } from "@/components/shared/main-navbar";
5-
6-
const navlinks: NavLink[] = [];
74

85
export default function InactiveMembersPage() {
96
return (
10-
<>
11-
<CursorWrapper>
12-
<MainNavbar navLinks={navlinks} />
13-
<InactiveMembers />
14-
</CursorWrapper>
15-
</>
7+
<CursorWrapper>
8+
<InactiveMembers />
9+
</CursorWrapper>
1610
);
1711
}

src/app/layout.tsx

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import "../styles/globals.css";
22
import { cookies } from "next/headers";
33
import { Providers } from "@/components/shared/providers";
4-
5-
// https://nextjs.org/docs/app/api-reference/functions/cookies
4+
import { NextIntlClientProvider } from "next-intl";
65

76
export default async function RootLayout({
87
children,
@@ -11,8 +10,6 @@ export default async function RootLayout({
1110
}) {
1211
const cookieStore = await cookies();
1312
const theme = cookieStore.get("theme")?.value ?? "light";
14-
const locale = cookieStore.get("locale")?.value ?? "en";
15-
const messages = (await import(`@/../messages/${locale}.json`)).default;
1613

1714
return (
1815
<html className={`${theme}`} lang="es" style={{ scrollBehavior: "smooth" }}>
@@ -22,9 +19,10 @@ export default async function RootLayout({
2219
</head>
2320
<body>
2421
<main className="dark:bg-[#121212]">
25-
<Providers locale={locale} messages={messages}>
26-
{children}
27-
</Providers>
22+
{/* El provider de internacionalización es especial, ya que necesariamente tiene que ser use server. */}
23+
<NextIntlClientProvider>
24+
<Providers>{children}</Providers>
25+
</NextIntlClientProvider>
2826
</main>
2927
</body>
3028
</html>
Lines changed: 58 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,83 @@
11
"use client";
22

3-
import { useParams } from "next/navigation";
4-
import MainNavbar from "@/components/shared/main-navbar";
5-
import Footer from "@/components/shared/footer";
3+
import { useParams, useRouter } from "next/navigation";
64
import { useQuery } from "@tanstack/react-query";
75
import {
8-
ContestMatchResult,
96
getContestMatchInfo,
7+
getMatchmakingTree,
108
} from "@/controllers/contest.controller";
119
import { MeshGradient } from "@/layouts/mesh-gradient";
12-
import { ContestantsCards } from "@/components/league/contest/ContestantsCards";
13-
import { ContestInstructions } from "@/components/league/contest/ContestInstructions";
10+
import { ContestantsCards } from "@/components/league/contest/contestants-cards";
11+
import { ContestInstructions } from "@/components/league/contest/contest-instructions";
1412
import MatchmakingTree from "@/components/league/matchmaking-tree";
15-
16-
const navLinks = [
17-
{ key: "home", label: "Inicio", href: "/" },
18-
{ key: "league", label: "Liga", href: "/league" },
19-
{ key: "rank", label: "Ranking", href: "/rank" },
20-
];
13+
import { ContestMatchResult } from "@/models/contest.model";
14+
import { useEffect } from "react";
15+
import LogoLoader from "@/components/shared/ui/logo-loader/loader";
16+
import { ContestFailedLoad } from "@/components/league/contest/contest-failed-load";
17+
import { useContestMatch } from "@/hooks/use-contest-match";
2118

2219
export default function ContestDetailPage() {
2320
const params = useParams();
21+
const router = useRouter();
2422
const contestId = params.contestId as string;
2523

2624
const { data, isLoading } = useQuery({
2725
queryKey: ["matchmaking", contestId],
2826
queryFn: async () => getContestMatchInfo(Number(contestId)),
2927
});
3028

29+
const { data: tree, isLoading: isLoadingTree } = useQuery({
30+
queryKey: ["matchmaking-tree", contestId],
31+
queryFn: async () => getMatchmakingTree(Number(contestId)),
32+
});
33+
34+
const [user_ready, toggleUserReady, codeforces_problem, opponent] =
35+
useContestMatch(Number(contestId), data?.current_student);
36+
37+
useEffect(() => {
38+
if (!isLoading && data?.msg === ContestMatchResult.NO_LOGGED) {
39+
router.replace("/log-in");
40+
}
41+
}, [data, isLoading, router]);
42+
43+
if (isLoading || isLoadingTree) {
44+
return (
45+
<MeshGradient>
46+
<div className="w-screen h-screen flex items-center justify-center">
47+
<LogoLoader size={300} />
48+
</div>
49+
</MeshGradient>
50+
);
51+
}
52+
3153
return (
3254
<>
33-
<MainNavbar navLinks={navLinks} />
3455
<MeshGradient>
35-
<div className="flex flex-col gap-10 items-center justify-center mt-[8%] mx-[20%]">
36-
<h1 className="text-white">
37-
{isLoading ? "Cargando..." : data.contest[0].name}
38-
</h1>
39-
<ContestantsCards />
40-
<ContestInstructions />
41-
<h1 className="text-white">Matchmaking</h1>
42-
{!isLoading && (
43-
<MatchmakingTree tree={data.tree} students={data.students} />
44-
)}
45-
</div>
56+
{!data.ok || !tree ? (
57+
<ContestFailedLoad
58+
msg={!tree ? ContestMatchResult.NO_TREE : data.msg}
59+
/>
60+
) : (
61+
<div className="flex flex-col gap-10 items-center justify-center mt-[8%] mx-[20%]">
62+
<h1 className="text-black dark:text-white">
63+
{data.contest[0].name}
64+
</h1>
65+
<ContestantsCards
66+
user={{ ...data.current_student, ready: user_ready }}
67+
oponent={opponent}
68+
/>
69+
<ContestInstructions
70+
ready={user_ready}
71+
codeforces_problem={codeforces_problem}
72+
toggleReady={toggleUserReady}
73+
/>
74+
<h1 className="text-black dark:text-white">Matchmaking</h1>
75+
{!isLoadingTree && (
76+
<MatchmakingTree tree={tree} students={data.students} />
77+
)}
78+
</div>
79+
)}
4680
</MeshGradient>
47-
<Footer />
4881
</>
4982
);
5083
}

src/app/league/page.tsx

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
"use client";
2-
import { useTranslations } from "next-intl";
32

4-
import MainNavbar from "@/components/shared/main-navbar";
53
import { Hero } from "@/components/league/sections/hero";
64
import { Rules } from "@/components/league/sections/rules";
75
import { UpcomingEvents } from "@/components/league/sections/upcoming-events";
86
import { Podium } from "@/components/league/sections/podium";
9-
import Footer from "@/components/shared/footer";
107
import { getContestsWithPictures } from "@/controllers/contest.controller";
118
import { useEffect } from "react";
129
import { createClient } from "@/lib/supabase/client";
@@ -15,19 +12,6 @@ import { RoadmapButton } from "@/components/home/ui/roadmap-button";
1512
import { useQuery, useQueryClient } from "@tanstack/react-query";
1613

1714
export default function LeagueHomePage() {
18-
const t = useTranslations("Navigation");
19-
const navLinks = [
20-
{ key: "home", label: t("home"), href: "/" },
21-
{ key: "rules", label: t("rules"), href: "#rules" },
22-
{
23-
key: "upcoming-events",
24-
label: t("upcomingEvents"),
25-
href: "#upcoming-events",
26-
},
27-
{ key: "podium", label: t("podium"), href: "#podium" },
28-
{ key: "rank", label: t("rank"), href: "/rank" },
29-
];
30-
3115
const { data: contests = [], isLoading } = useQuery({
3216
queryKey: ["league-contests"],
3317
queryFn: async () => {
@@ -54,12 +38,10 @@ export default function LeagueHomePage() {
5438

5539
return (
5640
<>
57-
<MainNavbar navLinks={navLinks} />
5841
<Hero />
5942
<Rules />
6043
<UpcomingEvents events={contests} loadingInitialState={isLoading} />
6144
<Podium />
62-
<Footer />
6345
<RoadmapButton />
6446
</>
6547
);

src/app/page.tsx

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,15 @@
11
"use client";
2-
import { useTranslations } from "next-intl";
32
import { Activities } from "@/components/home/sections/activities";
43
import { Hero } from "@/components/home/sections/hero";
54
import { Members } from "@/components/home/sections/members";
6-
import MainNavbar from "@/components/shared/main-navbar";
75
import { CursorWrapper } from "@/components/home/ui/cursor-wrapper";
8-
import Footer from "../components/shared/footer";
9-
import { getNavLinks } from "@/lib/nav-links";
106

117
export default function HomePage() {
12-
const t = useTranslations("Navigation");
13-
const navLinks = getNavLinks(t);
14-
158
return (
169
<CursorWrapper>
17-
<MainNavbar navLinks={navLinks} />
1810
<Hero />
1911
<Activities />
2012
<Members />
21-
<Footer />
2213
</CursorWrapper>
2314
);
2415
}

src/app/profile/page.tsx

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,14 @@
11
"use client";
22

3-
import { useTranslations } from "next-intl";
4-
import MainNavbar from "@/components/shared/main-navbar";
5-
import Footer from "@/components/shared/footer";
63
import { useProfileData } from "@/hooks/use-profile-data";
74
import { useStudentContests } from "@/hooks/use-student-contests";
85
import { ProfileSkeleton } from "@/components/profile/profile-skeleton";
96
import { ProfileHeader } from "@/components/profile/profile-header";
107
import { ProfileStats } from "@/components/profile/profile-stats";
118
import { ContestsHistory } from "@/components/profile/contests-history";
12-
import { getNavLinks } from "@/lib/nav-links";
9+
import { Toaster } from "react-hot-toast";
1310

1411
export default function ProfilePage() {
15-
const t = useTranslations();
16-
const navLinks = getNavLinks(t);
17-
1812
const {
1913
student,
2014
loadingStudent,
@@ -31,7 +25,7 @@ export default function ProfilePage() {
3125

3226
return (
3327
<div className="min-h-dvh flex flex-col dark:from-[#121212] dark:to-[#121212] bg-linear-to-b from-(--azul-niebla) to-(--white)">
34-
<MainNavbar navLinks={navLinks} />
28+
<Toaster position="top-right" />
3529
<div className="flex-1 max-w-6xl mx-auto p-6 md:p-8 w-full mt-40">
3630
<h1 className="text-3xl md:text-4xl font-bold text-(--azul-noche) dark:text-white mb-6">
3731
Perfil
@@ -61,7 +55,6 @@ export default function ProfilePage() {
6155
<ContestsHistory contests={contests} loading={loadingContests} />
6256
</div>
6357
</div>
64-
<Footer />
6558
</div>
6659
);
6760
}

0 commit comments

Comments
 (0)