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 Confirmation
Find Doctor
    ↓
Doctor Card
    ↓
Book Appointment
    ↓
Select Date
    ↓
Select Slot
    ↓
POST /api/appointments
    ↓
PENDING + UNPAID
    ↓
Booking Confirmation

This 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
UNPAID
because 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.jsx
src/components/doctor/DoctorCard.jsx

Add:

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.jsx

We need:

doctorId
date
slot
doctor details
loading
error
booking result

Use:

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 PM

But React sends:

MORNING_10_AM
AFTERNOON_2_PM
EVENING_5_PM

because 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

 ↓

email

 ↓

User

and consultation fee from:

DoctorProfile

This 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/doctors

contains doctor cards.

Clicking doctor 3 goes to:

/patient/doctors/3/book

And:

useParams()

extracts:

doctorId = "3"

7

Test Doctor Navigation

Login as PATIENT.

Open:

/patient/doctors

Click:

Book Appointment

Suppose doctor ID is 3.

You should reach:

/patient/doctors/3/book

The frontend calls:

GET /api/doctors/3

and 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
→ UNPAID

Expected:

201 CREATED

9

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 AM
Doctor:
Dr. Ananya

Date:
2026-09-26

Slot:
10 AM

Try booking it again.

Backend should detect:

Doctor 3
+
2026-09-26
+
MORNING_10_AM

and return:

409 CONFLICT

Something like:

Selected appointment slot is already booked

React displays it through:

error.response?.data?.message

Exactly 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.