Business Scenario
In this lab, we will enhance the Medicare application by implementing the appointment booking workflow for patients.
Pre-Lab Preparation
Module:
1) React Basics & Backend Integration (Spring Boot)
Phase 12E — Appointment Booking
Find Doctor ↓ Doctor Card ↓ Book Appointment ↓ Select Date
↓ Select Slot ↓ POST /api/appointments ↓ PENDING + UNPAID ↓ Booking Confirmation
Create appointmentService.js
1
src/services/appointmentService.js
import api, {getAuthConfig} from "../api/api";
const createAppointment = async (appointmentData) => {
const response = await api.post("/appointments",appointmentData,
getAuthConfig());
return response.data;
};
const appointmentService = {createAppointment};
export default appointmentService;getMyAppointments() cancelAppointment() getDoctorAppointments() completeAppointment()
Update DoctorCard
2
import {Link} from "react-router";<button className="btn btn-primary w-100" disabled>
Book Appointment
</button><Link to={`/patient/doctors/${doctor.id}/book`} className="btn btn-primary w-100">
Book Appointment
</Link>import { Link } from "react-router";
function DoctorCard({ doctor }) {
return (
<div className="card h-100 shadow-sm">
{doctor.photoUrl ? (
<img
src={doctor.photoUrl}
className="card-img-top"
alt={doctor.name}
style={{ height: "220px", objectFit: "cover" }}
/> ) : (
<div
className="bg-light d-flex align-items-center j
ustify-content-center text-secondary"
style={{ height: "220px" }}>
No Photo
</div>
)}
<div className="card-body d-flex flex-column">
<h5 className="card-title">{doctor.name}</h5>
<p className="text-primary fw-semibold mb-2">
{doctor.specialization}
</p>
<p className="card-text mb-2">
<strong>Experience:</strong> {doctor.experienceYears} years
</p>
<p className="card-text mb-4">
<strong>Consultation Fee:</strong> ₹{doctor.consultationFee}
</p>
<Link
to={`/patient/doctors/${doctor.id}/book`}
className="btn btn-primary w-100 mt-auto"
>
Book Appointment
</Link> </div>
</div>
);
}
export default DoctorCard;Create Booking Page
3
src/pages/patient/BookAppointment.jsx
import { useEffect, useState } from "react";
import { Link, useParams } from "react-router";
import doctorService from "../../services/doctorService";
import appointmentService from "../../services/appointmentService";
function BookAppointment() {
const { doctorId } = useParams();
const [doctor, setDoctor] = useState(null);
const [appointmentDate, setAppointmentDate] = useState("");
const [timeSlot, setTimeSlot] = useState("");
const [loadingDoctor, setLoadingDoctor] = useState(true);
const [booking, setBooking] = useState(false);
const [error, setError] = useState("");
const [success, setSuccess] = useState(null);
useEffect(() => {
loadDoctor();
}, [doctorId]);
const loadDoctor = async () => {
try {
setLoadingDoctor(true);
setError("");
const data = await doctorService.getDoctorById(doctorId);
setDoctor(data);
} catch (error) {
setError(error.response?.data?.message ||
"Unable to load doctor");
} finally {
setLoadingDoctor(false);
}
};
const getToday = () => {
const today = new Date();
const year = today.getFullYear();
const month = String(today.getMonth() + 1).padStart(2, "0");
const day = String(today.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
};
const handleSubmit = async (event) => {
event.preventDefault();
setError("");
setSuccess(null);
if (!appointmentDate) {
setError("Please select an appointment date");
return;
}
if (!timeSlot) {
setError("Please select a time slot");
return;
}
try {
setBooking(true);
const appointmentData = {
doctorId: Number(doctorId),
appointmentDate,
timeSlot
};
const response =
await appointmentService.createAppointment(appointmentData);
setSuccess(response);
setAppointmentDate("");
setTimeSlot("");
} catch (error) {
setError(error.response?.data?.message ||
"Unable to book appointment");
} finally {
setBooking(false);
}
};
if (loadingDoctor) {
return (
<div className="container py-5 text-center">
<div className="spinner-border text-primary"></div>
<p className="text-secondary mt-3">Loading doctor...</p>
</div>
);
}
if (!doctor) {
return (
<div className="container py-5">
<div className="alert alert-danger">
Doctor information could not be loaded.
</div>
<Link to="/patient/doctors" className="btn btn-primary">
Back to Doctors
</Link>
</div>
);
}
return (
<div className="container py-5">
<div className="row justify-content-center">
<div className="col-lg-8">
{/* PAGE HEADER */}
<div className="mb-4">
<h2>Book Appointment</h2>
<p className="text-secondary">
Select your preferred appointment date and time.
</p>
</div>
{/* DOCTOR INFORMATION */}
<div className="card shadow-sm mb-4">
<div className="card-body">
<div className="row align-items-center">
{doctor.photoUrl && (
<div className="col-md-3 mb-3 mb-md-0">
<img src={doctor.photoUrl} alt={doctor.name}
className="img-fluid rounded" />
</div>
)}
<div className={doctor.photoUrl ? "col-md-9" : "col-12"}>
<h4 className="mb-1">{doctor.name}</h4>
<p className="text-primary fw-semibold mb-2">
{doctor.specialization}
</p>
<p className="mb-1">
<strong>Experience:</strong>
{doctor.experienceYears} years
</p>
<p className="mb-0">
<strong>Consultation Fee:</strong>
₹{doctor.consultationFee}
</p>
</div>
</div>
</div>
</div>
{/* ERROR */}
{error && (
<div className="alert alert-danger">{error}</div>
)}
{/* SUCCESS */}
{success && (
<div className="alert alert-success">
<h5>Appointment Booked</h5>
<p className="mb-1">
Appointment ID: <strong>{success.id}</strong>
</p>
<p className="mb-1">
Doctor: <strong>{success.doctorName}</strong>
</p>
<p className="mb-1">
Date: <strong>{success.appointmentDate}</strong>
</p>
<p className="mb-1">
Slot: <strong>{success.timeSlot}</strong>
</p>
<p className="mb-1">
Status: <strong>{success.appointmentStatus}</strong>
</p>
<p className="mb-0">
Payment: <strong>{success.paymentStatus}</strong>
</p>
</div>
)}
{/* BOOKING FORM */}
<div className="card shadow-sm">
<div className="card-body p-4">
<form onSubmit={handleSubmit}>
{/* DATE */}
<div className="mb-4">
<label className="form-label">
Appointment Date</label>
<input
type="date"
className="form-control"
min={getToday()}
value={appointmentDate}
onChange={(event) =>
setAppointmentDate(event.target.value)}
required
/>
</div>
{/* TIME SLOT */}
<div className="mb-4">
<label className="form-label">
Select Time Slot</label>
<div className="row g-3">
{/* 10 AM */}
<div className="col-md-4">
<input
type="radio"
className="btn-check"
name="timeSlot"
id="slotMorning"
value="MORNING_10_AM"
checked={timeSlot === "MORNING_10_AM"}
onChange={(event) =>
setTimeSlot(event.target.value)}
/>
<label className="btn btn-outline-primary w-100"
htmlFor="slotMorning">
10:00 AM
</label>
</div>
{/* 2 PM */}
<div className="col-md-4">
<input
type="radio"
className="btn-check"
name="timeSlot"
id="slotAfternoon"
value="AFTERNOON_2_PM"
checked={timeSlot === "AFTERNOON_2_PM"}
onChange={(event) =>
setTimeSlot(event.target.value)}
/>
<label className="btn btn-outline-primary
w-100" htmlFor="slotAfternoon">
2:00 PM
</label>
</div>
{/* 5 PM */}
<div className="col-md-4">
<input
type="radio"
className="btn-check"
name="timeSlot"
id="slotEvening"
value="EVENING_5_PM"
checked={timeSlot === "EVENING_5_PM"}
onChange={(event) =>
setTimeSlot(event.target.value)}
/>
<label className="btn btn-outline-primary
w-100" htmlFor="slotEvening">
5:00 PM
</label>
</div>
</div>
</div>
{/* FEE */}
<div className="alert alert-light border">
Consultation Fee: <strong>
₹{doctor.consultationFee}</strong>
</div>
<div className="d-flex gap-2">
<Link to="/patient/doctors"
className="btn btn-outline-secondary">
Back
</Link>
<button
type="submit"
className="btn btn-primary flex-grow-1"
disabled={booking}
>
{booking ? "Booking..." : "Book Appointment"}
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
);
}
export default BookAppointment;
function BookAppointment() {
const { doctorId } = useParams();
const [doctor, setDoctor] = useState(null);
const [appointmentDate, setAppointmentDate] = useState("");
const [timeSlot, setTimeSlot] = useState("");
const [loadingDoctor, setLoadingDoctor] = useState(true);
const [booking, setBooking] = useState(false);
const [error, setError] = useState("");
const [success, setSuccess] = useState(null);
useEffect(() => {
loadDoctor();
}, [doctorId]);
const loadDoctor = async () => {
try {
setLoadingDoctor(true);
setError("");
const data = await doctorService.getDoctorById(doctorId);
setDoctor(data);
} catch (error) {
setError(error.response?.data?.message ||
"Unable to load doctor");
} finally {
setLoadingDoctor(false);
}
};
const getToday = () => {
const today = new Date();
const year = today.getFullYear();
const month = String(today.getMonth() + 1).padStart(2, "0");
const day = String(today.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
};
const handleSubmit = async (event) => {
event.preventDefault();
setError("");
setSuccess(null);
if (!appointmentDate) {
setError("Please select an appointment date");
return;
}
if (!timeSlot) {
setError("Please select a time slot");
return;
}
try {
setBooking(true);
const appointmentData = {
doctorId: Number(doctorId),
appointmentDate,
timeSlot
};
const response =
await appointmentService.createAppointment(appointmentData);
setSuccess(response);
setAppointmentDate("");
setTimeSlot("");
} catch (error) {
setError(error.response?.data?.message ||
"Unable to book appointment");
} finally {
setBooking(false);
}
};
if (loadingDoctor) {
return (
<div className="container py-5 text-center">
<div className="spinner-border text-primary"></div>
<p className="text-secondary mt-3">Loading doctor...</p>
</div>
);
}
if (!doctor) {
return (
<div className="container py-5">
<div className="alert alert-danger">
Doctor information could not be loaded.
</div>
<Link to="/patient/doctors" className="btn btn-primary">
Back to Doctors
</Link>
</div>
);
}
return (
<div className="container py-5">
<div className="row justify-content-center">
<div className="col-lg-8">
{/* PAGE HEADER */}
<div className="mb-4">
<h2>Book Appointment</h2>
<p className="text-secondary">
Select your preferred appointment date and time.
</p>
</div>
{/* DOCTOR INFORMATION */}
<div className="card shadow-sm mb-4">
<div className="card-body">
<div className="row align-items-center">
{doctor.photoUrl && (
<div className="col-md-3 mb-3 mb-md-0">
<img src={doctor.photoUrl} alt={doctor.name}
className="img-fluid rounded" />
</div>
)}
<div className={doctor.photoUrl ? "col-md-9" : "col-12"}>
<h4 className="mb-1">{doctor.name}</h4>
<p className="text-primary fw-semibold mb-2">
{doctor.specialization}
</p>
<p className="mb-1">
<strong>Experience:</strong>
{doctor.experienceYears} years
</p>
<p className="mb-0">
<strong>Consultation Fee:</strong>
₹{doctor.consultationFee}
</p>
</div>
</div>
</div>
</div>
{/* ERROR */}
{error && (
<div className="alert alert-danger">{error}</div>
)}
{/* SUCCESS */}
{success && (
<div className="alert alert-success">
<h5>Appointment Booked</h5>
<p className="mb-1">
Appointment ID: <strong>{success.id}</strong>
</p>
<p className="mb-1">
Doctor: <strong>{success.doctorName}</strong>
</p>
<p className="mb-1">
Date: <strong>{success.appointmentDate}</strong>
</p>
<p className="mb-1">
Slot: <strong>{success.timeSlot}</strong>
</p>
<p className="mb-1">
Status: <strong>{success.appointmentStatus}</strong>
</p>
<p className="mb-0">
Payment: <strong>{success.paymentStatus}</strong>
</p>
</div>
)}
{/* BOOKING FORM */}
<div className="card shadow-sm">
<div className="card-body p-4">
<form onSubmit={handleSubmit}>
{/* DATE */}
<div className="mb-4">
<label className="form-label">
Appointment Date</label>
<input
type="date"
className="form-control"
min={getToday()}
value={appointmentDate}
onChange={(event) =>
setAppointmentDate(event.target.value)}
required
/>
</div>
{/* TIME SLOT */}
<div className="mb-4">
<label className="form-label">
Select Time Slot</label>
<div className="row g-3">
{/* 10 AM */}
<div className="col-md-4">
<input
type="radio"
className="btn-check"
name="timeSlot"
id="slotMorning"
value="MORNING_10_AM"
checked={timeSlot === "MORNING_10_AM"}
onChange={(event) =>
setTimeSlot(event.target.value)}
/>
<label className="btn btn-outline-primary w-100"
htmlFor="slotMorning">
10:00 AM
</label>
</div>
{/* 2 PM */}
<div className="col-md-4">
<input
type="radio"
className="btn-check"
name="timeSlot"
id="slotAfternoon"
value="AFTERNOON_2_PM"
checked={timeSlot === "AFTERNOON_2_PM"}
onChange={(event) =>
setTimeSlot(event.target.value)}
/>
<label className="btn btn-outline-primary
w-100" htmlFor="slotAfternoon">
2:00 PM
</label>
</div>
{/* 5 PM */}
<div className="col-md-4">
<input
type="radio"
className="btn-check"
name="timeSlot"
id="slotEvening"
value="EVENING_5_PM"
checked={timeSlot === "EVENING_5_PM"}
onChange={(event) =>
setTimeSlot(event.target.value)}
/>
<label className="btn btn-outline-primary
w-100" htmlFor="slotEvening">
5:00 PM
</label>
</div>
</div>
</div>
{/* FEE */}
<div className="alert alert-light border">
Consultation Fee: <strong>
₹{doctor.consultationFee}</strong>
</div>
<div className="d-flex gap-2">
<Link to="/patient/doctors"
className="btn btn-outline-secondary">
Back
</Link>
<button
type="submit"
className="btn btn-primary flex-grow-1"
disabled={booking}
>
{booking ? "Booking..." : "Book Appointment"}
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
);
}
export default BookAppointment;
const getToday = () => {
const today = new Date();
const year = today.getFullYear();
const month = String(today.getMonth() + 1).padStart(2, "0");
const day = String(today.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
};
const handleSubmit = async (event) => {
event.preventDefault();
setError("");
setSuccess(null);
if (!appointmentDate) {
setError("Please select an appointment date");
return;
}
if (!timeSlot) {
setError("Please select a time slot");
return;
}
try {
setBooking(true);
const appointmentData = {
doctorId: Number(doctorId),appointmentDate,timeSlot
};
const response =
await appointmentService.createAppointment(appointmentData);
setSuccess(response);
setAppointmentDate("");
setTimeSlot("");
} catch (error) {
setError(error.response?.data?.message ||
"Unable to book appointment");
} finally {
setBooking(false);
}
};
if (loadingDoctor) {
return (
<div className="container py-5 text-center">
<div className="spinner-border text-primary"></div>
<p className="text-secondary mt-3">Loading doctor...</p>
</div>
);
}
if (!doctor) {
return (
<div className="container py-5">
<div className="alert alert-danger">
Doctor information could not be loaded.
</div>
<Link to="/patient/doctors" className="btn btn-primary">
Back to Doctors
</Link>
</div>
);
}
return (
<div className="container py-5">
<div className="row justify-content-center">
<div className="col-lg-8">
{/* PAGE HEADER */}
<div className="mb-4">
<h2>Book Appointment</h2>
<p className="text-secondary">
Select your preferred appointment date and time.
</p>
</div>
{/* DOCTOR INFORMATION */}
<div className="card shadow-sm mb-4">
<div className="card-body">
<div className="row align-items-center">
{doctor.photoUrl && (
<div className="col-md-3 mb-3 mb-md-0">
<img src={doctor.photoUrl} alt={doctor.name}
className="img-fluid rounded" />
</div>
)}
<div className={doctor.photoUrl ? "col-md-9" : "col-12"}>
<h4 className="mb-1">{doctor.name}</h4>
<p className="text-primary fw-semibold mb-2">
{doctor.specialization}
</p>
<p className="mb-1">
<strong>Experience:</strong>
{doctor.experienceYears} years
</p>
<p className="mb-0">
<strong>Consultation Fee:</strong>
₹{doctor.consultationFee}
</p>
</div>
</div>
</div>
</div>
{/* ERROR */}
{error && (
<div className="alert alert-danger">{error}</div>
)}
{/* SUCCESS */}
{success && (
<div className="alert alert-success">
<h5>Appointment Booked</h5>
<p className="mb-1">
Appointment ID: <strong>{success.id}</strong>
</p>
<p className="mb-1">
Doctor: <strong>{success.doctorName}</strong>
</p>
<p className="mb-1">
Date: <strong>{success.appointmentDate}</strong>
</p>
<p className="mb-1">
Slot: <strong>{success.timeSlot}</strong>
</p>
<p className="mb-1">
Status: <strong>{success.appointmentStatus}</strong>
</p>
<p className="mb-0">
Payment: <strong>{success.paymentStatus}</strong>
</p>
</div>
)}
{/* BOOKING FORM */}
<div className="card shadow-sm">
<div className="card-body p-4">
<form onSubmit={handleSubmit}>
{/* DATE */}
<div className="mb-4">
<label className="form-label">
Appointment Date</label>
<input
type="date"
className="form-control"
min={getToday()}
value={appointmentDate}
onChange={(event) =>
setAppointmentDate(event.target.value)}
required
/>
</div>
{/* TIME SLOT */}
<div className="mb-4">
<label className="form-label">
Select Time Slot</label>
<div className="row g-3">
{/* 10 AM */}
<div className="col-md-4">
<input
type="radio"
className="btn-check"
name="timeSlot"
id="slotMorning"
value="MORNING_10_AM"
checked={timeSlot === "MORNING_10_AM"}
onChange={(event) =>
setTimeSlot(event.target.value)}
/>
<label className="btn btn-outline-primary w-100"
htmlFor="slotMorning">
10:00 AM
</label>
</div>
{/* 2 PM */}
<div className="col-md-4">
<input
type="radio"
className="btn-check"
name="timeSlot"
id="slotAfternoon"
value="AFTERNOON_2_PM"
checked={timeSlot === "AFTERNOON_2_PM"}
onChange={(event) =>
setTimeSlot(event.target.value)}
/>
<label className="btn btn-outline-primary
w-100" htmlFor="slotAfternoon">
2:00 PM
</label>
</div>
{/* 5 PM */}
<div className="col-md-4">
<input
type="radio"
className="btn-check"
name="timeSlot"
id="slotEvening"
value="EVENING_5_PM"
checked={timeSlot === "EVENING_5_PM"}
onChange={(event) =>
setTimeSlot(event.target.value)}
/>
<label className="btn btn-outline-primary
w-100" htmlFor="slotEvening">
5:00 PM
</label>
</div>
</div>
</div>
{/* FEE */}
<div className="alert alert-light border">
Consultation Fee: <strong>
₹{doctor.consultationFee}</strong>
</div>
<div className="d-flex gap-2">
<Link to="/patient/doctors"
className="btn btn-outline-secondary">
Back
</Link>
<button
type="submit"
className="btn btn-primary flex-grow-1"
disabled={booking}
>
{booking ? "Booking..." : "Book Appointment"}
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
);
}
export default BookAppointment;
const response =
await appointmentService.createAppointment(appointmentData);
setSuccess(response);
setAppointmentDate("");
setTimeSlot("");
} catch (error) {
setError(error.response?.data?.message ||
"Unable to book appointment");
} finally {
setBooking(false);
}
};
if (loadingDoctor) {
return (
<div className="container py-5 text-center">
<div className="spinner-border text-primary"></div>
<p className="text-secondary mt-3">Loading doctor...</p>
</div>
);
}
if (!doctor) {
return (
<div className="container py-5">
<div className="alert alert-danger">
Doctor information could not be loaded.
</div>
<Link to="/patient/doctors" className="btn btn-primary">
Back to Doctors
</Link>
</div>
);
}
return (
<div className="container py-5">
<div className="row justify-content-center">
<div className="col-lg-8">
{/* PAGE HEADER */}
<div className="mb-4">
<h2>Book Appointment</h2>
<p className="text-secondary">
Select your preferred appointment date and time.
</p>
</div>
{/* DOCTOR INFORMATION */}
<div className="card shadow-sm mb-4">
<div className="card-body">
<div className="row align-items-center">
{doctor.photoUrl && (
<div className="col-md-3 mb-3 mb-md-0">
<img src={doctor.photoUrl} alt={doctor.name}
className="img-fluid rounded" />
</div>
)}
<div className={doctor.photoUrl ?
"col-md-9" : "col-12"}>
<h4 className="mb-1">
{doctor.name}</h4>
<p className="text-primary
fw-semibold mb-2">
{doctor.specialization}
</p>
<p className="mb-1">
<strong>Experience:</strong>
{doctor.experienceYears} years
</p>
<p className="mb-0">
<strong>
Consultation Fee:</strong>
₹{doctor.consultationFee}
</p>
</div>
</div>
</div>
</div>
{/* ERROR */}
{error && (
<div className="alert alert-danger">
{error}</div>
)}
{/* SUCCESS */}
{success && (
<div className="alert alert-success">
<h5>Appointment Booked</h5>
<p className="mb-1">
Appointment ID: <strong>
{success.id}</strong>
</p>
<p className="mb-1">
Doctor: <strong>{success.doctorName}</strong>
</p>
<p className="mb-1">
Date: <strong>{success.appointmentDate}</strong>
</p>
<p className="mb-1">
Slot: <strong>{success.timeSlot}</strong>
</p>
<p className="mb-1">
Status: <strong>{success.appointmentStatus}</strong>
</p>
<p className="mb-0">
Payment: <strong>{success.paymentStatus}</strong>
</p>
</div>
)}
{/* BOOKING FORM */}
<div className="card shadow-sm">
<div className="card-body p-4">
<form onSubmit={handleSubmit}>
{/* DATE */}
<div className="mb-4">
<label className="form-label">
Appointment Date</label>
<input
type="date"
className="form-control"
min={getToday()}
value={appointmentDate}
onChange={(event) =>
setAppointmentDate(event.target.value)}
required
/>
</div>
{/* TIME SLOT */}
<div className="mb-4">
<label className="form-label">
Select Time Slot</label>
<div className="row g-3">
{/* 10 AM */}
<div className="col-md-4">
<input
type="radio"
className="btn-check"
name="timeSlot"
id="slotMorning"
value="MORNING_10_AM"
onChange={(event) =>
setTimeSlot(event.target.value)}
/>
<label
className="btn btn-outline-primary w-100"
htmlFor="slotMorning">
10:00 AM
</label>
</div>
{/* 2 PM */}
<div className="col-md-4">
<input
type="radio"
className="btn-check"
name="timeSlot"
id="slotAfternoon"
value="AFTERNOON_2_PM"
checked={timeSlot === "AFTERNOON_2_PM"}
onChange={(event) =>
setTimeSlot(event.target.value)}
/>
<label className="btn btn-outline-primary
w-100" htmlFor="slotAfternoon">
2:00 PM
</label>
</div>
{/* 5 PM */}
<div className="col-md-4">
<input
type="radio"
className="btn-check"
name="timeSlot"
id="slotEvening"
value="EVENING_5_PM"
checked={timeSlot === "EVENING_5_PM"}
onChange={(event) =>
setTimeSlot(event.target.value)}
/>
<label className="btn btn-outline-primary
w-100" htmlFor="slotEvening">
5:00 PM
</label>
</div>
</div>
</div>
{/* FEE */}
<div className="alert alert-light border">
Consultation Fee: <strong>
₹{doctor.consultationFee}</strong>
</div>
<div className="d-flex gap-2">
<Link to="/patient/doctors"
className="btn btn-outline-secondary">
Back
</Link>
<button
type="submit"
className="btn btn-primary flex-grow-1"
disabled={booking}>
{booking ? "Booking..." : "Book Appointment"}
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
);
}
export default BookAppointment;Text
Why the Backend Receives Enum Values
10:00 AM 2:00 PM 5:00 PM
MORNING_10_AM AFTERNOON_2_PM EVENING_5_PM
public enum TimeSlot {
MORNING_10_AM,
AFTERNOON_2_PM,
EVENING_5_PM
}Human-friendly UI ↓ Enum value ↓ Spring Boot
Why We Don't Send Patient ID
{
"doctorId": 3,
"appointmentDate": "2026-09-26",
"timeSlot": "MORNING_10_AM"
}patientId ❌ consultationFee ❌ appointmentStatus ❌ paymentStatus ❌
JWT ↓ email ↓ User
Update Router
4
import BookAppointment from "../pages/patient/BookAppointment";{
path: "patient/doctors/:doctorId/book",
element: (
<ProtectedRoute allowedRoles={[
"PATIENT"
]} >
<BookAppointment />
</ProtectedRoute>
)
},Test Doctor Navigation
5
Doctor Name Specialization Experience Fee
Test Appointment Booking
6
Date: September 26 Slot: 10:00 AM
POST /api/appointments Authorization: Bearer <patient-token> Content-Type: application/json
{
"doctorId": 3,
"appointmentDate": "2026-09-26",
"timeSlot": "MORNING_10_AM"
}patient → from JWT doctor → ID 3 consultationFee → DoctorProfile appointmentStatus → PENDING paymentStatus → UNPAID
{
"id": 7,
"patientId": 1,
"patientName": "Rahul Shah",
"doctorId": 3,
"doctorName": "Dr. Ananya Sharma", "specialization": "Cardiologist", "appointmentDate": "2026-09-26", "timeSlot": "MORNING_10_AM", "consultationFee": 1200, "appointmentStatus": "PENDING", "paymentStatus": "UNPAID", "createdAt": "2026-09-24T18:10:00" }
Duplicate Slot Test
7
Doctor: Dr. Ananya
Date: 2026-09-26
Slot: 10 AMDoctor 3 + 2026-09-26 + MORNING_10_AM
Test Another Slot
8
{
"doctorId": 3,
"appointmentDate": "2026-09-26",
"timeSlot": "AFTERNOON_2_PM"
}Test Another Doctor
9
Dr. Ananya 10 AM
Cancelled Slot Behavior
11
Doctor + Date + Slot
Appointment Doctor 3 10 AM CANCELLED
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 completed the appointment booking functionality in the Medicare application.
Checkpoint
Git Push
git push origin branchNameNext-Lab Preparation
Module:
1) React Basics & Backend Integration (Spring Boot)