diff --git a/backend/src/friends/friends.controller.ts b/backend/src/friends/friends.controller.ts index 18de8b60..2534bc4e 100644 --- a/backend/src/friends/friends.controller.ts +++ b/backend/src/friends/friends.controller.ts @@ -1,4 +1,13 @@ -import { Body, Controller, Delete, Get, Post, Put } from '@nestjs/common'; +import { + Body, + Controller, + Delete, + Get, + HttpException, + HttpStatus, + Post, + Put, +} from '@nestjs/common'; import { UpdateFriendDto } from './dto/updateFriend.dto'; import { FriendsService } from './friends.service'; import { CreateFriendDto } from './dto/createFriend.dto'; @@ -10,7 +19,20 @@ export class FriendsController { @Post() async createFriend(@Body() createFriendDto: CreateFriendDto) { - return this.friendsService.createFriend(createFriendDto); + try { + return this.friendsService.createFriend(createFriendDto); + } catch (error) { + throw new HttpException( + { + status: HttpStatus.NOT_MODIFIED, + error: 'Friend not created', + }, + HttpStatus.NOT_MODIFIED, + { + cause: error, + }, + ); + } } @Get() diff --git a/backend/src/lobby/lobby.controller.ts b/backend/src/lobby/lobby.controller.ts index 38fd310a..73a6272e 100644 --- a/backend/src/lobby/lobby.controller.ts +++ b/backend/src/lobby/lobby.controller.ts @@ -1,4 +1,11 @@ -import { Body, Controller, Get, Param } from '@nestjs/common'; +import { + Body, + Controller, + Get, + HttpException, + HttpStatus, + Param, +} from '@nestjs/common'; import { LobbyService } from './lobby.service'; import { GetLeaderboardDto } from './dto/getLeaderboard.dto'; @@ -8,12 +15,41 @@ export class LobbyController { @Get('leaderboard') getAllLeaderboard(@Body() getLeaderboardDto: GetLeaderboardDto) { - return this.lobbyService.getLeaderboard(getLeaderboardDto, -1); + try { + return this.lobbyService.getLeaderboard(getLeaderboardDto, -1); + } catch (error) { + throw new HttpException( + { + status: HttpStatus.FORBIDDEN, + error: 'No leaderboard found', + }, + HttpStatus.FORBIDDEN, + { + cause: error, + }, + ); + } } - @Get('leaderboard/:limit') - getSomeLeaderboard(@Body() getLeaderboardDto: GetLeaderboardDto, @Param('limit') limit: string) { - const parsedLimit = parseInt(limit, 10); - return this.lobbyService.getLeaderboard(getLeaderboardDto, parsedLimit); + @Get('leaderboard/:limit') + getSomeLeaderboard( + @Body() getLeaderboardDto: GetLeaderboardDto, + @Param('limit') limit: string, + ) { + const parsedLimit = parseInt(limit, 10); + try { + return this.lobbyService.getLeaderboard(getLeaderboardDto, parsedLimit); + } catch (error) { + throw new HttpException( + { + status: HttpStatus.FORBIDDEN, + error: 'No leaderboard found', + }, + HttpStatus.FORBIDDEN, + { + cause: error, + }, + ); + } } } diff --git a/backend/src/users/users.controller.ts b/backend/src/users/users.controller.ts index 808776b9..f8288d36 100644 --- a/backend/src/users/users.controller.ts +++ b/backend/src/users/users.controller.ts @@ -6,6 +6,8 @@ import { Param, Patch, Post, + HttpException, + HttpStatus, } from '@nestjs/common'; import { UsersService } from './users.service'; import { UpdateUserDto } from './dto/updateUser.dto'; @@ -17,31 +19,109 @@ export class UsersController { @Get('me') findMe(@Body() dto: any) { - return this.service.findMe(dto); + try { + return this.service.findMe(dto); + } catch (error) { + throw new HttpException( + { + status: HttpStatus.FORBIDDEN, + error: 'No user found', + }, + HttpStatus.FORBIDDEN, + { + cause: error, + }, + ); + } } @Get() findAll() { - return this.service.findAll(); + try { + return this.service.findAll(); + } catch (error) { + throw new HttpException( + { + status: HttpStatus.FORBIDDEN, + error: 'No users found', + }, + HttpStatus.FORBIDDEN, + { + cause: error, + }, + ); + } } @Get(':login') findOne(@Param('login') login: string) { - return this.service.findOne(login); + try { + return this.service.findOne(login); + } catch (error) { + throw new HttpException( + { + status: HttpStatus.FORBIDDEN, + error: 'No user found', + }, + HttpStatus.FORBIDDEN, + { + cause: error, + }, + ); + } } @Post() create(@Body() createUserDto: CreateUserDto) { - return this.service.create(createUserDto); + try { + return this.service.create(createUserDto); + } catch (error) { + throw new HttpException( + { + status: HttpStatus.FORBIDDEN, + error: 'User already exists', + }, + HttpStatus.FORBIDDEN, + { + cause: error, + }, + ); + } } @Patch(':login') update(@Param('login') login: string, @Body() updateUserDto: UpdateUserDto) { - return this.service.update(login, updateUserDto); + try { + return this.service.update(login, updateUserDto); + } catch (error) { + throw new HttpException( + { + status: HttpStatus.NOT_MODIFIED, + error: 'User not modified', + }, + HttpStatus.NOT_MODIFIED, + { + cause: error, + }, + ); + } } @Delete(':login') remove(@Param('login') login: string) { - return this.service.remove(login); + try { + return this.service.remove(login); + } catch (error) { + throw new HttpException( + { + status: HttpStatus.NOT_MODIFIED, + error: 'User not deleted', + }, + HttpStatus.NOT_MODIFIED, + { + cause: error, + }, + ); + } } } diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 77ad5816..aebd34ad 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -24,6 +24,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-hook-form": "^7.44.3", + "react-hot-toast": "^2.4.1", "react-konva": "^18.2.9", "react-popper": "^2.3.0", "socket.io-client": "^4.6.2", @@ -2228,6 +2229,14 @@ "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==" }, + "node_modules/goober": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.13.tgz", + "integrity": "sha512-jFj3BQeleOoy7t93E9rZ2de+ScC4lQICLwiAQmKMg9F6roKGaLSHoCDYKkWlSafg138jejvq/mTdvmnwDQgqoQ==", + "peerDependencies": { + "csstype": "^3.0.10" + } + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -3584,6 +3593,21 @@ "react": "^16.8.0 || ^17 || ^18" } }, + "node_modules/react-hot-toast": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.4.1.tgz", + "integrity": "sha512-j8z+cQbWIM5LY37pR6uZR6D4LfseplqnuAO4co4u8917hBUvXlEqyP1ZzqVLcqoyUesZZv/ImreoCeHVDpE5pQ==", + "dependencies": { + "goober": "^2.1.10" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -6025,6 +6049,12 @@ "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==" }, + "goober": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.13.tgz", + "integrity": "sha512-jFj3BQeleOoy7t93E9rZ2de+ScC4lQICLwiAQmKMg9F6roKGaLSHoCDYKkWlSafg138jejvq/mTdvmnwDQgqoQ==", + "requires": {} + }, "gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -6899,6 +6929,14 @@ "integrity": "sha512-AbHeZ4ad+0dEIknSW9dBgIwcvRDfZ1O97sgj75WaMdOX0eg8TBiUf9wxzVkIjZbk76BBIE9lmFOzyD4PN80ZQg==", "requires": {} }, + "react-hot-toast": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.4.1.tgz", + "integrity": "sha512-j8z+cQbWIM5LY37pR6uZR6D4LfseplqnuAO4co4u8917hBUvXlEqyP1ZzqVLcqoyUesZZv/ImreoCeHVDpE5pQ==", + "requires": { + "goober": "^2.1.10" + } + }, "react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index 38c40e80..4206c046 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -25,6 +25,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-hook-form": "^7.44.3", + "react-hot-toast": "^2.4.1", "react-konva": "^18.2.9", "react-popper": "^2.3.0", "socket.io-client": "^4.6.2", diff --git a/frontend/src/app/(private)/layout.tsx b/frontend/src/app/(private)/layout.tsx index 3b2c5c79..8d4d1e0d 100644 --- a/frontend/src/app/(private)/layout.tsx +++ b/frontend/src/app/(private)/layout.tsx @@ -7,6 +7,7 @@ import Chat from "@/components/Chat"; import Header from "@/components/Header"; import Providers from "../login/providers"; import { ChatProvider } from "@/contexts/ChatContext"; +import { Toaster } from "react-hot-toast"; export const metadata: Metadata = { title: "Game | 42 Transcendence", @@ -21,6 +22,7 @@ export default function RootPrivateLayout({ return ( +
diff --git a/frontend/src/app/error.tsx b/frontend/src/app/error.tsx new file mode 100644 index 00000000..c67321bb --- /dev/null +++ b/frontend/src/app/error.tsx @@ -0,0 +1,30 @@ +"use client"; // Error components must be Client Components + +import { useEffect } from "react"; + +export default function Error({ + error, + reset, +}: { + error: Error; + reset: () => void; +}) { + useEffect(() => { + // Log the error to an error reporting service + console.error(error); + }, [error]); + + return ( +
+

Something went wrong!

+ +
+ ); +} diff --git a/frontend/src/app/login/auth/page.tsx b/frontend/src/app/login/auth/page.tsx index a2a34266..4081b257 100644 --- a/frontend/src/app/login/auth/page.tsx +++ b/frontend/src/app/login/auth/page.tsx @@ -1,6 +1,7 @@ "use client"; import { useRouter } from "next/navigation"; import { useState } from "react"; +import { toast } from "react-hot-toast"; // TODO: refactor states and input to be a client component, and use the client component here, also remove use client from top @@ -22,6 +23,7 @@ export default function AuthPage() { const handleSubmit = () => { // Handle submission logic here + toast.success("Código validado com sucesso!"); console.log("Submitted code:", code); router.push("/game"); }; diff --git a/frontend/src/app/login/layout.tsx b/frontend/src/app/login/layout.tsx index 8e0ccbc0..2f062348 100644 --- a/frontend/src/app/login/layout.tsx +++ b/frontend/src/app/login/layout.tsx @@ -1,5 +1,6 @@ import { Metadata } from "next"; import Providers from "./providers"; +import { Toaster } from "react-hot-toast"; export const metadata: Metadata = { title: "42 Transcendence", @@ -14,6 +15,7 @@ export default function RootLoginPublicLayout({ return ( + {children} diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index b8367420..699dde1d 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -1,8 +1,10 @@ import Image from "next/image"; +import { Toaster } from "react-hot-toast"; export default function Page() { return (
+
diff --git a/frontend/src/contexts/GameContext.tsx b/frontend/src/contexts/GameContext.tsx index cfce2150..fbb17393 100644 --- a/frontend/src/contexts/GameContext.tsx +++ b/frontend/src/contexts/GameContext.tsx @@ -52,7 +52,7 @@ export type MovePlayerData = { direction: string; }; -export type GameLayout = 'default' | 'sunlight' | 'moonlight' | 'dark'; +export type GameLayout = "default" | "sunlight" | "moonlight" | "dark"; type GameContextType = { waitingPlayer2: boolean; @@ -79,7 +79,7 @@ export const GameProvider = ({ children }: GameProviderProps) => { const [clientId, setClientId] = useState(""); const [gameData, setGameData] = useState({} as GameData); const [gameFinishedData, setGameFinishedData] = useState({} as GameData); - const [gameLayout, setGameLayout] = useState('default'); + const [gameLayout, setGameLayout] = useState("default"); const socket = useRef(null); @@ -180,8 +180,8 @@ export const GameProvider = ({ children }: GameProviderProps) => { gameAbandoned, gameFinishedData, gameData, - gameLayout, - setGameLayout + gameLayout, + setGameLayout, }} > {children} diff --git a/frontend/src/services/queryClient.ts b/frontend/src/services/queryClient.ts index 6c7b9ded..320c760b 100644 --- a/frontend/src/services/queryClient.ts +++ b/frontend/src/services/queryClient.ts @@ -1,3 +1,12 @@ -import { QueryClient } from "@tanstack/react-query"; +import { QueryCache, QueryClient } from "@tanstack/react-query"; +import toast from "react-hot-toast"; -export const queryClient = new QueryClient(); +export const queryClient = new QueryClient({ + queryCache: new QueryCache({ + onError: (error: any, query) => { + if (query.state.data !== undefined) { + toast.error(`Something went wrong: ${error.message}`); + } + }, + }), +});