Index: frontend/src/App.jsx
===================================================================
--- frontend/src/App.jsx	(revision 793ce2c999fddd728a9a4f772ab13cc20b8c9914)
+++ frontend/src/App.jsx	(revision 3ebe47cce61b3bfc1dee2566f7475e378374591e)
@@ -1,6 +1,34 @@
+import { Navigate, Route, Routes } from "react-router-dom";
+
 import LandingPage from "./pages/LandingPage/LandingPage.jsx";
+import Login from "./pages/Login/Login.jsx";
+import Register from "./pages/Register/Register.jsx";
+import Dashboard from "./pages/Dashboard.jsx";
+
+function RequireAuth({ children }) {
+  const token = localStorage.getItem("authToken");
+  if (!token) return <Navigate to="/login" replace />;
+  return children;
+}
 
 function App() {
-  return <LandingPage />;
+  return (
+    <div data-theme="forest" className="min-h-screen w-full">
+      <Routes>
+        <Route path="/" element={<LandingPage />} />
+        <Route path="/login" element={<Login />} />
+        <Route path="/register" element={<Register />} />
+        <Route
+          path="/dashboard"
+          element={
+            <RequireAuth>
+              <Dashboard />
+            </RequireAuth>
+          }
+        />
+        <Route path="*" element={<Navigate to="/" replace />} />
+      </Routes>
+    </div>
+  );
 }
 
Index: frontend/src/api/axios.js
===================================================================
--- frontend/src/api/axios.js	(revision 793ce2c999fddd728a9a4f772ab13cc20b8c9914)
+++ frontend/src/api/axios.js	(revision 3ebe47cce61b3bfc1dee2566f7475e378374591e)
@@ -1,6 +1,16 @@
 import axios from "axios";
 
-export default axios.create({
-  baseURL: "http://localhost:8080/api",
-  withCredentials: true, // important if using cookies
+const api = axios.create({
+  baseURL: import.meta.env.VITE_API_BASE_URL ?? "http://localhost:8080/api",
 });
+
+api.interceptors.request.use((config) => {
+  const token = localStorage.getItem("authToken");
+  if (token) {
+    config.headers = config.headers ?? {};
+    config.headers.Authorization = `Bearer ${token}`;
+  }
+  return config;
+});
+
+export default api;
Index: frontend/src/main.jsx
===================================================================
--- frontend/src/main.jsx	(revision 793ce2c999fddd728a9a4f772ab13cc20b8c9914)
+++ frontend/src/main.jsx	(revision 3ebe47cce61b3bfc1dee2566f7475e378374591e)
@@ -1,10 +1,14 @@
-import { StrictMode } from 'react'
-import { createRoot } from 'react-dom/client'
-import './index.css'
-import App from './App.jsx'
+import { StrictMode } from "react";
+import { createRoot } from "react-dom/client";
+import { BrowserRouter } from "react-router-dom";
 
-createRoot(document.getElementById('root')).render(
+import "./index.css";
+import App from "./App.jsx";
+
+createRoot(document.getElementById("root")).render(
   <StrictMode>
-    <App />
+    <BrowserRouter>
+      <App />
+    </BrowserRouter>
   </StrictMode>,
-)
+);
Index: frontend/src/pages/Dashboard.jsx
===================================================================
--- frontend/src/pages/Dashboard.jsx	(revision 793ce2c999fddd728a9a4f772ab13cc20b8c9914)
+++ frontend/src/pages/Dashboard.jsx	(revision 3ebe47cce61b3bfc1dee2566f7475e378374591e)
@@ -1,2 +1,61 @@
-// hello world
-import React from "react";
+import React, { useMemo } from "react";
+import { Link, useNavigate } from "react-router-dom";
+
+const Dashboard = () => {
+  const navigate = useNavigate();
+
+  const user = useMemo(() => {
+    try {
+      const raw = localStorage.getItem("authUser");
+      return raw ? JSON.parse(raw) : null;
+    } catch {
+      return null;
+    }
+  }, []);
+
+  const onLogout = () => {
+    localStorage.removeItem("authToken");
+    localStorage.removeItem("authUser");
+    navigate("/", { replace: true });
+  };
+
+  return (
+    <div className="min-h-screen p-6">
+      <div className="max-w-3xl mx-auto">
+        <div className="flex items-center justify-between gap-3">
+          <h1 className="text-2xl font-bold">Dashboard</h1>
+          <div className="flex items-center gap-2">
+            <Link className="btn btn-ghost" to="/">
+              Landing
+            </Link>
+            <button className="btn btn-outline" onClick={onLogout}>
+              Logout
+            </button>
+          </div>
+        </div>
+
+        <div className="mt-6 card bg-base-200 border border-base-300">
+          <div className="card-body">
+            <h2 className="card-title">Account</h2>
+            <div className="text-sm leading-7">
+              <div>
+                <span className="opacity-70">Username:</span>{" "}
+                <span className="font-medium">{user?.username ?? "—"}</span>
+              </div>
+              <div>
+                <span className="opacity-70">Email:</span>{" "}
+                <span className="font-medium">{user?.email ?? "—"}</span>
+              </div>
+              <div>
+                <span className="opacity-70">User ID:</span>{" "}
+                <span className="font-medium">{user?.userId ?? "—"}</span>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  );
+};
+
+export default Dashboard;
Index: frontend/src/pages/LandingPage/components/Hero.jsx
===================================================================
--- frontend/src/pages/LandingPage/components/Hero.jsx	(revision 793ce2c999fddd728a9a4f772ab13cc20b8c9914)
+++ frontend/src/pages/LandingPage/components/Hero.jsx	(revision 3ebe47cce61b3bfc1dee2566f7475e378374591e)
@@ -1,5 +1,20 @@
-import React from "react";
+import React, { useEffect, useState } from "react";
+import { Link } from "react-router-dom";
 
 const Hero = () => {
+  const [isAuthed, setIsAuthed] = useState(() =>
+    Boolean(localStorage.getItem("authToken")),
+  );
+
+  useEffect(() => {
+    const onStorage = (e) => {
+      if (e.key === "authToken") {
+        setIsAuthed(Boolean(e.newValue));
+      }
+    };
+    window.addEventListener("storage", onStorage);
+    return () => window.removeEventListener("storage", onStorage);
+  }, []);
+
   return (
     <div
@@ -21,5 +36,10 @@
             consistently, and see your progress in one place.
           </p>
-          <button className="btn btn-primary">Get started</button>
+          <Link
+            className="btn btn-primary"
+            to={isAuthed ? "/dashboard" : "/register"}
+          >
+            {isAuthed ? "Go to dashboard" : "Get started"}
+          </Link>
         </div>
       </div>
Index: frontend/src/pages/LandingPage/components/NavbarLanding.jsx
===================================================================
--- frontend/src/pages/LandingPage/components/NavbarLanding.jsx	(revision 793ce2c999fddd728a9a4f772ab13cc20b8c9914)
+++ frontend/src/pages/LandingPage/components/NavbarLanding.jsx	(revision 3ebe47cce61b3bfc1dee2566f7475e378374591e)
@@ -1,17 +1,54 @@
-import React from "react";
+import React, { useEffect, useState } from "react";
+import { Link, useNavigate } from "react-router-dom";
 import logo from "../../../assets/logo.png";
 
 const NavbarLanding = () => {
+  const navigate = useNavigate();
+  const [isAuthed, setIsAuthed] = useState(() =>
+    Boolean(localStorage.getItem("authToken")),
+  );
+
+  useEffect(() => {
+    const onStorage = (e) => {
+      if (e.key === "authToken") {
+        setIsAuthed(Boolean(e.newValue));
+      }
+    };
+    window.addEventListener("storage", onStorage);
+    return () => window.removeEventListener("storage", onStorage);
+  }, []);
+
+  const onLogout = () => {
+    localStorage.removeItem("authToken");
+    localStorage.removeItem("authUser");
+    setIsAuthed(false);
+    navigate("/", { replace: true });
+  };
+
   return (
     <div className="navbar bg-neutral shadow-sm">
       <div className="navbar-start">
-        <a className="btn btn-ghost" aria-label="Trekr home">
+        <Link className="btn btn-ghost" aria-label="Trekr home" to="/">
           <img src={logo} alt="Trekr" className="h-12 w-auto rounded-2xl" />
-        </a>
+        </Link>
       </div>
       <div className="navbar-end">
-        <button className="btn bg-black text-green-200 border-black hover:bg-black/90 hover:border-black px-8 mr-6">
-          Start Tracking
-        </button>
+        {isAuthed ? (
+          <div className="flex items-center gap-2 mr-6">
+            <Link className="btn btn-primary" to="/dashboard">
+              Dashboard
+            </Link>
+            <button className="btn btn-outline" onClick={onLogout}>
+              Logout
+            </button>
+          </div>
+        ) : (
+          <Link
+            className="btn bg-black text-green-200 border-black hover:bg-black/90 hover:border-black px-8 mr-6"
+            to="/login"
+          >
+            Start Tracking
+          </Link>
+        )}
       </div>
     </div>
Index: frontend/src/pages/Login/Login.jsx
===================================================================
--- frontend/src/pages/Login/Login.jsx	(revision 3ebe47cce61b3bfc1dee2566f7475e378374591e)
+++ frontend/src/pages/Login/Login.jsx	(revision 3ebe47cce61b3bfc1dee2566f7475e378374591e)
@@ -0,0 +1,99 @@
+import React, { useEffect, useState } from "react";
+import { Link, useNavigate } from "react-router-dom";
+
+import api from "../../api/axios";
+
+const Login = () => {
+  const navigate = useNavigate();
+  const [usernameOrEmail, setUsernameOrEmail] = useState("");
+  const [password, setPassword] = useState("");
+  const [error, setError] = useState("");
+  const [isSubmitting, setIsSubmitting] = useState(false);
+
+  useEffect(() => {
+    const token = localStorage.getItem("authToken");
+    if (token) navigate("/dashboard", { replace: true });
+  }, [navigate]);
+
+  const onSubmit = async (e) => {
+    e.preventDefault();
+    setError("");
+    setIsSubmitting(true);
+    try {
+      const res = await api.post("/auth/login", {
+        usernameOrEmail,
+        password,
+      });
+      localStorage.setItem("authToken", res.data.token);
+      localStorage.setItem(
+        "authUser",
+        JSON.stringify({
+          userId: res.data.userId,
+          username: res.data.username,
+          email: res.data.email,
+        }),
+      );
+      navigate("/dashboard");
+    } catch (err) {
+      const message =
+        err?.response?.data?.message ||
+        err?.response?.data?.errors?.usernameOrEmail ||
+        "Login failed";
+      setError(message);
+    } finally {
+      setIsSubmitting(false);
+    }
+  };
+
+  return (
+    <div className="min-h-screen flex items-center justify-center p-4">
+      <form
+        onSubmit={onSubmit}
+        className="fieldset bg-base-200 border-base-300 rounded-box w-xs border p-4"
+      >
+        <legend className="fieldset-legend">Login</legend>
+
+        <label className="label">Username or Email</label>
+        <input
+          type="text"
+          className="input"
+          placeholder="Username or Email"
+          value={usernameOrEmail}
+          onChange={(e) => setUsernameOrEmail(e.target.value)}
+          autoComplete="username"
+          required
+        />
+
+        <label className="label">Password</label>
+        <input
+          type="password"
+          className="input"
+          placeholder="Password"
+          value={password}
+          onChange={(e) => setPassword(e.target.value)}
+          autoComplete="current-password"
+          required
+        />
+
+        {error ? <p className="text-error mt-2 text-sm">{error}</p> : null}
+
+        <button
+          className="btn btn-neutral mt-4"
+          type="submit"
+          disabled={isSubmitting}
+        >
+          {isSubmitting ? "Logging in..." : "Login"}
+        </button>
+
+        <p className="mt-3 text-sm">
+          Don&apos;t have an account?{" "}
+          <Link className="link link-primary" to="/register">
+            Register
+          </Link>
+        </p>
+      </form>
+    </div>
+  );
+};
+
+export default Login;
Index: frontend/src/pages/Register/Register.jsx
===================================================================
--- frontend/src/pages/Register/Register.jsx	(revision 3ebe47cce61b3bfc1dee2566f7475e378374591e)
+++ frontend/src/pages/Register/Register.jsx	(revision 3ebe47cce61b3bfc1dee2566f7475e378374591e)
@@ -0,0 +1,112 @@
+import React, { useEffect, useState } from "react";
+import { Link, useNavigate } from "react-router-dom";
+
+import api from "../../api/axios";
+
+const Register = () => {
+  const navigate = useNavigate();
+  const [email, setEmail] = useState("");
+  const [username, setUsername] = useState("");
+  const [password, setPassword] = useState("");
+  const [error, setError] = useState("");
+  const [isSubmitting, setIsSubmitting] = useState(false);
+
+  useEffect(() => {
+    const token = localStorage.getItem("authToken");
+    if (token) navigate("/dashboard", { replace: true });
+  }, [navigate]);
+
+  const onSubmit = async (e) => {
+    e.preventDefault();
+    setError("");
+    setIsSubmitting(true);
+    try {
+      const res = await api.post("/auth/register", {
+        email,
+        username,
+        password,
+      });
+      localStorage.setItem("authToken", res.data.token);
+      localStorage.setItem(
+        "authUser",
+        JSON.stringify({
+          userId: res.data.userId,
+          username: res.data.username,
+          email: res.data.email,
+        }),
+      );
+      navigate("/dashboard");
+    } catch (err) {
+      const message =
+        err?.response?.data?.message ||
+        Object.values(err?.response?.data?.errors ?? {})[0] ||
+        "Registration failed";
+      setError(message);
+    } finally {
+      setIsSubmitting(false);
+    }
+  };
+
+  return (
+    <div className="min-h-screen flex items-center justify-center p-4">
+      <form
+        onSubmit={onSubmit}
+        className="fieldset bg-base-200 border-base-300 rounded-box w-xs border p-4"
+      >
+        <legend className="fieldset-legend">Register</legend>
+
+        <label className="label">Email</label>
+        <input
+          type="email"
+          className="input"
+          placeholder="Email"
+          value={email}
+          onChange={(e) => setEmail(e.target.value)}
+          autoComplete="email"
+          required
+        />
+
+        <label className="label">Username</label>
+        <input
+          type="text"
+          className="input"
+          placeholder="Username"
+          value={username}
+          onChange={(e) => setUsername(e.target.value)}
+          autoComplete="username"
+          required
+        />
+
+        <label className="label">Password</label>
+        <input
+          type="password"
+          className="input"
+          placeholder="Password"
+          value={password}
+          onChange={(e) => setPassword(e.target.value)}
+          autoComplete="new-password"
+          required
+        />
+
+        {error ? <p className="text-error mt-2 text-sm">{error}</p> : null}
+
+        <button
+          className="btn btn-neutral mt-4"
+          type="submit"
+          disabled={isSubmitting}
+        >
+          {isSubmitting ? "Creating account..." : "Register"}
+        </button>
+
+        <p className="mt-3 text-sm">
+          Already have an account?{" "}
+          <Link className="link link-primary" to="/login">
+            Login
+          </Link>
+        </p>
+      </form>
+    </div>
+  );
+};
+
+export default Register;
