Medicare Shared Navbar, Role-Based Dashboards & Patient Profile Integration

Business Scenario

In this lab, we will enhance the Medicare React frontend by implementing a reusable navigation bar and a shared page layout using React Router's Outlet.

Pre-Lab Preparation

Module:

1) React Basics & Backend Integration (Spring Boot)​

Phase 12C — Navbar, Shared Layout, Dashboards & Profile

Update api.js

1

  • Open: src/api/api.js
  • Use :
import axios from "axios";
const api = axios.create({
    baseURL: import.meta.env.VITE_API_BASE_URL,
    headers: {
        "Content-Type": "application/json"
    }
});


// Used for APIs that require JWT
export const getAuthConfig = () => {

    const token =
        localStorage.getItem("token");

    if (!token) {
        return {};
    }

    return {
        headers: {
            Authorization: `Bearer ${token}`
        }
    };
};


export default api;
  • This keeps JWT handling visible:
localStorage
     ↓
getAuthConfig()
     ↓
Authorization: Bearer <token>
     ↓
Spring Boot
  • No interceptor hiding the process.

Create userService.js

2

  • Open: src/api/api.js
src/services/userService.js
import api, {getAuthConfig} from "../api/api";


const getCurrentUser = async () => {

    const response = await api.get(
        "/users/me",
        getAuthConfig()
    );

    return response.data;
};


const userService = {
    getCurrentUser
};


export default userService;
  • This connects to: GET /api/users/me

Create Shared Layout

3

  • We don't want to import Navbar manually on every page.
src/components/layout/MainLayout.jsx
  • Create :
import {Outlet} from "react-router";
import Navbar from "../navbar/Navbar";
function MainLayout() {
    return (
        <>
            <Navbar />
            <main> <Outlet /> </main>
        </>
    );
}
export default MainLayout;

What is Outlet?

MainLayout
    │
    ├── Navbar
    │
    └── Outlet
          ↓
       Current Page
  • Our router will have:
  • So if URL is:
 /patient/dashboard

React renders:

Navbar
PatientDashboard
  • If URL changes to : /patient/profile React renders
Navbar
MyProfile

Create Navbar

4

src/components/navbar/Navbar.jsx
  • Create :
import { Link, useNavigate } from "react-router";
import { useAuth } from "../../context/AuthContext";

function Navbar() {
    const navigate = useNavigate();
    const { user, isAuthenticated, logout } = useAuth();

    const handleLogout = () => {
        logout();
        navigate("/login");
    };

    return (
        <nav className="navbar navbar-expand-lg bg-white border-bottom">
            <div className="container">
                <Link className="navbar-brand fw-bold text-primary" to="/">
                Medicare</Link>

                <button className="navbar-toggler" type="button" 
                data-bs-toggle="collapse"  data-bs-target="#mainNavbar">
                    <span className="navbar-toggler-icon"></span>
                </button>

                <div className="collapse navbar-collapse" id="mainNavbar">
                    <ul className="navbar-nav ms-auto align-items-lg-center">
                        {!isAuthenticated && (
                            <>
                                <li className="nav-item">
                                    <Link className="nav-link" to="/login">
                                    Login</Link>
                                </li>
                                <li className="nav-item">
                                    <Link className="btn btn-primary ms-lg-2" 
                                    to="/register">Register</Link>
                                </li>
                            </>
                        )}

                        {isAuthenticated && user?.role === "PATIENT" && (
                            <>
                                <li className="nav-item">
                                    <Link className="nav-link" 
                                    to="/patient/dashboard"> Dashboard</Link>
                                </li>
                                <li className="nav-item">
                                    <Link className="nav-link" 
                                    to="/patient/profile">
                                    My Profile</Link>
                                </li>
                            </>
                        )}

                        {isAuthenticated && user?.role === "DOCTOR" && (
                            <li className="nav-item">
                                <Link className="nav-link" to="/doctor/dashboard">
                                Dashboard</Link>
                            </li>
                        )}

                        {isAuthenticated && (
                            <li className="nav-item ms-lg-3">
                                <button className="btn btn-outline-danger" 
                                onClick={handleLogout}>Logout</button>
                            </li>
                        )}
                    </ul>
                </div>
            </div>
        </nav>
    );
}
export default Navbar;

What useEffect() Is Doing

  • When: MyProfile component loads , this executes once:
useEffect(() => {

    loadProfile();

}, []);
  • Then:
MyProfile
    ↓
userService.getCurrentUser()
    ↓
GET /api/users/me
    ↓
Authorization: Bearer JWT
    ↓
Spring Boot
    ↓
UserResponse
    ↓
setUser()
    ↓
UI updates

Improve Patient Dashboard

5

  • Now remove the temporary logout button from : PatientDashboard.jsx  
    because Navbar handles logout.
  • Replace the PatientDashboard page
import { Link } from "react-router";
import { useAuth } from "../../context/AuthContext";

function PatientDashboard() {
    const { user } = useAuth();

    return (
        <div className="container py-5">
            <div className="mb-5">
                <h2>Welcome, {user?.name}</h2>
                <p className="text-secondary">
                Manage your healthcare from your dashboard.</p>
            </div>
            <div className="row g-4">
                {/* FIND DOCTOR */}
                <div className="col-md-6 col-lg-3">
                    <div className="card h-100 shadow-sm">
                        <div className="card-body">
                            <h5 className="card-title">Find Doctor</h5>
                            <p className="card-text text-secondary">
                            Search doctors by specialization, 
                            name and consultation fee.</p>
                            <button className="btn btn-primary" disabled>
                            Coming Next</button>
                        </div>
                    </div>
                </div>
                
                

                {/* APPOINTMENTS */}
                <div className="col-md-6 col-lg-3">
                    <div className="card h-100 shadow-sm">
                        <div className="card-body">
                            <h5 className="card-title">My Appointments</h5>
                            <p className="card-text text-secondary">
                            View upcoming and completed appointments.</p>
                            <button className="btn btn-primary" disabled>
                            Coming Soon</button>
                        </div>
                    </div>
                </div>
                {/* PRESCRIPTIONS */}
                <div className="col-md-6 col-lg-3">
                    <div className="card h-100 shadow-sm">
                        <div className="card-body">
                            <h5 className="card-title">Prescriptions</h5>
                            <p className="card-text text-secondary">
                            View prescriptions from completed appointments.</p>
                            <button className="btn btn-primary" disabled>
                            Coming Soon</button>
                        </div>
                    </div>
                </div>
                

                {/* PROFILE */}
                <div className="col-md-6 col-lg-3">
                    <div className="card h-100 shadow-sm">
                        <div className="card-body">
                            <h5 className="card-title">My Profile</h5>
                            <p className="card-text text-secondary">
                            View your Medicare account information.</p>
                            <Link to="/patient/profile" 
                            className="btn btn-outline-primary">
                            View Profile</Link>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    );
}

export default PatientDashboard;
.cors(cors -> cors.configurationSource(
                corsConfigurationSource()
        )
)
  • Then inside your existing:
SecurityFilterChain securityFilterChain(...)
  • Important :Don't replace your existing:
JWT filter
Authorization rules
SessionCreationPolicy.STATELESS
CustomAuthenticationEntryPoint
CustomAccessDeniedHandler

Check .env

2

  • Your React frontend should already contain:
medicare-frontend/.env
  • with:
VITE_API_BASE_URL=http://localhost:8080/api
  • Vite exposes client environment variables prefixed with VITE_ through import.meta.env. ​

Create authService.js

3

src/services/authService.js
import api from "../api/api";

const register = async (registerData) => {
    const response = await api.post("/auth/register",registerData);
    return response.data;
};

const login = async (loginData) => {
    const response = await api.post("/auth/login",loginData);
    return response.data;
};

const authService = {register,login};

export default authService;

Understand Our Login Response

4

  • Our backend returns something conceptually like:
{
    "token": "eyJhbGciOiJIUzI1NiJ9...",
    "tokenType": "Bearer",
    "userId": 1,
    "name": "Rahul Shah",
    "email": "rahul@gmail.com",
    "role": "PATIENT"
}
{
    "token": "eyJhbGciOiJIUzI1NiJ9...",
    "tokenType": "Bearer",
    "userId": 2,
    "name": "Dr. Ananya",
    "email": "ananya@gmail.com",
    "role": "DOCTOR"
}
  • We're going to store: token, user separately.

Create AuthContext

5

  • Create:
src/context/AuthContext.jsx
import {createContext,useContext,useState} from "react";

const AuthContext = createContext(null);

function getStoredUser() {
    const storedUser = localStorage.getItem("user");

    if (!storedUser) {
        return null;
    }

    try {
        return JSON.parse(storedUser);
    } catch {
        localStorage.removeItem("user");
        return null;
    }
}


export function AuthProvider({ children }) {

    const [token, setToken] = useState(() =>
            localStorage.getItem("token")
        );

    const [user, setUser] = useState(getStoredUser);


    const login = (authResponse) => {
        const loggedInUser = {
            userId: authResponse.userId,
            name: authResponse.name,
            email: authResponse.email,
            role: authResponse.role
        };

        localStorage.setItem("token",authResponse.token);
        localStorage.setItem("user",JSON.stringify(loggedInUser));
        setToken(authResponse.token);
        setUser(loggedInUser);
    };
    const logout = () => {
        localStorage.removeItem("token");
        localStorage.removeItem("user"
        );
        setToken(null);
        setUser(null);
    };


    const isAuthenticated = Boolean(token && user);

    return (
        <AuthContext.Provider
            value={{user,token,isAuthenticated,login,logout
            }}>
            {children}
        </AuthContext.Provider>
    );
}

export function useAuth() {
    return useContext( AuthContext);
}

Why AuthContext?

  • Without Context:
Login
 ↓Patient Dashboard
 ↓Doctor Search
 ↓Appointments
 ↓Profile
  • Every page would need its own authentication state.
             AuthContext
                 │
      ┌──────────┼──────────┐
      ↓          ↓          ↓
   Navbar    Dashboard    Routes
  • Everyone can access:
const {
    user,
    token,
    login,
    logout,
    isAuthenticated
} = useAuth();

Why localStorage AND Context?

  • Context gives us: React application state
  • But refreshing the browser destroys normal React state.
  • Therefore:
localStorage
      ↓
persists JWT + user

AuthContext
      ↓
provides that information through React

Wrap Application with AuthProvider

6

  • Open : src/main.jsx
  • Replace it with:
import React from "react";
import ReactDOM from "react-dom/client";
import {RouterProvider} from "react-router/dom";
import "bootstrap/dist/css/bootstrap.min.css";
import "bootstrap/dist/js/bootstrap.bundle.min.js";
import "./index.css";
import router from "./routes/router";
import {AuthProvider} from "./context/AuthContext";


ReactDOM.createRoot(document.getElementById("root")).render(
    <React.StrictMode>
        <AuthProvider>
            <RouterProvider
                router={router}/>
        </AuthProvider>
    </React.StrictMode>
);

Build Real Registration Page

7

  • Open: src/pages/auth/Register.jsx
  • Replace it with:
import { useState } from "react";
import { Link, useNavigate } from "react-router";
import authService from "../../services/authService";

function Register() {
    const navigate = useNavigate();

    const [formData, setFormData] = useState({
        name: "",
        email: "",
        phone: "",
        password: ""
    });

    const [error, setError] = useState("");
    const [loading, setLoading] = useState(false);

    const handleChange = (event) => {
        const { name, value } = event.target;
        setFormData((previous) => ({
            ...previous,
            [name]: value
        }));
    };

    const handleSubmit = async (event) => {
        event.preventDefault();
        setError("");
        setLoading(true);

        try {
            await authService.register(formData);
            navigate("/login", {
                state: {
                    message: "Registration successful. Please login."
                }
            });
        } catch (error) {
            setError(
                error.response?.data?.message || "Registration failed"
            );
        } finally {
            setLoading(false);
        }
    };

    return (
        <div className="container py-5">
            <div className="row justify-content-center">
                <div className="col-md-7 col-lg-6">
                    <div className="card shadow-sm">
                        <div className="card-body p-4">
                            <h2 className="text-center mb-4">Create Account</h2>

                            {error && (
                                <div className="alert alert-danger">
                                    {error}
                                </div>
                            )}

                            <form onSubmit={handleSubmit}>
                                <div className="mb-3">
                                    <label className="form-label">Name</label>
                                    <input
                                        type="text"
                                        name="name"
                                        className="form-control"
                                        value={formData.name}
                                        onChange={handleChange}
                                        required
                                    />
                                </div>

                                <div className="mb-3">
                                    <label className="form-label">Email</label>
                                    <input
                                        type="email"
                                        name="email"
                                        className="form-control"
                                        value={formData.email}
                                        onChange={handleChange}
                                        required
                                    />
                                </div>

                                <div className="mb-3">
                                    <label className="form-label">Phone Number</label>
                                    <input
                                        type="tel"
                                        name="phone"
                                        className="form-control"
                                        value={formData.phone}
                                        onChange={handleChange}
                                        required
                                    />
                                </div>

                                <div className="mb-3">
                                    <label className="form-label">Password</label>
                                    <input
                                        type="password"
                                        name="password"
                                        className="form-control"
                                        value={formData.password}
                                        onChange={handleChange}
                                        required
                                    />
                                </div>

                                <button
                                    type="submit"
                                    className="btn btn-primary w-100"
                                    disabled={loading}
                                >
                                    {loading ? "Creating Account..." : "Register"}
                                </button>
                            </form>

                            <p className="text-center mt-4 mb-0">
                                Already have an account?{" "}
                                <Link to="/login">Login</Link>
                            </p>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    );
}
export default Register;

Very Important — No Role Field

  • We deliberately did NOT create:
Role:
[ PATIENT ]
[ DOCTOR ]
  • Because public users should never be able to send:
{ "role": "DOCTOR" }
  • Registration sends only:
{
    "name": "Rahul Shah",
    "email": "rahul@gmail.com",
    "phone": "9876543210",
    "password": "password"
}
  • Backend automatically sets: PATIENT

Build Real Login Page

8

  • Open: src/pages/auth/Login.jsx
  • Replace it:


import { useState } from "react";
import { Link, useLocation, useNavigate } from "react-router";
import authService from "../../services/authService";
import { useAuth } from "../../context/AuthContext";

function Login() {
    const navigate = useNavigate();
    const location = useLocation();
    const { login } = useAuth();

    const [formData, setFormData] = useState({
        email: "",
        password: ""
    });

    const [error, setError] = useState("");
    const [loading, setLoading] = useState(false);

    const handleChange = (event) => {
        const { name, value } = event.target;
        setFormData((previous) => ({
            ...previous,
            [name]: value
        }));
    };

    const handleSubmit = async (event) => {
        event.preventDefault();
        setError("");
        setLoading(true);
        try {
            const response = await authService.login(formData);
            login(response);

            if (response.role === "PATIENT") {
                navigate("/patient/dashboard");
            } else if (response.role === "DOCTOR") {
                navigate("/doctor/dashboard");
            } else {
                navigate("/unauthorized");
            }
        } catch (error) {
            setError(
                error.response?.data?.message || "Invalid email or password"
            );
        } finally {
            setLoading(false);
        }
    };

    return (
        <div className="container py-5">
            <div className="row justify-content-center">
                <div className="col-md-6 col-lg-5">
                    <div className="card shadow-sm">
                        <div className="card-body p-4">
                            <h2 className="text-center mb-4">Login</h2>

                            {location.state?.message && (
                                <div className="alert alert-success">
                                    {location.state.message}
                                </div>
                            )}

                            {error && (
                                <div className="alert alert-danger">
                                    {error}
                                </div>
                            )}

                            <form onSubmit={handleSubmit}>
                                <div className="mb-3">
                                    <label className="form-label">Email</label>
                                    <input
                                        type="email"
                                        name="email"
                                        className="form-control"
                                        value={formData.email}
                                        onChange={handleChange}
                                        required
                                    />
                                </div>

                                <div className="mb-3">
                                    <label className="form-label">Password</label>
                                    <input
                                        type="password"
                                        name="password"
                                        className="form-control"
                                        value={formData.password}
                                        onChange={handleChange}
                                        required
                                    />
                                </div>

                                <button
                                    type="submit"
                                    className="btn btn-primary w-100"
                                    disabled={loading}
                                >
                                    {loading ? "Logging in..." : "Login"}
                                </button>
                            </form>

                            <p className="text-center mt-4 mb-0">
                                Don't have an account?{" "}
                                <Link to="/register">Register</Link>
                            </p>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    );
}

export default Login;

Create ProtectedRoute

9

  • Create : src/routes/ProtectedRoute.jsx
import { Navigate } from "react-router";
import { useAuth } from "../context/AuthContext";

function ProtectedRoute({ children, allowedRoles = [] }) {
    const { user, isAuthenticated } = useAuth();

    if (!isAuthenticated) {
        return <Navigate to="/login" replace />;
    }

    if (allowedRoles.length > 0 && !allowedRoles.includes(user?.role)) {
        return <Navigate to="/unauthorized" replace />;
    }

    return children;
}

export default ProtectedRoute;

Protect Routes

10

  • Open:src/routes/router.jsx
import { createBrowserRouter } from "react-router";
import Home from "../pages/common/Home";
import Login from "../pages/auth/Login";
import Register from "../pages/auth/Register";
import PatientDashboard from "../pages/patient/PatientDashboard";
import DoctorDashboard from "../pages/doctor/DoctorDashboard";
import Unauthorized from "../pages/common/Unauthorized";
import NotFound from "../pages/common/NotFound";
import ProtectedRoute from "./ProtectedRoute";

const router = createBrowserRouter([
    {
        path: "/",
        element: <Home />
    },
  • Replace it with: 
  • Open:src/routes/router.jsx
   {
        path: "/login",
        element: <Login />
    },
    {
        path: "/register",
        element: <Register />
    },
    {
        path: "/patient/dashboard",
        element: (
            <ProtectedRoute allowedRoles={["PATIENT"]}>
                <PatientDashboard />
            </ProtectedRoute>
        )
    },
    {
        path: "/doctor/dashboard",
        element: (
            <ProtectedRoute allowedRoles={["DOCTOR"]}>
                <DoctorDashboard />
            </ProtectedRoute>
        )
    },
    {
        path: "/unauthorized",
        element: <Unauthorized />
    },
    {
        path: "*",
        element: <NotFound />
    }
]);

export default router;

What ProtectedRoute Is Doing

  • Suppose
Not logged in
  • And user manually types:
/patient/dashboard
  • Result: /login
  • Suppose logged-in patient enters:
/doctor/dashboard
  • Result: /unauthorized

Add Logout to Patient Dashboard

11

  • For now we'll add a basic logout button so authentication can be properly tested.
  • Open: PatientDashboard.jsx
  • Replace with:
import { useNavigate } from "react-router";
import { useAuth } from "../../context/AuthContext";

function PatientDashboard() {
    const navigate = useNavigate();
    const { user, logout } = useAuth();

    const handleLogout = () => {
        logout();
        navigate("/login");
    };

    return (
        <div className="container py-5">
            <div className="d-flex justify-content-between align-items-center mb-4">
                <div>
                    <h2>Patient Dashboard</h2>
                    <p className="text-secondary mb-0">
                        Welcome, {user?.name}
                    </p>
                </div>

                <button
                    className="btn btn-outline-danger"
                    onClick={handleLogout}
                >
                    Logout
                </button>
            </div>

            <div className="alert alert-info">
                Patient healthcare modules will be added next.
            </div>
        </div>
    );
}

export default PatientDashboard;

Add Logout to Doctor Dashboard

12

  • Open: DoctorDashboard.jsx
  • Replace it with:
import { useNavigate } from "react-router";
import { useAuth } from "../../context/AuthContext";

function DoctorDashboard() {
    const navigate = useNavigate();
    const { user, logout } = useAuth();

    const handleLogout = () => {
        logout();
        navigate("/login");
    };

    return (
        <div className="container py-5">
            <div className="d-flex justify-content-between align-items-center mb-4">
                <div>
                    <h2>Doctor Dashboard</h2>
                    <p className="text-secondary mb-0">
                        Welcome, {user?.name}
                    </p>
                </div>

                <button className="btn btn-outline-danger"
                    onClick={handleLogout}>
                    Logout
                </button>
            </div>

            <div className="alert alert-info">
                Doctor appointment modules will be added next.
            </div>
        </div>
    );
}

export default DoctorDashboard;
  • Later the logout button moves into our global Navbar.
  • For now this lets us verify authentication.

Restart Both Applications

13

Test Registration

14

  • Go to: http://localhost:5173/register
  • Enter:
Name: Rahul Shah
Email: rahul@gmail.com
Phone: 9876543210
Password: your password
  • Click: Register
  • Frontend sends: POST /api/auth/register
  • Body:
{
    "name": "Rahul Shah",
    "email": "rahul@gmail.com",
    "phone": "9876543210",
    "password": "your password"
}
  • Backend should create: User role = PATIENT
  • Then React redirects: /login with:
    ​Registration successful. Please login. 

Test Patient Login

15

  • Login using the patient account.
  • Frontend sends:
{
    "email": "rahul@gmail.com",
    "password": "your password"
}
  • Backend returns JWT.
  • Then :
  • React stores:
localStorage
token
user
PATIENT --> /patient/dashboard

Inspect Browser Storage

16

  • Open browser Developer Tools:
Application
   ↓
Local Storage
   ↓
http://localhost:5173
  • You should see approximately:
token : eyJhbGciOiJIUzI1NiJ9...

user : {"userId":1,"name":"Rahul Shah","email":"rahul@gmail.com","role":"PATIENT"}

Test Browser Refresh

17

  • While on: /patient/dashboard   press: F5
  • The user should remain logged in.
localStorage
      ↓
AuthContext initialization
      ↓
token restored
user restored
  • Why?

Test Patient Security

18

403
Access Denied
  • While logged in as PATIENT manually type: http://localhost:5173/doctor/dashboard
  • Expected:

Test Doctor Login

19

  • Because public registration only creates patients, use the doctor account you already created for backend testing.
Doctor Email
Doctor Password
  • Expected:
{
    "role": "DOCTOR"
}
  • Backend returns:
  • React redirects: /doctor/dashboard

Test Doctor Security

20

  • Doctor manually enters: /patient/dashboard
  • Expected /unauthorized

Test Logout

21

  • Click: Logout
  • React performs:
localStorage.removeItem("token");
localStorage.removeItem("user");
  • Context becomes:
user = null
token = null
  • Then: /login
  • Try manually entering: /patient/dashboard
  • Expected: /login
                 REGISTER
                    │
                    ▼
             Register.jsx
                    │
                    ▼
            authService.js
                    │
                    ▼
       POST /api/auth/register
                    │
                    ▼
               Spring Boot
                    │
             role = PATIENT

Complete Authentication Architecture

                  LOGIN
                    │
                    ▼
               Login.jsx
                    │
                    ▼
             authService.js
                    │
                    ▼
         POST /api/auth/login
                    │
                    ▼
             Spring Security
                    │
                    ▼
                   JWT
                    │
                    ▼
               AuthContext
                    │
          ┌─────────┴─────────┐
          ▼                   ▼
       Context           localStorage
          │
          ▼
    ProtectedRoute
          │
     ┌────┴─────┐
     ▼          ▼
  PATIENT     DOCTOR
     │          │
     ▼          ▼
 Patient      Doctor
Dashboard    Dashboard

We are done with this lab. The latest source code has been uploaded to GitHub. You can access the latest commit using the link below: 

 

Great job!

You have successfully implemented the Medicare frontend authentication workflow using React, JWT, and protected routes.

Checkpoint

   Git Push

git push origin branchName

Next-Lab Preparation

Module:

1) React Basics & Backend Integration (Spring Boot)​