ADD Google OAuth login and logout functionality on frontend
This commit is contained in:
10
frontend/package-lock.json
generated
10
frontend/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 (
|
||||
<Router>
|
||||
<Routes>
|
||||
<Route element={<MainLayout />}>
|
||||
<Route path="/" element={<Home />} />
|
||||
<Route path="/create" element={<ClipUpload />} />
|
||||
<Route path="/create/:id" element={<ClipEdit />} />
|
||||
<Route path="/my-clips" element={<MyClips />} />
|
||||
<Route path="/video/:id" element={<VideoPlayer />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
</Router>
|
||||
<GoogleOAuthProvider clientId={import.meta.env.VITE_GOOGLE_CLIENT_ID}>
|
||||
<Router>
|
||||
<Routes>
|
||||
<Route element={<MainLayout />}>
|
||||
<Route path="/" element={<Home />} />
|
||||
<Route path="/create" element={<ClipUpload />} />
|
||||
<Route path="/create/:id" element={<ClipEdit />} />
|
||||
<Route path="/my-clips" element={<MyClips />} />
|
||||
<Route path="/video/:id" element={<VideoPlayer />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
</Router>
|
||||
</ GoogleOAuthProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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) =>
|
||||
</div>
|
||||
) :
|
||||
(
|
||||
<GoogleOAuthProvider
|
||||
clientId={import.meta.env.VITE_GOOGLE_CLIENT_ID}>
|
||||
<GoogleLogin
|
||||
onSuccess={(credentialResponse) => {
|
||||
if (!credentialResponse.credential) {
|
||||
console.error("No credential received from Google Login");
|
||||
return;
|
||||
}
|
||||
login(credentialResponse.credential).then(() => {navigate(0)});
|
||||
}}
|
||||
/>
|
||||
</GoogleOAuthProvider>
|
||||
<GoogleLogin
|
||||
shape={"pill"}
|
||||
useOneTap={false}
|
||||
onSuccess={(credentialResponse) => handleLogin(credentialResponse)} />
|
||||
)}
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -10,16 +10,16 @@ const MainLayout = () => {
|
||||
const [sidebarToggled, setSidebarToggled] = useState(false);
|
||||
const [user, setUser] = useState<null | User>(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}/>
|
||||
<div className="overflow-auto">
|
||||
<Outlet />
|
||||
</div>
|
||||
|
||||
@@ -30,6 +30,23 @@ const login = async (GoogleToken: string): Promise<string> => {
|
||||
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<boolean> => {
|
||||
|
||||
export {
|
||||
login,
|
||||
logout,
|
||||
uploadFile,
|
||||
editFile,
|
||||
processFile,
|
||||
|
||||
@@ -51,4 +51,21 @@ public class UserController {
|
||||
new APIResponse<>("success", "Logged in successfully", new TokenDTO(jwt))
|
||||
);
|
||||
}
|
||||
|
||||
@PostMapping("/logout")
|
||||
public ResponseEntity<APIResponse<Void>> 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)
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user