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 (
-
-
- }>
- } />
- } />
- } />
- } />
- } />
-
-
-
+
+
+
+ }>
+ } />
+ } />
+ } />
+ } />
+ } />
+
+
+
+ GoogleOAuthProvider>
);
}
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