-
+
);
diff --git a/app/(dashboard)/room/[roomId]/_components/players-cards.tsx b/app/(dashboard)/room/[roomId]/_components/players-cards.tsx
index fc4dbc3..a22cd55 100644
--- a/app/(dashboard)/room/[roomId]/_components/players-cards.tsx
+++ b/app/(dashboard)/room/[roomId]/_components/players-cards.tsx
@@ -3,7 +3,8 @@ import { useBoard } from "@/context/board";
import { PlayerCard } from "./player-card";
export const PlayersCards = () => {
- const { others, self, handleRevealCards, revealCards } = useBoard();
+ const { others, self, handleRevealCards, revealCards, handleReset } =
+ useBoard();
const players = [self, ...others];
@@ -16,6 +17,13 @@ export const PlayersCards = () => {
);
}
+ const onRevealClick = () => {
+ if (revealCards) {
+ handleReset();
+ }
+ handleRevealCards();
+ };
+
return (
@@ -23,9 +31,15 @@ export const PlayersCards = () => {
))}
-
);
};
diff --git a/app/api/auth/[...nextauth]/route.ts b/app/api/auth/[...nextauth]/route.ts
index b2e6633..3ed4c03 100644
--- a/app/api/auth/[...nextauth]/route.ts
+++ b/app/api/auth/[...nextauth]/route.ts
@@ -1,30 +1,5 @@
-import { env } from "@/env";
-import NextAuth, { AuthOptions } from "next-auth";
-import GoogleProvider from "next-auth/providers/google";
-
-export const authOptions: AuthOptions = {
- providers: [
- GoogleProvider({
- clientId: env.GOOGLE_CLIENT_ID,
- clientSecret: env.GOOGLE_CLIENT_SECRET,
- }),
- ],
- pages: {
- signIn: "/login",
- },
- callbacks: {
- async jwt({ token, account }) {
- if (account) {
- token.accessToken = account.access_token;
- }
- return token;
- },
- async session({ session, token, user }) {
- (session as any).accessToken = token.accessToken;
- return session;
- },
- },
-};
+import { authOptions } from "@/authOptions";
+import NextAuth from "next-auth";
const handler = NextAuth(authOptions);
diff --git a/app/providers.tsx b/app/providers.tsx
index 61bd5e9..3c47e30 100644
--- a/app/providers.tsx
+++ b/app/providers.tsx
@@ -1,7 +1,7 @@
"use client";
+import { Toaster } from "@/components/ui/sonner";
import { SessionProvider } from "next-auth/react";
-import { Toaster } from "sonner";
export function Providers({
children,
diff --git a/authOptions.ts b/authOptions.ts
new file mode 100644
index 0000000..5512ae5
--- /dev/null
+++ b/authOptions.ts
@@ -0,0 +1,27 @@
+import { env } from "@/env";
+import { AuthOptions } from "next-auth";
+import GoogleProvider from "next-auth/providers/google";
+
+export const authOptions: AuthOptions = {
+ providers: [
+ GoogleProvider({
+ clientId: env.GOOGLE_CLIENT_ID,
+ clientSecret: env.GOOGLE_CLIENT_SECRET,
+ }),
+ ],
+ pages: {
+ signIn: "/login",
+ },
+ callbacks: {
+ async jwt({ token, account }) {
+ if (account) {
+ token.accessToken = account.access_token;
+ }
+ return token;
+ },
+ async session({ session, token, user }) {
+ (session as any).accessToken = token.accessToken;
+ return session;
+ },
+ },
+};
diff --git a/components/actions/index.tsx b/components/room-actions/index.tsx
similarity index 86%
rename from components/actions/index.tsx
rename to components/room-actions/index.tsx
index 271dc15..8ac7985 100644
--- a/components/actions/index.tsx
+++ b/components/room-actions/index.tsx
@@ -9,11 +9,12 @@ import {
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { useAction } from "@/hooks/use-action";
+import { buildInviteUrl } from "@/use-cases/invite/build-invite-url";
import { deleteRoom } from "@/use-cases/room";
import { DropdownMenuContentProps } from "@radix-ui/react-dropdown-menu";
import { Link2, Trash2 } from "lucide-react";
import { useSession } from "next-auth/react";
-import { toast } from "sonner";
+import { toast } from "../toast";
interface ActionsProps {
children: React.ReactNode;
@@ -24,7 +25,7 @@ interface ActionsProps {
roomOwnerEmail: string;
}
-export const Actions = ({
+export const RoomActions = ({
children,
side,
sideOffset,
@@ -38,9 +39,9 @@ export const Actions = ({
const onCopyLink = () => {
navigator.clipboard
- .writeText(`${window.location.origin}/room/${id}`)
+ .writeText(buildInviteUrl(id, window.location.origin))
.then(() => toast.success("Copiado!", { icon: "📋" }))
- .catch(() => toast.error("Ops, algo deu errado", { icon: "🚨" }));
+ .catch(() => toast.error());
};
const onDelete = () => {
@@ -48,7 +49,7 @@ export const Actions = ({
mutation({ roomId: id, user: data?.user })
.then(() => toast.success("Sala deletada!", { icon: "🗑️" }))
- .catch(() => toast.error("Ops, algo deu errado", { icon: "🚨" }));
+ .catch(() => toast.error());
};
return (
@@ -62,7 +63,7 @@ export const Actions = ({
>
- Copiar link da sala
+ Copiar convite
{roomOwner && (
Deletar sala
diff --git a/components/toast/index.tsx b/components/toast/index.tsx
new file mode 100644
index 0000000..61b4fb1
--- /dev/null
+++ b/components/toast/index.tsx
@@ -0,0 +1,43 @@
+import {
+ ExternalToast,
+ Toaster as ToasterSonner,
+ toast as toastSonner,
+} from "sonner";
+
+export const Toaster = () => {
+ return ;
+};
+
+interface ToastOptions extends ExternalToast {}
+
+const basicToast = (message: string, options?: ToastOptions) => {
+ toastSonner(message, options);
+};
+
+const success = (message: string, options?: ToastOptions) => {
+ toastSonner.success(message, {
+ ...options,
+ icon: options?.icon ?? "🎉",
+ });
+};
+
+const randomErrorMessages = [
+ "Ops, algo deu errado",
+ "Puts, de novo?",
+ "Vish, algo deu errado",
+ "Vacilamos nessa, tenta de novo",
+ "Deu ruim, algo deu errado",
+];
+
+const error = (message?: string, options?: ToastOptions) => {
+ const errorMessage =
+ message ??
+ randomErrorMessages[Math.floor(Math.random() * randomErrorMessages.length)];
+
+ toastSonner.error(errorMessage, {
+ ...options,
+ icon: options?.icon ?? "🚨",
+ });
+};
+
+export const toast = Object.assign(basicToast, { success, error });
diff --git a/components/ui/sonner.tsx b/components/ui/sonner.tsx
index 452f4d9..549cf84 100644
--- a/components/ui/sonner.tsx
+++ b/components/ui/sonner.tsx
@@ -1,12 +1,12 @@
-"use client"
+"use client";
-import { useTheme } from "next-themes"
-import { Toaster as Sonner } from "sonner"
+import { useTheme } from "next-themes";
+import { Toaster as Sonner } from "sonner";
-type ToasterProps = React.ComponentProps
+type ToasterProps = React.ComponentProps;
const Toaster = ({ ...props }: ToasterProps) => {
- const { theme = "system" } = useTheme()
+ const { theme = "system" } = useTheme();
return (
{
}}
{...props}
/>
- )
-}
+ );
+};
-export { Toaster }
+export { Toaster };
diff --git a/app/(dashboard)/(home)/_components/user-buttom.tsx b/components/user-button/index.tsx
similarity index 100%
rename from app/(dashboard)/(home)/_components/user-buttom.tsx
rename to components/user-button/index.tsx
diff --git a/context/board.tsx b/context/board.tsx
index 1c30e8e..b1592d4 100644
--- a/context/board.tsx
+++ b/context/board.tsx
@@ -1,4 +1,5 @@
import { LoadingLogo } from "@/components/loading-logo/loading";
+import { toast } from "@/components/toast";
import { http } from "@/lib/api";
import { Player } from "@/lib/schemas/player";
import { ChoiceOptions } from "@/types/choice-options";
@@ -7,17 +8,18 @@ import {
joinBoardKey,
leaveBoardKey,
playerChoiceKey,
+ resetBoardKey,
} from "@/use-cases/event-keys/board";
import { useRouter } from "next/navigation";
import {
ReactNode,
createContext,
+ useCallback,
useContext,
useEffect,
useRef,
useState,
} from "react";
-import { toast } from "sonner";
import { useSocketClient } from "./socket-client";
type BoardContextProps = {
@@ -28,6 +30,7 @@ type BoardContextProps = {
choiceOptions: ChoiceOptions;
handleChoice: (choice: string) => Promise;
handleRevealCards: () => Promise;
+ handleReset: () => Promise;
};
export const BoardContext = createContext(
@@ -38,6 +41,8 @@ export const useBoard = () => {
return useContext(BoardContext);
};
+const TIMEOUT = 60 * 1000;
+
export const BoardProvider = ({
roomId,
children,
@@ -74,11 +79,8 @@ export const BoardProvider = ({
joinBoard();
}, []);
- console.log({ self, others });
-
- const leaveBoard = async () => {
+ const leaveBoard = useCallback(async () => {
if (!selfRef.current?.boardId) return;
- console.log("leave board", selfRef.current);
await http.post("/leave-board", {
playerId: selfRef.current.id,
@@ -89,7 +91,27 @@ export const BoardProvider = ({
setSelf({} as Player);
setOthers([]);
selfRef.current = undefined;
- };
+ }, [roomId]);
+
+ useEffect(() => {
+ const timer = setTimeout(() => {
+ if (!isConnected || !self?.id) {
+ toast("Você foi desconectado da sala", { icon: "💩" });
+ leaveBoard();
+ router.push("/");
+ }
+ }, TIMEOUT);
+
+ if (isConnected && self?.id) {
+ return () => {
+ clearTimeout(timer);
+ };
+ }
+
+ return () => {
+ clearTimeout(timer);
+ };
+ }, [isConnected, leaveBoard, router, self?.id]);
useEffect(() => {
return () => {
@@ -134,14 +156,14 @@ export const BoardProvider = ({
console.log(`[${choiceEventKey}] for ${self.name}:`, message);
if (!message?.player) return;
- const updatedPlayer = message.player;
+ const updatedPlayer = message.player as Player;
if (updatedPlayer.id === self.id) {
return;
}
setOthers((prev) =>
- prev.map((p) => (p.id === updatedPlayer.id ? updatedPlayer : p))
+ prev.map((p) => (p.email === updatedPlayer.email ? updatedPlayer : p))
);
});
@@ -149,13 +171,42 @@ export const BoardProvider = ({
socket.on(revealCardsEventKey, (message) => {
console.log(`[${revealCardsEventKey}] for ${self.name}:`, message);
+
+ if (!message?.players?.length) return;
+
+ const players = message.players as Array;
+
+ const _others = players.filter((_player) => _player.id !== self.id);
+
setRevealCards(message.reveal);
+ setOthers(_others);
+ });
+
+ const resetBoardEventKey = resetBoardKey(self.boardId);
+
+ socket.on(resetBoardEventKey, (message) => {
+ console.log(`[${resetBoardEventKey}] for ${self.name}:`, message);
+ if (!message?.players?.length) return;
+
+ const players = message.players as Array;
+
+ const _self = players.find((_player) => _player.id === self.id);
+ const _others = players.filter((_player) => _player.id !== self.id);
+
+ if (!_self) return;
+
+ setOthers(_others);
+ setSelf(_self);
+
+ setRevealCards(false);
});
return () => {
socket.off(joinBoardEventKey);
socket.off(leaveBoardEventKey);
socket.off(choiceEventKey);
+ socket.off(revealCardsEventKey);
+ socket.off(resetBoardEventKey);
};
}, [others, roomId, self, socket]);
@@ -181,6 +232,12 @@ export const BoardProvider = ({
});
};
+ const handleReset = async () => {
+ await http.post("/reset-board", {
+ boardId: self.boardId,
+ });
+ };
+
return (
{children}
diff --git a/lib/api/index.tsx b/lib/api/index.tsx
index 5e47319..0d1c782 100644
--- a/lib/api/index.tsx
+++ b/lib/api/index.tsx
@@ -1,4 +1,4 @@
-import { toast } from "sonner";
+import { toast } from "@/components/toast";
const isServer = typeof window === "undefined";
@@ -9,14 +9,13 @@ const createHttpHandler = async (fn: () => Promise) => {
return result.json() as T;
}
- const error = await result.json();
+ const error = result?.statusText ?? (await result.json());
if (isServer || process.env.NODE_ENV === "development") {
console.error(error);
}
- const message = "Algo deu errado 😥";
- toast.error(message);
+ toast.error();
return null;
};
diff --git a/package-lock.json b/package-lock.json
index dbeffee..dcef1a2 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "pontim",
- "version": "0.1.0",
+ "version": "0.2.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "pontim",
- "version": "0.1.0",
+ "version": "0.2.0",
"dependencies": {
"@hookform/resolvers": "^3.3.4",
"@prisma/client": "^5.16.1",
diff --git a/package.json b/package.json
index ec5b890..5bc0994 100644
--- a/package.json
+++ b/package.json
@@ -1,12 +1,14 @@
{
"name": "pontim",
- "version": "0.1.0",
+ "version": "0.2.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
- "lint": "next lint"
+ "lint": "next lint",
+ "postinstall": "npx prisma generate",
+ "db:push": "prisma db push --preview-feature"
},
"dependencies": {
"@hookform/resolvers": "^3.3.4",
diff --git a/pages/api/join-board.ts b/pages/api/join-board.ts
index 3bdf50b..d0c3070 100644
--- a/pages/api/join-board.ts
+++ b/pages/api/join-board.ts
@@ -1,6 +1,6 @@
"use server";
-import { authOptions } from "@/app/api/auth/[...nextauth]/route";
+import { authOptions } from "@/authOptions";
import { ResponseWithSocket } from "@/types/response-with-socket";
import { joinBoardKey } from "@/use-cases/event-keys/board";
import { getPlayersByBoardId } from "@/use-cases/player/get-players-by-board-id";
diff --git a/pages/api/leave-board.ts b/pages/api/leave-board.ts
index 6d4f0bd..ac9cbe6 100644
--- a/pages/api/leave-board.ts
+++ b/pages/api/leave-board.ts
@@ -1,4 +1,4 @@
-import { authOptions } from "@/app/api/auth/[...nextauth]/route";
+import { authOptions } from "@/authOptions";
import { ResponseWithSocket } from "@/types/response-with-socket";
import { leaveBoard } from "@/use-cases/board/leave-board";
import { leaveBoardKey } from "@/use-cases/event-keys/board";
@@ -36,7 +36,7 @@ const leaveBoardHandler = async (
res.status(200).json({ message: "Succefully leave board" });
} catch (error: any) {
- return res.status(400).json({ message: error?.message });
+ return res.redirect("/");
}
};
diff --git a/pages/api/make-choice.ts b/pages/api/make-choice.ts
index 7364a7e..b724b01 100644
--- a/pages/api/make-choice.ts
+++ b/pages/api/make-choice.ts
@@ -30,14 +30,17 @@ const makeChoiceHandler = async (
const eventKey = playerChoiceKey(updatedPlayer.boardId);
const message = {
- player: updatedPlayer,
+ player: {
+ ...updatedPlayer,
+ choice: "hidden",
+ },
};
res.socket?.server?.io?.emit(eventKey, message);
return res.status(200).json(updatedPlayer);
} catch (error: any) {
- return res.status(500).json({ message: error.message });
+ return res.status(500).json({ message: error.message ?? error });
}
};
diff --git a/pages/api/reset-board.ts b/pages/api/reset-board.ts
new file mode 100644
index 0000000..80492e7
--- /dev/null
+++ b/pages/api/reset-board.ts
@@ -0,0 +1,49 @@
+import { authOptions } from "@/authOptions";
+import { ResponseWithSocket } from "@/types/response-with-socket";
+import { resetBoard } from "@/use-cases/board/reset-board";
+import { resetBoardKey } from "@/use-cases/event-keys/board";
+import { getPlayersByBoardId } from "@/use-cases/player/get-players-by-board-id";
+import { NextApiRequest } from "next";
+import { getServerSession } from "next-auth";
+
+const resetBoardHandler = async (
+ req: NextApiRequest,
+ res: ResponseWithSocket
+) => {
+ if (req.method !== "POST") {
+ res.status(405).json({ message: "Method Not Allowed" });
+ return;
+ }
+
+ try {
+ const { boardId } = req.body;
+
+ if (!boardId) {
+ res.status(400).json({ message: "Board ID is required" });
+ return;
+ }
+
+ const session = await getServerSession(req, res, authOptions);
+
+ if (!session?.user) {
+ return res.status(401).json({ message: "Unauthorized" });
+ }
+
+ const result = await resetBoard({ boardId });
+
+ if (!result.count)
+ return res.status(404).json({ message: "Error while reseting board" });
+
+ const eventKey = resetBoardKey(boardId);
+
+ const players = await getPlayersByBoardId({ boardId });
+
+ const message = { boardId, players };
+
+ res.socket?.server?.io?.emit(eventKey, message);
+ } catch (error: any) {
+ return res.status(500).json({ message: error.message });
+ }
+};
+
+export default resetBoardHandler;
diff --git a/pages/api/reveal-cards.ts b/pages/api/reveal-cards.ts
index a17c96a..4ef885c 100644
--- a/pages/api/reveal-cards.ts
+++ b/pages/api/reveal-cards.ts
@@ -1,8 +1,12 @@
import { ResponseWithSocket } from "@/types/response-with-socket";
import { revealCardsKey } from "@/use-cases/event-keys/board";
+import { getPlayersByBoardId } from "@/use-cases/player/get-players-by-board-id";
import { NextApiRequest } from "next";
-const revealCardsHandler = (req: NextApiRequest, res: ResponseWithSocket) => {
+const revealCardsHandler = async (
+ req: NextApiRequest,
+ res: ResponseWithSocket
+) => {
if (req.method !== "POST") {
res.status(405).json({ message: "Method Not Allowed" });
return;
@@ -16,9 +20,11 @@ const revealCardsHandler = (req: NextApiRequest, res: ResponseWithSocket) => {
return;
}
+ const players = await getPlayersByBoardId({ boardId });
+
const eventKey = revealCardsKey(boardId);
- const message = { reveal };
+ const message = { reveal, players };
res.socket?.server?.io?.emit(eventKey, message);
diff --git a/pages/api/socket.ts b/pages/api/socket.ts
index b22e6e9..d682c3b 100644
--- a/pages/api/socket.ts
+++ b/pages/api/socket.ts
@@ -10,7 +10,6 @@ export const config = {
};
const ioHandler = (req: NextApiRequest, res: ResponseWithSocket) => {
- console.log("Socket server handler", res.socket.server);
if (!res.socket.server.io) {
const path = "/api/socket";
const httpServer = res.socket.server as unknown as NetServer;
@@ -18,7 +17,6 @@ const ioHandler = (req: NextApiRequest, res: ResponseWithSocket) => {
path,
addTrailingSlash: false,
});
- console.log("Socket server created", io, httpServer);
res.socket.server.io = io;
}
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index 4af4abf..0d55e9d 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -3,8 +3,9 @@ generator client {
}
datasource db {
- provider = "postgresql"
- url = env("DATABASE_URL")
+ provider = "postgresql"
+ url = env("DATABASE_URL")
+ directUrl = env("POSTGRES_URL_NON_POOLING")
}
model Room {
diff --git a/use-cases/board/choice-options.ts b/use-cases/board/choice-options.ts
index 9a989d8..678a657 100644
--- a/use-cases/board/choice-options.ts
+++ b/use-cases/board/choice-options.ts
@@ -1,7 +1,6 @@
import { ChoiceOptions } from "@/types/choice-options";
export const fibonacciChoiceOptions: ChoiceOptions = [
- { value: "0", label: "0" },
{ value: "1", label: "1" },
{ value: "2", label: "2" },
{ value: "3", label: "3" },
@@ -10,7 +9,6 @@ export const fibonacciChoiceOptions: ChoiceOptions = [
{ value: "13", label: "13" },
{ value: "20", label: "20" },
{ value: "40", label: "40" },
- { value: "100", label: "100" },
{ value: "coffe", label: "☕" },
- { value: "?", label: "?" },
+ { value: "question", label: "?" },
];
diff --git a/use-cases/board/create-board.ts b/use-cases/board/create-board.ts
index 067cb84..1351a50 100644
--- a/use-cases/board/create-board.ts
+++ b/use-cases/board/create-board.ts
@@ -10,8 +10,6 @@ export const createBoard = validator({
handler: async ({ roomId }) => {
const board = await getBoard({ roomId });
- console.log("board", board);
-
if (board) {
throw new Error("Board already exists");
}
diff --git a/use-cases/board/reset-board.ts b/use-cases/board/reset-board.ts
new file mode 100644
index 0000000..8659b8e
--- /dev/null
+++ b/use-cases/board/reset-board.ts
@@ -0,0 +1,21 @@
+import { db } from "@/lib/db";
+import { z } from "zod";
+import { validator } from "../validator";
+
+const input = z.object({
+ boardId: z.string(),
+});
+
+export const resetBoard = validator({
+ input,
+ handler: async ({ boardId }) => {
+ return await db.player.updateMany({
+ where: {
+ boardId,
+ },
+ data: {
+ choice: null,
+ },
+ });
+ },
+});
diff --git a/use-cases/event-keys/board.ts b/use-cases/event-keys/board.ts
index 0f23a59..9fa843d 100644
--- a/use-cases/event-keys/board.ts
+++ b/use-cases/event-keys/board.ts
@@ -5,4 +5,5 @@ const createBoardEventKey = (event: string) => {
export const joinBoardKey = createBoardEventKey("join");
export const leaveBoardKey = createBoardEventKey("leave");
export const playerChoiceKey = createBoardEventKey("choice");
-export const revealCardsKey = createBoardEventKey("choice");
+export const revealCardsKey = createBoardEventKey("reveal");
+export const resetBoardKey = createBoardEventKey("reset");
diff --git a/use-cases/invite/handle-invite.ts b/use-cases/invite/handle-invite.ts
index c062686..3ba1a80 100644
--- a/use-cases/invite/handle-invite.ts
+++ b/use-cases/invite/handle-invite.ts
@@ -11,7 +11,18 @@ export const handleInvite = async (code: string) => {
throw new Error("No invite code found in URL");
}
- const [roomId, createdAt] = Buffer.from(code, "base64").toString().split(":");
+ const invalidMap: Record = {
+ undefined: true,
+ null: true,
+ NaN: true,
+ };
+
+ const [roomId, createdAt] = Buffer.from(code, "base64")
+ .toString()
+ .split(":")
+ .filter((part) => !invalidMap[part]);
+
+ console.log(roomId, createdAt);
if (!roomId || !createdAt) {
throw new Error("Invalid invite code");
@@ -23,6 +34,8 @@ export const handleInvite = async (code: string) => {
throw new Error("Invite code has expired");
}
+ console.log(roomId);
+
const room = await getRoom({ roomId });
if (!room) {
diff --git a/use-cases/player/join-or-create-board-as-player.ts b/use-cases/player/join-or-create-board-as-player.ts
index 2bb3573..4e62b89 100644
--- a/use-cases/player/join-or-create-board-as-player.ts
+++ b/use-cases/player/join-or-create-board-as-player.ts
@@ -32,8 +32,6 @@ export const joinOrCreateBoardAsPlayer = validator({
},
});
- console.log({ existingPlayer });
-
if (existingPlayer) {
return existingPlayer;
}