Content ITV PRO
This is Itvedant Content department
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.
By Content ITV