From 0c11abc7b9fe6556883774f837e7200dd55dee87 Mon Sep 17 00:00:00 2001 From: ThisBirchWood Date: Mon, 11 Aug 2025 15:16:47 +0200 Subject: [PATCH] ADD Google OAuth login and logout functionality on frontend --- frontend/package-lock.json | 10 +++ frontend/package.json | 1 + frontend/src/App.tsx | 25 +++---- frontend/src/components/Topbar.tsx | 65 ++++++++++++------- frontend/src/layouts/MainLayout.tsx | 21 +++--- frontend/src/utils/endpoints.ts | 18 +++++ .../vodsystem/controllers/UserController.java | 17 +++++ 7 files changed, 113 insertions(+), 44 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index c811e8e..7a4980f 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -15,6 +15,7 @@ "clsx": "^2.1.1", "dotenv": "^16.5.0", "flowbite-react": "^0.11.8", + "js-cookie": "^3.0.5", "lucide-react": "^0.511.0", "path": "^0.12.7", "react": "^19.1.0", @@ -3358,6 +3359,15 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 419829e..819c519 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -18,6 +18,7 @@ "clsx": "^2.1.1", "dotenv": "^16.5.0", "flowbite-react": "^0.11.8", + "js-cookie": "^3.0.5", "lucide-react": "^0.511.0", "path": "^0.12.7", "react": "^19.1.0", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 3b611f5..0c84db0 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -7,6 +7,7 @@ import Home from './pages/Home'; import {useEffect} from "react"; import MyClips from './pages/MyClips'; import VideoPlayer from "./pages/VideoPlayer.tsx"; +import { GoogleOAuthProvider } from '@react-oauth/google'; function App() { @@ -15,17 +16,19 @@ function App() { }, []); return ( - - - }> - } /> - } /> - } /> - } /> - } /> - - - + + + + }> + } /> + } /> + } /> + } /> + } /> + + + + ); } diff --git a/frontend/src/components/Topbar.tsx b/frontend/src/components/Topbar.tsx index d5352c1..3455952 100644 --- a/frontend/src/components/Topbar.tsx +++ b/frontend/src/components/Topbar.tsx @@ -1,25 +1,53 @@ import { Menu, X } from 'lucide-react'; +import { login, logout } from "../utils/endpoints.ts"; +import { Dropdown, DropdownItem } from "./Dropdown.tsx"; +import { GoogleLogin } from '@react-oauth/google'; + +import type { User } from "../utils/types.ts"; +import type { CredentialResponse } from '@react-oauth/google'; + import MenuButton from "./buttons/MenuButton.tsx"; import clsx from "clsx"; -import type {User} from "../utils/types.ts"; -import { login } from "../utils/endpoints.ts"; -import { Dropdown, DropdownItem } from "./Dropdown.tsx"; -import { GoogleOAuthProvider, GoogleLogin } from '@react-oauth/google'; -import {useNavigate} from "react-router-dom"; + type props = { sidebarToggled: boolean; - setSidebarToggled: Function; + setSidebarToggled: (toggled: boolean) => void; user: User | null; + fetchUser: () => void; className?: string; } -const Topbar = ({sidebarToggled, setSidebarToggled, user, className}: props) => { - const navigate = useNavigate(); +const Topbar = ({ + sidebarToggled, + setSidebarToggled, + user, + fetchUser, + className}: props) => { + + const handleLogin = (response: CredentialResponse) => { + if (!response.credential) { + console.error("No credential received from Google login."); + return; + } + + login(response.credential) + .then(() => { + fetchUser(); + }) + .catch((error) => { + console.error("Login failed:", error); + }); + } const handleLogout = () => { - // delete token cookie - document.cookie = "token=; Secure; SameSite=None; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;"; + logout() + .then(() => { + fetchUser(); + }) + .catch((error) => { + console.error("Logout failed:", error); + }); } return ( @@ -44,20 +72,11 @@ const Topbar = ({sidebarToggled, setSidebarToggled, user, className}: props) => ) : ( - - { - if (!credentialResponse.credential) { - console.error("No credential received from Google Login"); - return; - } - login(credentialResponse.credential).then(() => {navigate(0)}); - }} - /> - + handleLogin(credentialResponse)} /> )} - ) } diff --git a/frontend/src/layouts/MainLayout.tsx b/frontend/src/layouts/MainLayout.tsx index db8d02d..3e42247 100644 --- a/frontend/src/layouts/MainLayout.tsx +++ b/frontend/src/layouts/MainLayout.tsx @@ -10,16 +10,16 @@ const MainLayout = () => { const [sidebarToggled, setSidebarToggled] = useState(false); const [user, setUser] = useState(null); - useEffect(() => { - const fetchUser = async () => { - try { - const userData = await getUser(); - setUser(userData); - } catch (error) { - console.error('Failed to fetch user:', error); - } - }; + const fetchUser = async () => { + try { + const userData = await getUser(); + setUser(userData); + } catch (error) { + console.error('Failed to fetch user:', error); + } + }; + useEffect(() => { fetchUser(); }, []); @@ -32,7 +32,8 @@ const MainLayout = () => { className={"transition-all duration-300"} sidebarToggled={sidebarToggled} setSidebarToggled={setSidebarToggled} - user={user}/> + user={user} + fetchUser={fetchUser}/>
diff --git a/frontend/src/utils/endpoints.ts b/frontend/src/utils/endpoints.ts index c76f966..45da036 100644 --- a/frontend/src/utils/endpoints.ts +++ b/frontend/src/utils/endpoints.ts @@ -30,6 +30,23 @@ const login = async (GoogleToken: string): Promise => { return result.data.token; } +const logout = async () => { + const response = await fetch(API_URL + '/api/v1/auth/logout', { + method: 'POST', + credentials: 'include' + }); + + if (!response.ok) { + throw new Error(`Logout failed: ${response.status}`); + } + + const result: APIResponse = await response.json(); + + if (result.status === "error") { + throw new Error(`Logout failed: ${result.message}`); + } +} + /** * Uploads a file to the backend. * @param file - The file to upload. @@ -233,6 +250,7 @@ const isThumbnailAvailable = async (id: number): Promise => { export { login, + logout, uploadFile, editFile, processFile, diff --git a/src/main/java/com/ddf/vodsystem/controllers/UserController.java b/src/main/java/com/ddf/vodsystem/controllers/UserController.java index cd46d35..9a095c7 100644 --- a/src/main/java/com/ddf/vodsystem/controllers/UserController.java +++ b/src/main/java/com/ddf/vodsystem/controllers/UserController.java @@ -51,4 +51,21 @@ public class UserController { new APIResponse<>("success", "Logged in successfully", new TokenDTO(jwt)) ); } + + @PostMapping("/logout") + public ResponseEntity> logout(HttpServletResponse response) { + ResponseCookie cookie = ResponseCookie.from("token", "") + .httpOnly(true) + .maxAge(0) + .sameSite("None") + .secure(true) + .path("/") + .build(); + + response.addHeader("Set-Cookie", cookie.toString()); + + return ResponseEntity.ok( + new APIResponse<>("success", "Logged out successfully", null) + ); + } } \ No newline at end of file