Skip to content

Commit 906c235

Browse files
authored
Merge pull request webdevcody#658 from scape76/234-race-room
234 race room
2 parents 59d6585 + 79816a9 commit 906c235

37 files changed

+1213
-363
lines changed

package-lock.json

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/app/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
"tailwindcss": "3.3.2",
7373
"tailwindcss-animate": "^1.0.6",
7474
"unstyled-table": "^0.0.3-alpha-3",
75+
"uuid": "^9.0.0",
7576
"zod": "^3.21.4",
7677
"@prettier/plugin-ruby": "^4.0.2",
7778
"prettier": "^3.0.0",
@@ -83,6 +84,7 @@
8384
"@types/node": "^20.4.1",
8485
"@types/react": "^18.2.15",
8586
"@types/react-dom": "18.2.6",
87+
"@types/uuid": "^9.0.2",
8688
"@typescript-eslint/eslint-plugin": "^5.61.0",
8789
"@typescript-eslint/parser": "^5.61.0",
8890
"autoprefixer": "10.4.14",

packages/app/src/app/add-snippet/_components/add-snippet-form.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -170,10 +170,7 @@ export default function AddSnippetForm({ lang }: { lang: string }) {
170170
<FormItem className="w-full">
171171
<FormLabel>Language</FormLabel>
172172
<FormControl>
173-
<LanguageDropDown
174-
codeLanguage={field.value}
175-
setCodeLanguage={field.onChange}
176-
/>
173+
<LanguageDropDown {...field} />
177174
</FormControl>
178175
<FormMessage />
179176
</FormItem>

packages/app/src/app/add-snippet/_components/language-dropdown.tsx

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,25 +19,24 @@ import {
1919
} from "@/components/ui/popover";
2020
import { snippetLanguages } from "@/config/languages";
2121

22-
const LanguageDropDown = ({
23-
codeLanguage,
24-
setCodeLanguage,
22+
const LanguageDropdown = ({
2523
className,
24+
value,
25+
onChange,
2626
}: {
27-
codeLanguage: string;
28-
setCodeLanguage: (event: string) => void;
2927
className?: string;
28+
value: string;
29+
onChange: (props: React.SetStateAction<string>) => void;
3030
}) => {
3131
const [open, setOpen] = React.useState(false);
3232
const [search, setSearch] = React.useState("");
3333

3434
useEffect(() => {
3535
const savedCodeLanguage = window.localStorage.getItem("codeLanguage");
3636
if (savedCodeLanguage) {
37-
setCodeLanguage(savedCodeLanguage);
37+
onChange(savedCodeLanguage);
3838
}
3939
}, []);
40-
4140
return (
4241
<Popover open={open} onOpenChange={setOpen}>
4342
<PopoverTrigger asChild>
@@ -48,10 +47,9 @@ const LanguageDropDown = ({
4847
className={cn("justify-between w-full px-4 py-3", className)}
4948
data-cy="language-dropdown"
5049
>
51-
{codeLanguage
52-
? snippetLanguages.find(
53-
(language) => language.value === codeLanguage,
54-
)?.label
50+
{value
51+
? snippetLanguages.find((language) => language.value === value)
52+
?.label
5553
: "Select language..."}
5654
<ChevronsUpDown className="w-4 h-4 ml-2 opacity-50 shrink-0" />
5755
</Button>
@@ -76,8 +74,8 @@ const LanguageDropDown = ({
7674
value={language.value}
7775
onSelect={(currentValue) => {
7876
const newCodeLanguage =
79-
currentValue === codeLanguage ? "" : currentValue;
80-
setCodeLanguage(newCodeLanguage);
77+
currentValue === value ? "" : currentValue;
78+
onChange(newCodeLanguage);
8179
window.localStorage.setItem(
8280
"codeLanguage",
8381
newCodeLanguage,
@@ -89,9 +87,7 @@ const LanguageDropDown = ({
8987
<Check
9088
className={cn(
9189
"mr-2 h-4 w-4",
92-
codeLanguage === language.value
93-
? "opacity-100"
94-
: "opacity-0",
90+
value === language.value ? "opacity-100" : "opacity-0",
9591
)}
9692
/>
9793
{language.label}
@@ -104,4 +100,6 @@ const LanguageDropDown = ({
104100
);
105101
};
106102

107-
export default LanguageDropDown;
103+
LanguageDropdown.displayName = "LanguageDropdown";
104+
105+
export default LanguageDropdown;

packages/app/src/app/hero-banner.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export default function HeroBanner() {
2222
"
2323
>
2424
<Link
25+
2526
href={"/race"}
2627
title="Start Racing"
2728
prefetch

packages/app/src/app/race/(play)/multiplayer/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { getCurrentUser } from "@/lib/session";
22
import { redirect } from "next/navigation";
3-
import RaceMultiplayer from "../../_components/race/race-multiplayer";
43
import { Language, isValidLanguage } from "@/config/languages";
4+
import RaceMultiplayerRoom from "./room";
55

66
export default async function MultiplayerRacePage({
77
searchParams,
@@ -23,7 +23,7 @@ export default async function MultiplayerRacePage({
2323

2424
return (
2525
<main className="flex flex-col items-center justify-between py-10 lg:p-24">
26-
<RaceMultiplayer user={user} language={searchParams.lang as Language} />
26+
<RaceMultiplayerRoom user={user} language={searchParams.lang as Language} />
2727
</main>
2828
);
2929
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
"use client";
2+
3+
import { Language } from "@/config/languages";
4+
import React, { useEffect, useRef, useState } from "react";
5+
import { socket } from "@/lib/socket";
6+
7+
// Types
8+
import type { User } from "next-auth";
9+
import { Prisma } from "@prisma/client";
10+
import { RaceStatus } from "@code-racer/wss/src/types";
11+
import { GameStateUpdatePayload } from "@code-racer/wss/src/events/server-to-client";
12+
13+
import MultiplayerLoadingLobby from "@/app/race/_components/multiplayer-loading-lobby";
14+
import GameMultiplayer from "@/app/race/_components/race/game-multiplayer";
15+
16+
type Participant = Omit<
17+
GameStateUpdatePayload["raceState"]["participants"][number],
18+
"socketId"
19+
>;
20+
21+
export default function RaceMultiplayerRoom({
22+
user,
23+
language,
24+
}: {
25+
user?: User;
26+
language: Language;
27+
}) {
28+
const [race, setRace] = React.useState<Prisma.RaceGetPayload<
29+
Record<string, never>
30+
> | null>(null);
31+
const [raceParticipantId, setRaceParticipantId] = React.useState<string>("");
32+
const [raceStatus, setRaceStatus] = React.useState<RaceStatus | null>(null);
33+
const [raceStartCountdown, setRaceStartCountdown] = useState<number>(0);
34+
35+
const [participants, setParticipants] = React.useState<Participant[]>([]);
36+
37+
// Connection to wss
38+
useEffect(() => {
39+
socket.emit("UserGetRace", {
40+
language,
41+
userId: user?.id,
42+
});
43+
44+
return () => {
45+
socket.disconnect();
46+
socket.off("connect");
47+
};
48+
}, []);
49+
50+
useEffect(() => {
51+
socket.on("UserRaceResponse", (payload) => {
52+
setRace(payload.race);
53+
setParticipants(payload.participants);
54+
setRaceStatus(payload.raceStatus);
55+
setRaceParticipantId(payload.raceParticipantId);
56+
});
57+
58+
socket.on("GameStateUpdate", (payload) => {
59+
setParticipants(payload.raceState.participants);
60+
setRaceStatus(payload.raceState.status);
61+
setRaceStartCountdown(payload.raceState.countdown ?? 0);
62+
});
63+
});
64+
65+
return (
66+
<>
67+
{race && raceStatus !== "running" && (
68+
<MultiplayerLoadingLobby participants={participants}>
69+
{raceStatus === "waiting" && (
70+
<div className="flex flex-col items-center text-2xl font-bold">
71+
<div className="w-8 h-8 border-4 border-muted-foreground rounded-full border-t-4 border-t-warning animate-spin"></div>
72+
Waiting for players
73+
</div>
74+
)}
75+
{raceStatus === "countdown" && Boolean(raceStartCountdown) && (
76+
<div className="text-center text-2xl font-bold">
77+
Game starting in: {raceStartCountdown}
78+
</div>
79+
)}
80+
</MultiplayerLoadingLobby>
81+
)}
82+
83+
{race &&
84+
raceParticipantId &&
85+
(raceStatus === "running" || "finished") && (
86+
<GameMultiplayer
87+
race={race}
88+
participants={participants}
89+
currentRaceStatus={raceStatus}
90+
raceId={race.id}
91+
participantId={raceParticipantId}
92+
user={user}
93+
/>
94+
)}
95+
</>
96+
);
97+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import * as React from "react";
2+
3+
import { socket } from "@/lib/socket";
4+
import { useEffect } from "react";
5+
import { getSnippetById } from "../../(play)/loaders";
6+
import { toast } from "@/components/ui/use-toast";
7+
import { Snippet } from "@prisma/client";
8+
import { Room } from "./room";
9+
import { getCurrentUser } from "@/lib/session";
10+
import { redirect } from "next/navigation";
11+
12+
export default async function RoomPage({
13+
params,
14+
}: {
15+
params: { roomId: string };
16+
}) {
17+
const user = await getCurrentUser();
18+
19+
return (
20+
<main className="flex flex-col items-center justify-between py-10 lg:p-24">
21+
<Room user={user} roomId={params.roomId} />
22+
</main>
23+
);
24+
}

0 commit comments

Comments
 (0)