removed socket io and extracted lobby form
This commit is contained in:
@@ -10,10 +10,10 @@ export default async function QuizGameStartPage() {
|
||||
const session = await auth();
|
||||
return (
|
||||
<>
|
||||
<div className="mb-8 flex flex-col items-center justify-center gap-2 text-center">
|
||||
<Icons.logo className="animate-puls h-10 w-10 text-purple-700" />
|
||||
<div className="mb-8 flex flex-col items-center justify-center gap-2 pt-12 text-center">
|
||||
<Icons.logo className="animate-puls text-primary h-10 w-10" />
|
||||
|
||||
<h1 className="bg-gradient-to-r from-yellow-300 via-pink-400 to-yellow-300 bg-clip-text text-5xl font-extrabold tracking-tight text-transparent uppercase">
|
||||
<h1 className="from-primary to-secondary bg-gradient-to-b bg-clip-text text-5xl font-black tracking-tight text-transparent uppercase">
|
||||
{appConfig.name}
|
||||
</h1>
|
||||
<p className="text-lg font-medium text-indigo-200">
|
||||
@@ -26,9 +26,14 @@ export default async function QuizGameStartPage() {
|
||||
{/* Create Lobby Button */}
|
||||
<div className="space-y-6">
|
||||
{session ? (
|
||||
<CreateLobbyDialog className="w-full" />
|
||||
<CreateLobbyDialog className="text-shadow-primary w-full" />
|
||||
) : (
|
||||
<Button size={"xxl"} variant={"party"} asChild>
|
||||
<Button
|
||||
size={"xxl"}
|
||||
variant={"party"}
|
||||
asChild
|
||||
className="text-shadow-primary w-full font-black uppercase"
|
||||
>
|
||||
<Link href={"/api/auth/signin"}>Sign-in to create a Lobby</Link>
|
||||
</Button>
|
||||
)}
|
||||
@@ -37,7 +42,7 @@ export default async function QuizGameStartPage() {
|
||||
<Button
|
||||
size={"xxl"}
|
||||
variant={"party"}
|
||||
className="w-full rounded-xl bg-gradient-to-r from-blue-500 to-indigo-600 text-white hover:from-blue-600 hover:to-indigo-700"
|
||||
className="w-full rounded-xl bg-gradient-to-r from-blue-500 via-blue-600 to-indigo-600 text-white hover:from-blue-600 hover:to-indigo-700"
|
||||
>
|
||||
<Users className="mr-2 h-6 w-6" />
|
||||
Join Lobby
|
||||
|
||||
@@ -1,20 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { lobbyPatchSchema } from "@/lib/validations/lobby";
|
||||
import { Icons } from "@/components/icons";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -23,32 +12,10 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { toast } from "sonner";
|
||||
import { api } from "@/trpc/react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Icons } from "@/components/icons";
|
||||
import { cn } from "@/lib/utils";
|
||||
import LobbyForm from "./lobby/lobby-form";
|
||||
|
||||
function CreateLobbyDialog({ className }: { className?: string }) {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
const { mutateAsync } = api.lobby.create.useMutation();
|
||||
const router = useRouter();
|
||||
const form = useForm<z.infer<typeof lobbyPatchSchema>>({
|
||||
resolver: zodResolver(lobbyPatchSchema),
|
||||
defaultValues: {
|
||||
name: "",
|
||||
},
|
||||
});
|
||||
|
||||
async function onSubmit(lobby: z.infer<typeof lobbyPatchSchema>) {
|
||||
setLoading(true);
|
||||
const result = await mutateAsync({ lobby });
|
||||
setOpen(false);
|
||||
if (result) router.push(`/lobby/${result.id}`);
|
||||
else toast.error("Something went wrong.");
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
@@ -57,7 +24,7 @@ function CreateLobbyDialog({ className }: { className?: string }) {
|
||||
size={"xxl"}
|
||||
variant={"party"}
|
||||
className={cn(
|
||||
"border-green-700 bg-gradient-to-r from-green-500 to-emerald-600 text-white shadow-green-600/30 hover:from-green-600 hover:to-emerald-700",
|
||||
"text-shadow-primary text-primary-foreground font-black",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
@@ -68,38 +35,14 @@ function CreateLobbyDialog({ className }: { className?: string }) {
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent className="container-bg bg-border/30">
|
||||
<DialogContent className="container-bg bg-background/10">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Create a Lobby</DialogTitle>
|
||||
<DialogTitle className="text-shadow-primary text-4xl font-black uppercase">
|
||||
Create Lobby
|
||||
</DialogTitle>
|
||||
<DialogDescription></DialogDescription>
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
className="placeholder:text-white/50"
|
||||
placeholder="What is you lobbies name?"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={loading || !form.formState.isDirty}
|
||||
>
|
||||
Create Lobby
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
</DialogHeader>
|
||||
<LobbyForm cb={() => setOpen(false)} />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
@@ -7,11 +7,6 @@ import UserCard from "@/components/user-card";
|
||||
import DeleteLobbyDialog from "@/app/_components/delete-lobby-dialog";
|
||||
import LobbyMembershipDialog from "@/app/_components/lobby-membership-dialog";
|
||||
import { type Session } from "next-auth";
|
||||
import { getSocket } from "@/lib/hooks/use-socket";
|
||||
import {
|
||||
LOBBY_USER_PRESENCE_EVENT,
|
||||
LOBBY_USER_PRESENCE_UPDATE_EVENT,
|
||||
} from "@/server/socket/event-const";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
|
||||
function LobbyPage({
|
||||
@@ -27,25 +22,8 @@ function LobbyPage({
|
||||
const [memberPresence, setMemberPresence] = React.useState<Array<string>>();
|
||||
const isJoined = members.find((member) => member.id === session?.user.id);
|
||||
const isOwner = lobby.createdById === session?.user.id;
|
||||
const socket = session ? getSocket() : undefined;
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!session || !isJoined || !socket) return;
|
||||
socket.emit(LOBBY_USER_PRESENCE_EVENT, lobby.id, session.user.id, true);
|
||||
console.log("user joined");
|
||||
|
||||
// socket.on(LOBBY_USER_PRESENCE_UPDATE_EVENT, (users) => {
|
||||
// console.log("presence updated", users);
|
||||
|
||||
// setMemberPresence(users);
|
||||
// });
|
||||
|
||||
return () => {
|
||||
if (!session || !isJoined || !socket) return;
|
||||
socket.emit(LOBBY_USER_PRESENCE_EVENT, lobby.id, session.user.id, false);
|
||||
socket.disconnect();
|
||||
};
|
||||
}, [socket]);
|
||||
|
||||
return (
|
||||
<div className="container-bg w-full max-w-md space-y-4 p-6">
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
"use client";
|
||||
|
||||
import type { Lobby } from "@/server/db/schema";
|
||||
import React from "react";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { lobbyPatchSchema } from "@/lib/validations/lobby";
|
||||
import { toast } from "sonner";
|
||||
import { api } from "@/trpc/react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
function LobbyForm({
|
||||
server_lobby,
|
||||
cb,
|
||||
}: {
|
||||
server_lobby?: Lobby;
|
||||
cb?: () => void;
|
||||
}) {
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
const { mutateAsync: createLobby } = api.lobby.create.useMutation();
|
||||
const { mutateAsync: updateLobby } = api.lobby.update.useMutation();
|
||||
const router = useRouter();
|
||||
const form = useForm<z.infer<typeof lobbyPatchSchema>>({
|
||||
resolver: zodResolver(lobbyPatchSchema),
|
||||
defaultValues: {
|
||||
name: "",
|
||||
},
|
||||
});
|
||||
async function onSubmit(lobby: z.infer<typeof lobbyPatchSchema>) {
|
||||
setLoading(true);
|
||||
const existingLobby = Boolean(server_lobby?.id?.length);
|
||||
const result = existingLobby
|
||||
? await updateLobby({ lobby, lobbyId: server_lobby?.id! })
|
||||
: await createLobby({ lobby });
|
||||
cb?.();
|
||||
if (result) {
|
||||
if (!existingLobby) router.push(`/lobby/${result.id}`);
|
||||
} else toast.error("Something went wrong.");
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="w-full space-y-8">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
className="placeholder:text-white/50"
|
||||
placeholder="What is you lobbies name?"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<Button type="submit" disabled={loading || !form.formState.isDirty}>
|
||||
Create Lobby
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
export default LobbyForm;
|
||||
@@ -3,16 +3,10 @@
|
||||
import React from "react";
|
||||
import { Button } from "./ui/button";
|
||||
import Link from "next/link";
|
||||
import { Home } from "lucide-react";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
function NavLink({
|
||||
label,
|
||||
path,
|
||||
className,
|
||||
isLast,
|
||||
}: NavLink & { className?: string; isLast?: boolean }) {
|
||||
function NavLink({ label, path, className }: NavLink & { className?: string }) {
|
||||
const active = usePathname() === path;
|
||||
return (
|
||||
<Button
|
||||
|
||||
@@ -11,22 +11,19 @@ async function Navbar() {
|
||||
|
||||
return (
|
||||
<nav className="flex w-full items-center justify-center p-4">
|
||||
<div className="container-bg flex w-full max-w-sm items-center justify-between gap-2 overflow-hidden rounded-full border-2">
|
||||
<menu className="flex h-full items-center">
|
||||
<div className="container-bg flex h-10 w-full max-w-sm items-center justify-between gap-2 overflow-hidden rounded-full border-2">
|
||||
<menu className="flex items-center">
|
||||
{appConfig.navigation.map((navLink, idx) => (
|
||||
<li key={idx}>
|
||||
<NavLink
|
||||
isLast={idx === appConfig.navigation.length - 1}
|
||||
{...navLink}
|
||||
/>
|
||||
<NavLink {...navLink} />
|
||||
</li>
|
||||
))}
|
||||
</menu>
|
||||
{/* Login Button or Profile Avatar */}
|
||||
{session ? (
|
||||
{session?.user ? (
|
||||
<UserPopover user={session.user} />
|
||||
) : (
|
||||
<Button asChild className="bg-red-800">
|
||||
<Button asChild className="bg-primary font-black">
|
||||
<Link href="/api/auth/signin">Sign In</Link>
|
||||
</Button>
|
||||
)}
|
||||
|
||||
@@ -21,7 +21,7 @@ const buttonVariants = cva(
|
||||
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50 rounded-full",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
party:
|
||||
"bg-primary text-primary-foreground rounded-xl border-b-4 shadow-lg font-bold hover:translate-y-[0.1rem] hover:border-b-2 ",
|
||||
"bg-gradient-to-r from-secondary via-primary to-secondary text-shadow-primary text-primary-foreground rounded-xl border-b-4 shadow-lg font-bold hover:translate-y-[0.1rem] hover:border-b-2 ",
|
||||
},
|
||||
size: {
|
||||
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
||||
|
||||
@@ -25,8 +25,6 @@ export const env = createEnv({
|
||||
* `NEXT_PUBLIC_`.
|
||||
*/
|
||||
client: {
|
||||
NEXT_PUBLIC_SOCKET_URL: z.string().url(),
|
||||
|
||||
// NEXT_PUBLIC_CLIENTVAR: z.string(),
|
||||
},
|
||||
|
||||
@@ -39,7 +37,6 @@ export const env = createEnv({
|
||||
AUTH_DISCORD_ID: process.env.AUTH_DISCORD_ID,
|
||||
AUTH_DISCORD_SECRET: process.env.AUTH_DISCORD_SECRET,
|
||||
DATABASE_URL: process.env.DATABASE_URL,
|
||||
NEXT_PUBLIC_SOCKET_URL: process.env.NEXT_PUBLIC_SOCKET_URL,
|
||||
NODE_ENV: process.env.NODE_ENV,
|
||||
},
|
||||
/**
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { env } from "@/env";
|
||||
import io, { Socket } from "socket.io-client";
|
||||
|
||||
let socket: Socket | null = null;
|
||||
|
||||
export const getSocket = () => {
|
||||
if (!socket) {
|
||||
socket = io(env.NEXT_PUBLIC_SOCKET_URL);
|
||||
}
|
||||
return socket;
|
||||
};
|
||||
@@ -9,4 +9,5 @@ export const lobbyPatchSchema = z.object({
|
||||
.max(255, {
|
||||
message: "Name can only contain up to 255 characters",
|
||||
}),
|
||||
maxPlayers: z.number().default(0),
|
||||
});
|
||||
|
||||
+5
-17
@@ -16,13 +16,8 @@ export const lobbies = createTable("lobby", (d) => ({
|
||||
.primaryKey()
|
||||
.notNull()
|
||||
.$defaultFn(() => createId()),
|
||||
name: d
|
||||
.varchar({ length: 255 })
|
||||
.notNull(),
|
||||
maxMembers: d
|
||||
.integer()
|
||||
.notNull()
|
||||
.default(0),
|
||||
name: d.varchar({ length: 255 }).notNull(),
|
||||
maxPlayers: d.integer().notNull().default(0),
|
||||
createdById: d
|
||||
.varchar({ length: 255 })
|
||||
.notNull()
|
||||
@@ -57,15 +52,9 @@ export const lobbyMembers = createTable(
|
||||
.varchar({ length: 255 })
|
||||
.notNull()
|
||||
.references(() => lobbies.id, { onDelete: "cascade" }),
|
||||
joinedAt: d
|
||||
.timestamp("created_at", { withTimezone: true })
|
||||
.notNull(),
|
||||
role: d
|
||||
.varchar({ length: 255 })
|
||||
.notNull(),
|
||||
isReady: d
|
||||
.boolean()
|
||||
.notNull(),
|
||||
joinedAt: d.timestamp("created_at", { withTimezone: true }).notNull(),
|
||||
role: d.varchar({ length: 255 }).notNull(),
|
||||
isReady: d.boolean().notNull(),
|
||||
}),
|
||||
(t) => [primaryKey({ columns: [t.lobbyId, t.userId] })],
|
||||
);
|
||||
@@ -81,7 +70,6 @@ export const lobbyMembersRelations = relations(lobbyMembers, ({ one }) => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
|
||||
export const users = createTable("user", (d) => ({
|
||||
id: d
|
||||
.varchar({ length: 255 })
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
export const JOIN_LOBBY_EVENT = "joinLobby";
|
||||
export const LEAVE_LOBBY_EVENT = "leaveLobby";
|
||||
|
||||
export const LOBBY_USER_PRESENCE_EVENT = "lobbyPresence";
|
||||
export const LOBBY_USER_PRESENCE_UPDATE_EVENT = "lobbyPresenceUpdate";
|
||||
|
||||
// export const PLAYER_LOBBY_STATUS = "playerLobbyStatus";
|
||||
@@ -1,65 +0,0 @@
|
||||
import { type Server as SocketIOServer } from "socket.io";
|
||||
import {
|
||||
LOBBY_USER_PRESENCE_EVENT,
|
||||
JOIN_LOBBY_EVENT,
|
||||
LEAVE_LOBBY_EVENT,
|
||||
LOBBY_USER_PRESENCE_UPDATE_EVENT,
|
||||
} from "./event-const";
|
||||
import { env } from "@/env";
|
||||
|
||||
const log = (message?: any, ...optionalParams: any[]) => {
|
||||
if (env.NODE_ENV === "development") {
|
||||
console.log(message, ...optionalParams);
|
||||
}
|
||||
};
|
||||
|
||||
const lobbyPresenceMap = new Map<string, Array<string>>();
|
||||
|
||||
export const initSocketEvents = (io: SocketIOServer) => {
|
||||
io.on("connection", (socket) => {
|
||||
log("New client connected:", socket.id);
|
||||
|
||||
socket.on(
|
||||
LOBBY_USER_PRESENCE_EVENT,
|
||||
(lobbyId: string, userId: string, present: boolean) => {
|
||||
socket.join(lobbyId);
|
||||
// if (!lobbyPresenceMap.has(lobbyId)) {
|
||||
// lobbyPresenceMap.set(lobbyId, []);
|
||||
// }
|
||||
// if (present) {
|
||||
// lobbyPresenceMap.get(lobbyId)?.push(userId);
|
||||
// } else {
|
||||
// lobbyPresenceMap
|
||||
// .get(lobbyId)
|
||||
// ?.splice(lobbyPresenceMap.get(lobbyId)?.indexOf(userId)!, 1);
|
||||
// }
|
||||
// console.log(lobbyPresenceMap);
|
||||
|
||||
// socket
|
||||
// .to(lobbyId)
|
||||
// .emit(
|
||||
// LOBBY_USER_PRESENCE_UPDATE_EVENT,
|
||||
// lobbyPresenceMap.get(lobbyId),
|
||||
// );
|
||||
log(
|
||||
`user ${userId} is (present: ${present}) in lobby room: ${lobbyId}`,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
// Comments
|
||||
// socket.on(DELETE_COMMENT_EVENT, (commentId: string, articleId: string) => {
|
||||
// log(`Comment deleted: ${commentId} for article: ${articleId}`);
|
||||
// io.to(articleId).emit(DELETE_COMMENT_EVENT, commentId);
|
||||
// });
|
||||
|
||||
// socket.on(ADD_COMMENT_EVENT, (comment: Comment) => {
|
||||
// log(`Comment added: ${comment.id} for article: ${comment.articleId}`);
|
||||
// io.to(comment.articleId).emit(ADD_COMMENT_EVENT, comment);
|
||||
// });
|
||||
|
||||
socket.on("disconnect", () => {
|
||||
log("Client disconnected:", socket.id);
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -1,37 +0,0 @@
|
||||
import "dotenv/config";
|
||||
import express from "express";
|
||||
import http from "http";
|
||||
import { Server } from "socket.io";
|
||||
import { initSocketEvents } from "./events";
|
||||
import { env } from "@/env";
|
||||
|
||||
declare global {
|
||||
var io: Server | undefined;
|
||||
}
|
||||
|
||||
const createSocketServer = () => {
|
||||
const app = express();
|
||||
const server = http.createServer(app);
|
||||
|
||||
const PORT = 4000;
|
||||
const origin = "http://localhost:" + PORT;
|
||||
|
||||
if (!global.io) {
|
||||
global.io = new Server(server, {
|
||||
cors: {
|
||||
origin: "*",
|
||||
methods: ["GET", "POST"],
|
||||
},
|
||||
});
|
||||
|
||||
initSocketEvents(global.io);
|
||||
|
||||
server.listen(PORT, () => {
|
||||
console.log(`Socket.IO server running on port ${PORT}`);
|
||||
});
|
||||
}
|
||||
|
||||
return global.io;
|
||||
};
|
||||
|
||||
export const ioServer = createSocketServer();
|
||||
@@ -11,10 +11,10 @@
|
||||
--card-foreground: oklch(0.145 0 0);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.145 0 0);
|
||||
--primary: oklch(0.205 0 0);
|
||||
--primary-foreground: oklch(0.985 0 0);
|
||||
--secondary: oklch(0.97 0 0);
|
||||
--secondary-foreground: oklch(0.205 0 0);
|
||||
--primary: oklch(71.76% 0.2017 349.7);
|
||||
--primary-foreground: oklch(100% 0 349.7);
|
||||
--secondary: oklch(88.93% 0.143 90.13);
|
||||
--secondary-foreground: oklch(21.76% 0.0197 108.64);
|
||||
--muted: oklch(0.97 0 0);
|
||||
--muted-foreground: oklch(0.556 0 0);
|
||||
--accent: oklch(0.97 0 0);
|
||||
@@ -131,4 +131,7 @@
|
||||
.gradient-bg {
|
||||
@apply bg-gradient-to-br from-indigo-950 via-purple-900 to-indigo-900;
|
||||
}
|
||||
.text-shadow-primary {
|
||||
text-shadow: 1px 1px 2px var(--primary);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user