Implementing Appointment Booking Using Spring Boot and React
Business Scenario
In this lab, you will develop an Appointment Booking System using Spring Boot and React.
Pre-Lab Preparation
Module :
1. Set Up the Development Environment: Ensure Spring Boot, React, MySQL, and the required dependencies are installed and configured.
2. Verify Existing Features: Ensure user authentication, JWT security, doctor listing, and doctor details APIs are working correctly.
3. Understand Appointment Booking: Review REST API integration, date and time-slot selection, appointment validation, and booking status management.
Phase 12E — Appointment Booking
Now we connect:
Find Doctor
↓
Doctor Card
↓
Book Appointment
↓
Select Date
↓
Select Slot
↓
POST /api/appointments
↓
PENDING + UNPAID
↓
Booking ConfirmationFind Doctor
↓
Doctor Card
↓
Book Appointment
↓
Select Date
↓
Select Slot
↓
POST /api/appointments
↓
PENDING + UNPAID
↓
Booking ConfirmationThis matches the original project flow where the patient selects a consultation date and one of the fixed slots before proceeding toward payment.
For now we stop at:
PENDING
UNPAIDbecause payment is still Phase 13.1
Create appointmentService.js
Create:
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;
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;
For now only one function.
We will add:
getMyAppointments()
cancelAppointment()
getDoctorAppointments()
completeAppointment()
in the next phase when those screens are implemented.
2
Update DoctorCard
Currently the button is disabled.
Open:
src/components/doctor/DoctorCard.jsxsrc/components/doctor/DoctorCard.jsxAdd:
import {
Link
} from "react-router";
Then replace:
<button
className="btn btn-primary w-100"
disabled
>
Book Appointment
</button>with:
<Link
to={`/patient/doctors/${doctor.id}/book`}
className="btn btn-primary w-100"
>
Book Appointment
</Link>
Your complete DoctorCard.jsx becomes:
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
justify-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;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
justify-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;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
justify-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;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
justify-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;3
Create Booking Page
Create:
src/pages/patient/BookAppointment.jsxWe need:
doctorId
date
slot
doctor details
loading
error
booking resultUse:
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
4
Why the Backend Receives Enum Values
The user sees:
10:00 AM
2:00 PM
5:00 PMBut React sends:
MORNING_10_AM
AFTERNOON_2_PM
EVENING_5_PMbecause our Spring enum is:
public enum TimeSlot {
MORNING_10_AM,
AFTERNOON_2_PM,
EVENING_5_PM
}public enum TimeSlot {
MORNING_10_AM,
AFTERNOON_2_PM,
EVENING_5_PM
}So:
Human-friendly UI
↓
Enum value
↓
Spring Boot
This is better than exposing ugly enum names to the user.
5
Why We Don't Send Patient ID
Our request becomes:
{
"doctorId": 3,
"appointmentDate": "2026-09-26",
"timeSlot": "MORNING_10_AM"
}Notice:
patientId ❌
consultationFee ❌
appointmentStatus ❌
paymentStatus ❌The backend gets the patient from:
JWT
↓
↓
User
and consultation fee from:
DoctorProfileThis preserves the security design from Phase 10.
6
Update Router
Open:
src/routes/router.jsx
Add:
import BookAppointment
from "../pages/patient/BookAppointment";Then inside MainLayout → children, add:
{
path:
"patient/doctors/:doctorId/book",
element: (
<ProtectedRoute
allowedRoles={[
"PATIENT"
]}
>
<BookAppointment />
</ProtectedRoute>
)
},{
path:
"patient/doctors/:doctorId/book",
element: (
<ProtectedRoute
allowedRoles={[
"PATIENT"
]}
>
<BookAppointment />
</ProtectedRoute>
)
},So now:
/patient/doctorscontains doctor cards.
Clicking doctor 3 goes to:
/patient/doctors/3/bookAnd:
useParams()extracts:
doctorId = "3"7
Test Doctor Navigation
Login as PATIENT.
Open:
/patient/doctorsClick:
Book AppointmentSuppose doctor ID is 3.
You should reach:
/patient/doctors/3/bookThe frontend calls:
GET /api/doctors/3and displays:
Doctor Name
Specialization
Experience
Fee
8
Test Appointment Booking
Select:
Date: September 26 Slot: 10:00 AM
Date: September 26 Slot: 10:00 AM
Frontend sends:
POST /api/appointments
Authorization: Bearer <patient-token>
Content-Type: application/json
Body:
{
"doctorId": 3,
"appointmentDate": "2026-09-26",
"timeSlot": "MORNING_10_AM"
}Backend should create:
patient
→ from JWT
doctor
→ ID 3
consultationFee
→ DoctorProfile
appointmentStatus
→ PENDING
paymentStatus
→ UNPAIDExpected:
201 CREATED9
Expected Response
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>Something approximately like:
{
"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"
}React then shows the confirmation box.
10
Duplicate Slot Test
Suppose this already exists:
Doctor:
Dr. Ananya
Date:
2026-09-26
Slot:
10 AMDoctor:
Dr. Ananya
Date:
2026-09-26
Slot:
10 AMTry booking it again.
Backend should detect:
Doctor 3
+
2026-09-26
+
MORNING_10_AMand return:
409 CONFLICTSomething like:
Selected appointment slot is already bookedReact displays it through:
error.response?.data?.messageExactly what we want. ✅
11
Test Another Slot
React displays it through:
September 26
but choose:
2:00 PM
Request:
{
"doctorId": 3,
"appointmentDate": "2026-09-26",
"timeSlot": "AFTERNOON_2_PM"
}
{
"doctorId": 3,
"appointmentDate": "2026-09-26",
"timeSlot": "AFTERNOON_2_PM"
}
This should succeed if that slot is free.
12
Test Another Slot
If:
Dr. Ananya
10 AM
is already booked, another doctor can still have:
Dr. Rohan
10 AM
because slot availability is based on:
Doctor
+
Date
+
Slot
Doctor
+
Date
+
Slot
not globally across the hospital.
13
Cancelled Slot Behavior
Our backend query intentionally ignores:
CANCELLED
appointments when checking slot occupancy.
So:
Appointment
Doctor 3
10 AM
CANCELLED
means the same slot becomes available again.
No special React code is needed.
This is backend business logic.
14
Past Date Protection
Frontend:
min={getToday()}
prevents the date picker from selecting yesterday.
Backend also has:
@FutureOrPresent
on appointment date.
So:
React validation
+
Spring Boot validation
both protect it.
Frontend improves user experience.
Backend remains authoritative.
15
Why getToday() Is Written This Way
You may sometimes see:
new Date()
.toISOString()
.split("T")[0]But toISOString() uses UTC.
For our classroom project, I prefer:
getFullYear()
getMonth()
getDate()
because it constructs today's date according to the browser's local date.
That avoids timezone confusion around midnight.
16
What About Available Slot API?
We are not adding something like:
GET /api/doctors/{id}/available-slots
because our backend doesn't currently have such an API.
For the MVP:
Patient selects slot
↓
Backend validates availability
↓
Free → 201
Booked → 409
This is perfectly valid and significantly simpler.
Later, a real production improvement could pre-disable occupied slots.
But we aren't adding extra backend complexity without a real requirement.
17
What About Payment?
Your original workflow says:
Select date + slot
↓
Proceed to Payment
↓
Payment Gateway
But we're intentionally implementing this in stages.
Currently:
Book Appointment
↓
PENDING
UNPAID
Later Phase 13:
Select Date + Slot
↓
Create Payment Order
↓
Razorpay
↓
Verify Payment
↓
PAID
CONFIRMED
So don't integrate dummy Razorpay logic into this page yet.
18
Current Patient Flow
We now have:
REGISTER
↓
LOGIN
↓
Patient Dashboard
↓
Find Doctor
↓
Search
Filter
Sort
Pagination
↓
DoctorCard
↓
Book Appointment
↓
Date
↓
Time Slot
↓
POST /api/appointments
↓
PENDING
UNPAID
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;
REGISTER
↓
LOGIN
↓
Patient Dashboard
↓
Find Doctor
↓
Search
Filter
Sort
Pagination
↓
DoctorCard
↓
Book Appointment
↓
Date
↓
Time Slot
↓
POST /api/appointments
↓
PENDING
UNPAID
REGISTER
↓
LOGIN
↓
Patient Dashboard
↓
Find Doctor
↓
Search
Filter
Sort
Pagination
↓
DoctorCard
↓
Book Appointment
↓
Date
↓
Time Slot
↓
POST /api/appointments
↓
PENDING
UNPAID
This is our first complete patient business flow. 🎉
19
New Files
We reuse:
DoctorCard
doctorService
api.js
AuthContext
ProtectedRoute
MainLayout
DoctorCard
doctorService
api.js
AuthContext
ProtectedRoute
MainLayout
That's exactly the component/service reuse we want.
Phase 12E Testing Checklist
Verify all of these before moving on:
✅ Book Appointment button works
✅ Route contains doctor ID
✅ Doctor details load
✅ Only PATIENT can access booking route
✅ Date cannot be empty
✅ Past date cannot be selected
✅ Slot cannot be empty
✅ 10 AM works
✅ 2 PM works
✅ 5 PM works
✅ Correct enum reaches backend
✅ JWT reaches backend
✅ patientId is NOT sent
✅ consultationFee is NOT sent
✅ Backend gets actual fee from doctor
✅ New appointment = PENDING
✅ New payment status = UNPAID
✅ Successful booking shows confirmation
✅ Duplicate slot returns 409
✅ Error appears properly
✅ Cancelled slot can later be reused
✅ No Axios interceptor
✅ API call remains in appointmentService.js
✅ Book Appointment button works
✅ Route contains doctor ID
✅ Doctor details load
✅ Only PATIENT can access booking route
✅ Date cannot be empty
✅ Past date cannot be selected
✅ Slot cannot be empty
✅ 10 AM works
✅ 2 PM works
✅ 5 PM works
✅ Correct enum reaches backend
✅ JWT reaches backend
✅ patientId is NOT sent
✅ consultationFee is NOT sent
✅ Backend gets actual fee from doctor
✅ New appointment = PENDING
✅ New payment status = UNPAID
✅ Successful booking shows confirmation
✅ Duplicate slot returns 409
✅ Error appears properly
✅ Cancelled slot can later be reused
✅ No Axios interceptor
✅ API call remains in appointmentService.js
✅ Book Appointment button works
✅ Route contains doctor ID
✅ Doctor details load
✅ Only PATIENT can access booking route
✅ Date cannot be empty
✅ Past date cannot be selected
✅ Slot cannot be empty
✅ 10 AM works
✅ 2 PM works
✅ 5 PM works
✅ Correct enum reaches backend
✅ JWT reaches backend
✅ patientId is NOT sent
✅ consultationFee is NOT sent
✅ Backend gets actual fee from doctor
✅ New appointment = PENDING
✅ New payment status = UNPAID
✅ Successful booking shows confirmation
✅ Duplicate slot returns 409
✅ Error appears properly
✅ Cancelled slot can later be reused
✅ No Axios interceptor
✅ API call remains in appointmentService.js
✅ Book Appointment button works
✅ Route contains doctor ID
✅ Doctor details load
✅ Only PATIENT can access booking route
✅ Date cannot be empty
✅ Past date cannot be selected
✅ Slot cannot be empty
✅ 10 AM works
✅ 2 PM works
✅ 5 PM works
✅ Correct enum reaches backend
✅ JWT reaches backend
✅ patientId is NOT sent
✅ consultationFee is NOT sent
✅ Backend gets actual fee from doctor
✅ New appointment = PENDING
✅ New payment status = UNPAID
✅ Successful booking shows confirmation
✅ Duplicate slot returns 409
✅ Error appears properly
✅ Cancelled slot can later be reused
✅ No Axios interceptor
✅ API call remains in appointmentService.js
Once this works, Phase 12E is complete. ✅📅
Next — Phase 12F: My Appointments
Next we will build:
Patient Dashboard
↓
My Appointments
↓
GET /api/appointments/my
↓
Upcoming / Completed / Cancelled
Appointment Card
↓
Doctor
Date
Slot
Fee
Appointment Status
Payment Status
PENDING / CONFIRMED
↓
Cancel Appointment
COMPLETED
↓
View Prescription
20
Patient Dashboard
↓
My Appointments
↓
GET /api/appointments/my
↓
Upcoming / Completed / Cancelled
Appointment Card
↓
Doctor
Date
Slot
Fee
Appointment Status
Payment Status
PENDING / CONFIRMED
↓
Cancel Appointment
COMPLETED
↓
View Prescription
That will also introduce:
Patient Dashboard
↓
My Appointments
↓
GET /api/appointments/my
↓
Upcoming / Completed / Cancelled
Appointment Card
↓
Doctor
Date
Slot
Fee
Appointment Status
Payment Status
PENDING / CONFIRMED
↓
Cancel Appointment
COMPLETED
↓
View Prescription
PATCH /api/appointments/{id}/cancel
and prepare the patient UI for the prescription viewer.