Index: .gitignore
===================================================================
--- .gitignore	(revision 5c65932148f640bedd107abbafc22d3752f398be)
+++ .gitignore	(revision 0c86e77a055e354a71be0ff7c053051ca180993b)
@@ -1,1 +1,2 @@
 .idea/
+**/.env
Index: frontend/.env.example
===================================================================
--- frontend/.env.example	(revision 0c86e77a055e354a71be0ff7c053051ca180993b)
+++ frontend/.env.example	(revision 0c86e77a055e354a71be0ff7c053051ca180993b)
@@ -0,0 +1,1 @@
+VITE_API_BASE_URL=x
Index: frontend/package-lock.json
===================================================================
--- frontend/package-lock.json	(revision 5c65932148f640bedd107abbafc22d3752f398be)
+++ frontend/package-lock.json	(revision 0c86e77a055e354a71be0ff7c053051ca180993b)
@@ -10,6 +10,8 @@
       "dependencies": {
         "@tailwindcss/vite": "^4.1.18",
+        "axios": "^1.13.3",
         "react": "^19.2.0",
         "react-dom": "^19.2.0",
+        "react-router-dom": "^7.13.0",
         "tailwindcss": "^4.1.18"
       },
@@ -2011,4 +2013,21 @@
       "license": "Python-2.0"
     },
+    "node_modules/asynckit": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+      "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+      "license": "MIT"
+    },
+    "node_modules/axios": {
+      "version": "1.13.3",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.3.tgz",
+      "integrity": "sha512-ERT8kdX7DZjtUm7IitEyV7InTHAF42iJuMArIiDIV5YtPanJkgw4hw5Dyg9fh0mihdWNn1GKaeIWErfe56UQ1g==",
+      "license": "MIT",
+      "dependencies": {
+        "follow-redirects": "^1.15.6",
+        "form-data": "^4.0.4",
+        "proxy-from-env": "^1.1.0"
+      }
+    },
     "node_modules/balanced-match": {
       "version": "1.0.2",
@@ -2073,4 +2092,17 @@
       }
     },
+    "node_modules/call-bind-apply-helpers": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+      "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "function-bind": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
     "node_modules/callsites": {
       "version": "3.1.0",
@@ -2141,4 +2173,16 @@
       "license": "MIT"
     },
+    "node_modules/combined-stream": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+      "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+      "license": "MIT",
+      "dependencies": {
+        "delayed-stream": "~1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
     "node_modules/concat-map": {
       "version": "0.0.1",
@@ -2155,4 +2199,17 @@
       "license": "MIT"
     },
+    "node_modules/cookie": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
+      "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/express"
+      }
+    },
     "node_modules/cross-spawn": {
       "version": "7.0.6",
@@ -2201,4 +2258,13 @@
       "dev": true,
       "license": "MIT"
+    },
+    "node_modules/delayed-stream": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+      "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.4.0"
+      }
     },
     "node_modules/detect-libc": {
@@ -2211,4 +2277,18 @@
       }
     },
+    "node_modules/dunder-proto": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+      "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.1",
+        "es-errors": "^1.3.0",
+        "gopd": "^1.2.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
     "node_modules/electron-to-chromium": {
       "version": "1.5.267",
@@ -2229,4 +2309,49 @@
       "engines": {
         "node": ">=10.13.0"
+      }
+    },
+    "node_modules/es-define-property": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+      "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-errors": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+      "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-object-atoms": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+      "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-set-tostringtag": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+      "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "get-intrinsic": "^1.2.6",
+        "has-tostringtag": "^1.0.2",
+        "hasown": "^2.0.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
       }
     },
@@ -2568,4 +2693,40 @@
       "license": "ISC"
     },
+    "node_modules/follow-redirects": {
+      "version": "1.15.11",
+      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
+      "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://github.com/sponsors/RubenVerborgh"
+        }
+      ],
+      "license": "MIT",
+      "engines": {
+        "node": ">=4.0"
+      },
+      "peerDependenciesMeta": {
+        "debug": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/form-data": {
+      "version": "4.0.5",
+      "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
+      "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
+      "license": "MIT",
+      "dependencies": {
+        "asynckit": "^0.4.0",
+        "combined-stream": "^1.0.8",
+        "es-set-tostringtag": "^2.1.0",
+        "hasown": "^2.0.2",
+        "mime-types": "^2.1.12"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
     "node_modules/fsevents": {
       "version": "2.3.3",
@@ -2582,4 +2743,13 @@
       }
     },
+    "node_modules/function-bind": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+      "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
     "node_modules/gensync": {
       "version": "1.0.0-beta.2",
@@ -2592,4 +2762,41 @@
       }
     },
+    "node_modules/get-intrinsic": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+      "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.2",
+        "es-define-property": "^1.0.1",
+        "es-errors": "^1.3.0",
+        "es-object-atoms": "^1.1.1",
+        "function-bind": "^1.1.2",
+        "get-proto": "^1.0.1",
+        "gopd": "^1.2.0",
+        "has-symbols": "^1.1.0",
+        "hasown": "^2.0.2",
+        "math-intrinsics": "^1.1.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/get-proto": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+      "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+      "license": "MIT",
+      "dependencies": {
+        "dunder-proto": "^1.0.1",
+        "es-object-atoms": "^1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
     "node_modules/glob-parent": {
       "version": "6.0.2",
@@ -2616,4 +2823,16 @@
       "funding": {
         "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/gopd": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+      "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
       }
     },
@@ -2632,4 +2851,43 @@
       "engines": {
         "node": ">=8"
+      }
+    },
+    "node_modules/has-symbols": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+      "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/has-tostringtag": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+      "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+      "license": "MIT",
+      "dependencies": {
+        "has-symbols": "^1.0.3"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/hasown": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+      "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+      "license": "MIT",
+      "dependencies": {
+        "function-bind": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
       }
     },
@@ -3107,4 +3365,34 @@
       "dependencies": {
         "@jridgewell/sourcemap-codec": "^1.5.5"
+      }
+    },
+    "node_modules/math-intrinsics": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+      "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/mime-db": {
+      "version": "1.52.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mime-types": {
+      "version": "2.1.35",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+      "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+      "license": "MIT",
+      "dependencies": {
+        "mime-db": "1.52.0"
+      },
+      "engines": {
+        "node": ">= 0.6"
       }
     },
@@ -3300,4 +3588,10 @@
       }
     },
+    "node_modules/proxy-from-env": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+      "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+      "license": "MIT"
+    },
     "node_modules/punycode": {
       "version": "2.3.1",
@@ -3339,4 +3633,42 @@
       "engines": {
         "node": ">=0.10.0"
+      }
+    },
+    "node_modules/react-router": {
+      "version": "7.13.0",
+      "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.0.tgz",
+      "integrity": "sha512-PZgus8ETambRT17BUm/LL8lX3Of+oiLaPuVTRH3l1eLvSPpKO3AvhAEb5N7ihAFZQrYDqkvvWfFh9p0z9VsjLw==",
+      "license": "MIT",
+      "dependencies": {
+        "cookie": "^1.0.1",
+        "set-cookie-parser": "^2.6.0"
+      },
+      "engines": {
+        "node": ">=20.0.0"
+      },
+      "peerDependencies": {
+        "react": ">=18",
+        "react-dom": ">=18"
+      },
+      "peerDependenciesMeta": {
+        "react-dom": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/react-router-dom": {
+      "version": "7.13.0",
+      "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.13.0.tgz",
+      "integrity": "sha512-5CO/l5Yahi2SKC6rGZ+HDEjpjkGaG/ncEP7eWFTvFxbHP8yeeI0PxTDjimtpXYlR3b3i9/WIL4VJttPrESIf2g==",
+      "license": "MIT",
+      "dependencies": {
+        "react-router": "7.13.0"
+      },
+      "engines": {
+        "node": ">=20.0.0"
+      },
+      "peerDependencies": {
+        "react": ">=18",
+        "react-dom": ">=18"
       }
     },
@@ -3410,4 +3742,10 @@
         "semver": "bin/semver.js"
       }
+    },
+    "node_modules/set-cookie-parser": {
+      "version": "2.7.2",
+      "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
+      "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
+      "license": "MIT"
     },
     "node_modules/shebang-command": {
Index: frontend/package.json
===================================================================
--- frontend/package.json	(revision 5c65932148f640bedd107abbafc22d3752f398be)
+++ frontend/package.json	(revision 0c86e77a055e354a71be0ff7c053051ca180993b)
@@ -12,6 +12,8 @@
   "dependencies": {
     "@tailwindcss/vite": "^4.1.18",
+    "axios": "^1.13.3",
     "react": "^19.2.0",
     "react-dom": "^19.2.0",
+    "react-router-dom": "^7.13.0",
     "tailwindcss": "^4.1.18"
   },
Index: frontend/src/App.tsx
===================================================================
--- frontend/src/App.tsx	(revision 5c65932148f640bedd107abbafc22d3752f398be)
+++ frontend/src/App.tsx	(revision 0c86e77a055e354a71be0ff7c053051ca180993b)
@@ -1,19 +1,41 @@
-import { useEffect } from "react";
+import { createBrowserRouter, Outlet, RouterProvider } from "react-router-dom";
+import LandingPage from "./LandingPage";
+import Login from "./Login";
+import Register from "./Register";
+
+const Layout = () => {
+	return (
+		<div className="flex flex-col min-h-screen">
+			{/* <Nav /> */}
+			<main className="grow">
+				<Outlet />
+			</main>
+		</div>
+	);
+};
+
+const router = createBrowserRouter([
+	{
+		path: "",
+		element: <Layout />,
+		children: [
+			{
+				path: "/",
+				element: <LandingPage />,
+			},
+			{
+				path: "/login",
+				element: <Login />,
+			},
+			{
+				path: "/register",
+				element: <Register />,
+			},
+		],
+	},
+]);
 
 function App() {
-	useEffect(() => {
-		const fetchData = async () => {
-			const response = await fetch("http://localhost:8080/");
-			const data = await response.json();
-			console.log(data);
-		};
-		fetchData();
-	});
-
-	return (
-		<>
-			<p className="text-red-500">ok</p>
-		</>
-	);
+	return <RouterProvider router={router} />;
 }
 
Index: frontend/src/LandingPage.tsx
===================================================================
--- frontend/src/LandingPage.tsx	(revision 0c86e77a055e354a71be0ff7c053051ca180993b)
+++ frontend/src/LandingPage.tsx	(revision 0c86e77a055e354a71be0ff7c053051ca180993b)
@@ -0,0 +1,5 @@
+const LandingPage = () => {
+	return <div>landing page</div>;
+};
+
+export default LandingPage;
Index: frontend/src/Login.tsx
===================================================================
--- frontend/src/Login.tsx	(revision 0c86e77a055e354a71be0ff7c053051ca180993b)
+++ frontend/src/Login.tsx	(revision 0c86e77a055e354a71be0ff7c053051ca180993b)
@@ -0,0 +1,5 @@
+const Login = () => {
+	return <div>login</div>;
+};
+
+export default Login;
Index: frontend/src/Register.tsx
===================================================================
--- frontend/src/Register.tsx	(revision 0c86e77a055e354a71be0ff7c053051ca180993b)
+++ frontend/src/Register.tsx	(revision 0c86e77a055e354a71be0ff7c053051ca180993b)
@@ -0,0 +1,123 @@
+import { useState } from "react";
+import { useNavigate } from "react-router";
+import axiosInstance, { scheduleTokenRefresh } from "./api/axiosInstance";
+import { useAuth } from "./context/authContext";
+import type { User, UserResponse } from "./types";
+
+const Register = () => {
+	const { setUser, user } = useAuth();
+	const [username, setUsername] = useState("");
+	const [password, setPassword] = useState("");
+	const [fullname, setFullname] = useState("");
+	const [email, setEmail] = useState("");
+	const [profilePhoto, setProfilePhoto] = useState<string | null>(null);
+
+	const navigate = useNavigate();
+
+	const handleRegister = async () => {
+		const newUser: User = {
+			username,
+			fullName: fullname,
+			email: email,
+			profilePhoto: profilePhoto,
+		};
+		try {
+			const response = await axiosInstance.post<{
+				user: UserResponse;
+				tokenExpiresIn: number;
+			}>("/auth/register", {
+				username,
+				fullname,
+				email,
+				password,
+				profilePhoto,
+			});
+			scheduleTokenRefresh(response.data.tokenExpiresIn);
+			setUser(newUser);
+			// setUser(response.data.user);
+			navigate("/");
+		} catch (error) {
+			console.error("Registration failed:", error);
+		}
+	};
+
+	return (
+		<div className="flex flex-col items-center justify-center min-h-screen 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" htmlFor="profilePhoto">
+						Profile Photo URL
+					</label>
+					<input
+						type="text"
+						id="profilePhoto"
+						className="w-full p-2 border border-gray-300 rounded"
+						value={profilePhoto || ""}
+						onChange={(e) => setProfilePhoto(e.target.value)}
+					/>
+				</div>
+				<button
+					type="submit"
+					className="w-full bg-blue-500 text-white p-2 rounded hover:bg-blue-600"
+					onClick={(e) => {
+						e.preventDefault();
+						handleRegister();
+					}}
+				>
+					Register
+				</button>
+			</form>
+		</div>
+	);
+};
+
+export default Register;
Index: frontend/src/api/axiosInstance.ts
===================================================================
--- frontend/src/api/axiosInstance.ts	(revision 0c86e77a055e354a71be0ff7c053051ca180993b)
+++ frontend/src/api/axiosInstance.ts	(revision 0c86e77a055e354a71be0ff7c053051ca180993b)
@@ -0,0 +1,78 @@
+import axios from "axios";
+
+export const baseURL = import.meta.env.VITE_API_BASE_URL;
+
+export const axiosInstance = axios.create({
+	baseURL,
+	headers: {
+		"Content-Type": "application/json",
+	},
+	withCredentials: true,
+});
+
+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();
+
+	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) {
+			console.error(err);
+			throw err;
+		} finally {
+			refreshPromise = null;
+		}
+	})();
+	return refreshPromise;
+};
+
+axios.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 0c86e77a055e354a71be0ff7c053051ca180993b)
+++ frontend/src/context/authContext.tsx	(revision 0c86e77a055e354a71be0ff7c053051ca180993b)
@@ -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;
+				}>("/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 5c65932148f640bedd107abbafc22d3752f398be)
+++ frontend/src/main.tsx	(revision 0c86e77a055e354a71be0ff7c053051ca180993b)
@@ -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 0c86e77a055e354a71be0ff7c053051ca180993b)
+++ frontend/src/types.ts	(revision 0c86e77a055e354a71be0ff7c053051ca180993b)
@@ -0,0 +1,12 @@
+export interface UserResponse {
+	username: string;
+	role: "ADMIN" | "NONADMIN";
+}
+
+export interface User {
+	username: string;
+	fullName: string;
+	email: string;
+	profilePhoto?: string | null;
+	role?: "ADMIN" | "NONADMIN";
+}
