Content ITV PRO
This is Itvedant Content department
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
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;localStorage ↓ getAuthConfig() ↓ Authorization: Bearer <token> ↓ Spring Boot
Create userService.js
2
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;Create Shared Layout
3
src/components/layout/MainLayout.jsx
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
/patient/dashboardReact renders:
Navbar PatientDashboard
Navbar MyProfile
Create Navbar
4
src/components/navbar/Navbar.jsx
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
useEffect(() => {
loadProfile();
}, []);MyProfile ↓ userService.getCurrentUser() ↓ GET /api/users/me ↓ Authorization: Bearer JWT ↓ Spring Boot ↓ UserResponse ↓ setUser() ↓ UI updates
Improve Patient Dashboard
5
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()
)
)SecurityFilterChain securityFilterChain(...)
JWT filter Authorization rules SessionCreationPolicy.STATELESS CustomAuthenticationEntryPoint CustomAccessDeniedHandler
Check .env
2
medicare-frontend/.env
VITE_API_BASE_URL=http://localhost:8080/api
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
{
"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"
}Create AuthContext
5
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?
Login ↓Patient Dashboard ↓Doctor Search ↓Appointments ↓Profile
AuthContext │ ┌──────────┼──────────┐ ↓ ↓ ↓ Navbar Dashboard Routes
const {
user,
token,
login,
logout,
isAuthenticated
} = useAuth();Why localStorage AND Context?
localStorage ↓ persists JWT + user AuthContext ↓ provides that information through React
Wrap Application with AuthProvider
6
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
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
Role: [ PATIENT ] [ DOCTOR ]
{ "role": "DOCTOR" }{
"name": "Rahul Shah",
"email": "rahul@gmail.com",
"phone": "9876543210",
"password": "password"
}Build Real Login Page
8
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
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
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 />
}, {
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
Not logged in
/patient/dashboard
/doctor/dashboard
Add Logout to Patient Dashboard
11
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
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;Restart Both Applications
13
Test Registration
14
Name: Rahul Shah Email: rahul@gmail.com Phone: 9876543210 Password: your password
{
"name": "Rahul Shah",
"email": "rahul@gmail.com",
"phone": "9876543210",
"password": "your password"
}Test Patient Login
15
{
"email": "rahul@gmail.com",
"password": "your password"
}localStorage token user
PATIENT --> /patient/dashboard
Inspect Browser Storage
16
Application ↓ Local Storage ↓ http://localhost:5173
token : eyJhbGciOiJIUzI1NiJ9...
user : {"userId":1,"name":"Rahul Shah","email":"rahul@gmail.com","role":"PATIENT"}Test Browser Refresh
17
localStorage ↓ AuthContext initialization ↓ token restored user restored
Test Patient Security
18
403 Access Denied
Test Doctor Login
19
Doctor Email Doctor Password
{ "role": "DOCTOR" }
Test Doctor Security
20
Test Logout
21
localStorage.removeItem("token");
localStorage.removeItem("user");user = null token = null
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 branchNameNext-Lab Preparation
Module:
1) React Basics & Backend Integration (Spring Boot)
By Content ITV