Index: frontend/src/App.tsx
===================================================================
--- frontend/src/App.tsx	(revision a3ae097ff557650aeeb473a86f99d8430c23d60d)
+++ frontend/src/App.tsx	(revision d47e22514aceaaacf9474187f2d3ee201e310ecd)
@@ -1,34 +1,52 @@
-import { useEffect } from "react";
-import { BrowserRouter, Routes, Route } from "react-router-dom";
+import { createBrowserRouter, Outlet, RouterProvider } from "react-router-dom";
 import AllUsers from "./components/userProfile/AllUsersHelper";
 import UserDetail from "./components/userProfile/UserDetailView";
+import LandingPage from "./LandingPage";
+import Login from "./Login";
+import Nav from "./Nav";
+import Register from "./Register";
 
-function Home() {
-  useEffect(() => {
-    const fetchData = async () => {
-      const response = await fetch("http://localhost:8080/");
-      const data = await response.json();
-      console.log(data);
-    };
-    fetchData();
-  });
+const Layout = () => {
+	return (
+		<div className="flex flex-col min-h-screen">
+			<Nav />
+			<main className="grow">
+				<Outlet />
+			</main>
+		</div>
+	);
+};
 
-  return (
-    <>
-      <p className="text-red-500">ok</p>
-    </>
-  );
-}
+const router = createBrowserRouter([
+	{
+		path: "",
+		element: <Layout />,
+		children: [
+			{
+				path: "/",
+				element: <LandingPage />,
+			},
+			{
+				path: "/login",
+				element: <Login />,
+			},
+			{
+				path: "/users",
+				element: <AllUsers />,
+			},
+			{
+				path: "/users/:userId",
+				element: <UserDetail />,
+			},
+			{
+				path: "/register",
+				element: <Register />,
+			},
+		],
+	},
+]);
 
 function App() {
-  return (
-    <BrowserRouter>
-      <Routes>
-        <Route path="/" element={<Home />} />
-        <Route path="/users" element={<AllUsers />} />
-        <Route path="/users/:userId" element={<UserDetail />} />
-      </Routes>
-    </BrowserRouter>
-  );
+	return <RouterProvider router={router} />;
 }
 
Index: frontend/src/LandingPage.tsx
===================================================================
--- frontend/src/LandingPage.tsx	(revision d47e22514aceaaacf9474187f2d3ee201e310ecd)
+++ frontend/src/LandingPage.tsx	(revision d47e22514aceaaacf9474187f2d3ee201e310ecd)
@@ -0,0 +1,5 @@
+const LandingPage = () => {
+	return <div>landing page</div>;
+};
+
+export default LandingPage;
Index: frontend/src/Login.tsx
===================================================================
--- frontend/src/Login.tsx	(revision d47e22514aceaaacf9474187f2d3ee201e310ecd)
+++ frontend/src/Login.tsx	(revision d47e22514aceaaacf9474187f2d3ee201e310ecd)
@@ -0,0 +1,71 @@
+import { useState } from "react";
+import { useNavigate } from "react-router-dom";
+import axiosInstance, { scheduleTokenRefresh } from "./api/axiosInstance";
+import { useAuth } from "./context/authContext";
+import type { User } from "./types";
+
+const Login = () => {
+	const [username, setUsername] = useState("");
+	const [password, setPassword] = useState("");
+	const [error, setError] = useState<string | null>(null);
+	const { setUser } = useAuth();
+	const navigate = useNavigate();
+
+	const handleLogin = async (e: React.FormEvent) => {
+		e.preventDefault();
+		try {
+			const response = await axiosInstance.post<{
+				user: User;
+				tokenExpiresIn: number;
+			}>("/auth/login", { username, password });
+			scheduleTokenRefresh(response.data.tokenExpiresIn);
+			setUser(response.data.user);
+			navigate("/");
+		} catch (error) {
+			setError("Login failed. Please check your credentials.");
+		}
+	};
+	return (
+		<div className="flex flex-col items-center justify-center min-h-[90vh] bg-gray-100">
+			<h2 className="text-2xl mb-4">Login</h2>
+			<form
+				onSubmit={handleLogin}
+				className="bg-white p-6 rounded shadow-md w-80"
+			>
+				{error && <p className="text-red-500 mb-4">{error}</p>}
+				<div className="mb-4">
+					<label className="block text-gray-700 mb-2" htmlFor="username">
+						Username
+					</label>
+					<input
+						type="text"
+						id="username"
+						className="w-full p-2 border border-gray-300 rounded"
+						value={username}
+						onChange={(e) => setUsername(e.target.value)}
+					/>
+				</div>
+				<div className="mb-4">
+					<label className="block text-gray-700 mb-2" htmlFor="password">
+						Password
+					</label>
+					<input
+						type="password"
+						id="password"
+						className="w-full p-2 border border-gray-300 rounded"
+						value={password}
+						onChange={(e) => setPassword(e.target.value)}
+					/>
+				</div>
+				<button
+					type="submit"
+					className="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-lg text-sm font-medium transition-colors duration-200 w-full cursor-pointer"
+				>
+					Login
+				</button>
+			</form>
+		</div>
+	);
+};
+
+export default Login;
Index: frontend/src/Nav.tsx
===================================================================
--- frontend/src/Nav.tsx	(revision d47e22514aceaaacf9474187f2d3ee201e310ecd)
+++ frontend/src/Nav.tsx	(revision d47e22514aceaaacf9474187f2d3ee201e310ecd)
@@ -0,0 +1,85 @@
+import { Link } from "react-router-dom";
+import axiosInstance, { baseURL } from "./api/axiosInstance";
+import Logo from "./assets/logo-finkwave.png";
+import { useAuth } from "./context/authContext";
+
+const Nav = () => {
+	const { user, setUser, isAuthLoading } = useAuth();
+
+	const handleLogout = async (e: React.MouseEvent<HTMLButtonElement>) => {
+		e.preventDefault();
+		try {
+			await axiosInstance.post("/auth/logout");
+			setUser(undefined);
+		} catch (error) {
+			console.error("Logout failed:", error);
+		}
+	};
+
+	return (
+		<div className="bg-gray-800 p-4 flex justify-between items-center">
+			<Link to="/" className="text-white text-lg font-semibold">
+				<img src={Logo} alt="Finkwave Logo" className="h-12 w-auto" />
+			</Link>
+
+			<div className="flex items-center space-x-4">
+				{!isAuthLoading && (
+					<div className="flex items-center space-x-3">
+						{user ? (
+							<div className="flex items-center space-x-3">
+								<div className="flex items-center space-x-2 bg-slate-700 rounded-lg px-3 py-2">
+									{user.profilePhoto ? (
+										<img
+											src={`${baseURL}/${user.profilePhoto}`}
+											alt={`${user.username}'s profile`}
+											className="w-8 h-8 rounded-full object-cover"
+										/>
+									) : (
+										<div className="w-8 h-8 bg-blue-500 rounded-full flex items-center justify-center">
+											<span className="text-white text-sm font-semibold">
+												{user.username.charAt(0).toUpperCase()}
+											</span>
+										</div>
+									)}
+
+									<div className="text-white">
+										<p className="text-sm font-medium">{user.username}</p>
+										<p className="text-xs text-gray-300 capitalize">
+											{user.role}
+										</p>
+									</div>
+								</div>
+								<button
+									onClick={handleLogout}
+									className="bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-lg text-sm 
+                                    font-medium transition-colors duration-200 flex items-center space-x-1 cursor-pointer"
+								>
+									Logout
+								</button>
+							</div>
+						) : (
+							<div className="flex items-center space-x-4">
+								<Link
+									to="/login"
+									className="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-lg text-sm 
+                                    font-medium transition-colors duration-200 cursor-pointer"
+								>
+									Login
+								</Link>
+								<Link
+									to="/register"
+									className="bg-green-500 hover:bg-green-600 text-white px-4 py-2 rounded-lg text-sm
+                                    font-medium transition-colors duration-200 cursor-pointer"
+								>
+									Register
+								</Link>
+							</div>
+						)}
+					</div>
+				)}
+			</div>
+		</div>
+	);
+};
+
+export default Nav;
Index: frontend/src/Register.tsx
===================================================================
--- frontend/src/Register.tsx	(revision d47e22514aceaaacf9474187f2d3ee201e310ecd)
+++ frontend/src/Register.tsx	(revision d47e22514aceaaacf9474187f2d3ee201e310ecd)
@@ -0,0 +1,160 @@
+import { useEffect, useState } from "react";
+import { useNavigate } from "react-router";
+import axiosInstance, { scheduleTokenRefresh } from "./api/axiosInstance";
+import { useAuth } from "./context/authContext";
+import type { User } from "./types";
+
+const Register = () => {
+	const { setUser } = useAuth();
+	const [username, setUsername] = useState("");
+	const [password, setPassword] = useState("");
+	const [fullname, setFullname] = useState("");
+	const [email, setEmail] = useState("");
+	const [profilePhotoFile, setProfilePhotoFile] = useState<File | null>(null);
+	const [previewProfilePhoto, setPreviewProfilePhoto] = useState<string | null>(
+		null,
+	);
+
+	const navigate = useNavigate();
+
+	useEffect(() => {
+		return () => {
+			if (previewProfilePhoto) {
+				URL.revokeObjectURL(previewProfilePhoto);
+			}
+		};
+	}, [previewProfilePhoto]);
+
+	const handleRegister = async (e: React.FormEvent) => {
+		e.preventDefault();
+		// todo: add proper error handling
+		if (profilePhotoFile && profilePhotoFile.size > 5 * 1024 * 1024) {
+			alert("Max file size is 5MB");
+			return;
+		}
+
+		if (profilePhotoFile && !profilePhotoFile.type.startsWith("image/")) {
+			alert("Only images allowed");
+			return;
+		}
+		const formData = new FormData();
+		if (profilePhotoFile) formData.append("profilePhoto", profilePhotoFile);
+		formData.append("username", username);
+		formData.append("fullname", fullname);
+		formData.append("email", email);
+		formData.append("password", password);
+
+		try {
+			const response = await axiosInstance.post<{
+				user: User;
+				tokenExpiresIn: number;
+			}>("/auth/register", formData);
+			scheduleTokenRefresh(response.data.tokenExpiresIn);
+			setUser(response.data.user);
+			navigate("/");
+		} catch (error) {
+			console.error("Registration failed:", error);
+		}
+	};
+
+	return (
+		<div className="flex flex-col items-center justify-center min-h-[90vh] bg-gray-100">
+			<h2 className="text-2xl mb-4">Register</h2>
+			<form className="bg-white p-6 rounded shadow-md w-80">
+				<div className="mb-4">
+					<label className="block text-gray-700 mb-2" htmlFor="username">
+						Username
+					</label>
+					<input
+						type="text"
+						id="username"
+						className="w-full p-2 border border-gray-300 rounded"
+						value={username}
+						onChange={(e) => setUsername(e.target.value)}
+					/>
+				</div>
+				<div className="mb-4">
+					<label className="block text-gray-700 mb-2" htmlFor="password">
+						Password
+					</label>
+					<input
+						type="password"
+						id="password"
+						className="w-full p-2 border border-gray-300 rounded"
+						value={password}
+						onChange={(e) => setPassword(e.target.value)}
+					/>
+				</div>
+				<div className="mb-4">
+					<label className="block text-gray-700 mb-2" htmlFor="fullName">
+						Full Name
+					</label>
+					<input
+						type="text"
+						id="fullName"
+						className="w-full p-2 border border-gray-300 rounded"
+						value={fullname}
+						onChange={(e) => setFullname(e.target.value)}
+					/>
+				</div>
+				<div className="mb-4">
+					<label className="block text-gray-700 mb-2" htmlFor="email">
+						Email
+					</label>
+					<input
+						type="email"
+						id="email"
+						className="w-full p-2 border border-gray-300 rounded"
+						value={email}
+						onChange={(e) => setEmail(e.target.value)}
+					/>
+				</div>
+				<div className="mb-4">
+					<label className="block text-gray-700 mb-2">Profile Photo</label>
+					<div className="flex items-center gap-3">
+						<label
+							htmlFor="profilePhoto"
+							className="px-4 py-2 bg-blue-500 text-white rounded cursor-pointer hover:bg-blue-600 transition-colors text-sm font-medium"
+						>
+							Choose File
+						</label>
+						<span className="text-gray-600 text-sm">
+							{profilePhotoFile ? profilePhotoFile.name : "No file chosen"}
+						</span>
+					</div>
+					<input
+						type="file"
+						accept="image/*"
+						id="profilePhoto"
+						className="hidden"
+						onChange={(e) => {
+							if (e.target.files && e.target.files[0]) {
+								setProfilePhotoFile(e.target.files[0]);
+								setPreviewProfilePhoto(URL.createObjectURL(e.target.files[0]));
+							}
+						}}
+					/>
+				</div>
+				{previewProfilePhoto && (
+					<div className="mb-4">
+						<p className="block text-gray-700 mb-2">Profile Photo Preview:</p>
+						<img
+							src={previewProfilePhoto}
+							alt="Profile Preview"
+							className="w-20 h-20 object-cover rounded-full"
+						/>
+					</div>
+				)}
+				<button
+					type="submit"
+					className="w-full bg-blue-500 text-white p-2 rounded hover:bg-blue-600 cursor-pointer"
+					onClick={handleRegister}
+				>
+					Register
+				</button>
+			</form>
+		</div>
+	);
+};
+
+export default Register;
Index: frontend/src/api/axiosInstance.ts
===================================================================
--- frontend/src/api/axiosInstance.ts	(revision d47e22514aceaaacf9474187f2d3ee201e310ecd)
+++ frontend/src/api/axiosInstance.ts	(revision d47e22514aceaaacf9474187f2d3ee201e310ecd)
@@ -0,0 +1,88 @@
+import axios from "axios";
+
+export const baseURL = import.meta.env.VITE_API_BASE_URL;
+
+export const axiosInstance = axios.create({
+	baseURL,
+	withCredentials: true,
+});
+
+// set content-type to application/json unless sending FormData
+axiosInstance.interceptors.request.use((config) => {
+	if (!(config.data instanceof FormData)) {
+		config.headers["Content-Type"] = "application/json";
+	}
+	return config;
+});
+
+let refreshPromise: Promise<void> | null = null;
+let refreshTimeout: number | null = null;
+
+export const clearRefreshTimeout = () => {
+	if (refreshTimeout) {
+		clearTimeout(refreshTimeout);
+		refreshTimeout = null;
+	}
+};
+
+export const scheduleTokenRefresh = (expiryTimeSeconds: number) => {
+	clearRefreshTimeout();
+
+	if (!expiryTimeSeconds || expiryTimeSeconds <= 0) {
+		return;
+	}
+
+	const refreshTime = (expiryTimeSeconds - 60) * 1000;
+	if (refreshTime <= 0) {
+		refreshTokenMethod();
+	} else {
+		refreshTimeout = setTimeout(refreshTokenMethod, refreshTime);
+	}
+};
+
+export const refreshTokenMethod = async (): Promise<void | null> => {
+	if (refreshPromise) {
+		return refreshPromise;
+	}
+
+	refreshPromise = (async () => {
+		try {
+			const refreshResponse = await axiosInstance.post<{
+				tokenExpiresIn: number;
+			}>("/auth/refresh");
+			scheduleTokenRefresh(refreshResponse.data.tokenExpiresIn);
+		} catch (err) {
+			clearRefreshTimeout();
+			console.error(err);
+			throw err;
+		} finally {
+			refreshPromise = null;
+		}
+	})();
+	return refreshPromise;
+};
+
+axiosInstance.interceptors.response.use(
+	(response) => response,
+	async (error) => {
+		const originalConfig = error.config;
+		if (
+			[401, 403].includes(error.response?.status) &&
+			!originalConfig._retry &&
+			originalConfig.url !== "/auth/refresh"
+		) {
+			originalConfig._retry = true;
+
+			try {
+				await refreshTokenMethod();
+				return axiosInstance(originalConfig);
+			} catch (err) {
+				clearRefreshTimeout();
+				return Promise.reject(err);
+			}
+		}
+		return Promise.reject(error);
+	},
+);
+
+export default axiosInstance;
Index: frontend/src/context/authContext.tsx
===================================================================
--- frontend/src/context/authContext.tsx	(revision d47e22514aceaaacf9474187f2d3ee201e310ecd)
+++ frontend/src/context/authContext.tsx	(revision d47e22514aceaaacf9474187f2d3ee201e310ecd)
@@ -0,0 +1,63 @@
+import {
+	createContext,
+	useContext,
+	useEffect,
+	useState,
+	type Dispatch,
+	type ReactNode,
+	type SetStateAction,
+} from "react";
+import axiosInstance, {
+	refreshTokenMethod,
+	scheduleTokenRefresh,
+} from "../api/axiosInstance";
+import { type User } from "../types";
+
+interface AuthContextType {
+	user: User | undefined;
+	setUser: Dispatch<SetStateAction<User | undefined>>;
+	isAuthLoading: boolean;
+}
+
+const AuthContext = createContext<AuthContextType>({
+	user: undefined,
+	setUser: () => {},
+	isAuthLoading: false,
+});
+
+interface AuthProviderProps {
+	children: ReactNode;
+}
+
+export const AuthProvider = ({ children }: AuthProviderProps) => {
+	const [user, setUser] = useState<User | undefined>(undefined);
+	const [isAuthLoading, setIsAuthLoading] = useState(true);
+
+	useEffect(() => {
+		const fetchUserDetails = async () => {
+			try {
+				await refreshTokenMethod();
+				const response = await axiosInstance.get<{
+					tokenExpiresIn: number;
+					user: User;
+				}>("/auth/user");
+				setUser(response.data.user);
+				scheduleTokenRefresh(response.data.tokenExpiresIn);
+			} catch (error) {
+				setUser(undefined);
+			} finally {
+				setIsAuthLoading(false);
+			}
+		};
+
+		fetchUserDetails();
+	}, []);
+
+	return (
+		<AuthContext.Provider value={{ user, setUser, isAuthLoading }}>
+			{children}
+		</AuthContext.Provider>
+	);
+};
+
+export const useAuth = () => useContext(AuthContext);
Index: frontend/src/main.tsx
===================================================================
--- frontend/src/main.tsx	(revision a3ae097ff557650aeeb473a86f99d8430c23d60d)
+++ frontend/src/main.tsx	(revision d47e22514aceaaacf9474187f2d3ee201e310ecd)
@@ -2,9 +2,12 @@
 import { createRoot } from "react-dom/client";
 import App from "./App.tsx";
+import { AuthProvider } from "./context/authContext.tsx";
 import "./index.css";
 
 createRoot(document.getElementById("root")!).render(
 	<StrictMode>
-		<App />
+		<AuthProvider>
+			<App />
+		</AuthProvider>
 	</StrictMode>,
 );
Index: frontend/src/types.ts
===================================================================
--- frontend/src/types.ts	(revision d47e22514aceaaacf9474187f2d3ee201e310ecd)
+++ frontend/src/types.ts	(revision d47e22514aceaaacf9474187f2d3ee201e310ecd)
@@ -0,0 +1,7 @@
+export interface User {
+	username: string;
+	fullName: string;
+	email?: string;
+	profilePhoto?: string | null;
+	role?: "ADMIN" | "NONADMIN";
+}
