Medicare Doctor Profile Management

Business Scenario

In this lab, you will enhance the Medicare application by implementing the Doctor Profile Management feature, allowing doctors to create, view, update, and delete their professional profiles.

Pre-Lab Preparation

Module:

1) React Basics & Backend Integration (Spring Boot)​

Phase 12H — Doctor Profile Management

DOCTOR Login
     ↓
My Profile
     ↓
GET /api/doctors/profile/me
     ↓
  • The flow becomes:
Profile exists?
   ↙           ↘
 NO             YES
 ↓               ↓
Create          View/Edit
 ↓               ↓
POST            PUT
/profile        /profile/me
  • And optionally:
Delete Profile
      ↓
DELETE /api/doctors/profile/me
  • The frontend never sends a doctorId or userId. The backend gets the logged-in doctor from the JWT.

Update doctorService.js

1

  • Open: src/services/doctorService.js
  • Use this updated version:
import api, {getAuthConfig} from "../api/api";

const getDoctors = async ({
    specialization = "",
    maxFee = "",
    name = "",
    page = 0,
    size = 6,
    sortBy = "consultationFee",
    direction = "asc"
}) => {

    const params = {
        page,
        size,
        sortBy,
        direction
    };


    if (specialization) {
        params.specialization =
            specialization;
    }


    if (
        maxFee !== ""
        &&
        Number(maxFee) > 0
    ) {

        params.maxFee =
            maxFee;
    }


    if (name.trim()) {
        params.name =
            name.trim();
    }


    const response =  await api.get(
            "/doctors",
            {
                ...getAuthConfig(),
                params
            }
        );

    return response.data;
};


const getDoctorById =
    async (doctorId) => {

        const response =
            await api.get(
                `/doctors/${doctorId}`,
                getAuthConfig()
            );

        return response.data;
    };


const createMyProfile =
    async (profileData) => {

        const response =
            await api.post(
                "/doctors/profile",
                profileData,
                getAuthConfig()
            );

        return response.data;
    };


const getMyProfile =
    async () => {

        const response =
            await api.get(
                "/doctors/profile/me",
                getAuthConfig()
            );

        return response.data;
    };


const updateMyProfile =
    async (profileData) => {

        const response =
            await api.put(
                "/doctors/profile/me",
                profileData,
                getAuthConfig()
            );

        return response.data;
    };


const deleteMyProfile =
    async () => {

        await api.delete(
            "/doctors/profile/me",
            getAuthConfig()
        );
    };


const doctorService = {

    getDoctors,
  
    getDoctorById,
  
    createMyProfile,
  
    getMyProfile,
  
    updateMyProfile,
  
    deleteMyProfile
};

export default doctorService;

Create Doctor Profile Page

2

  • Create:
src/pages/doctor/DoctorProfile.jsx
import
   { useEffect, useState } from "react";
import 
    doctorService from "../../services/doctorService";

const emptyProfile = {
    specialization: "",
      experienceYears: "",
    consultationFee: "",
    photoUrl: ""
  • Now one service handles all doctor-related APIs.


function DoctorProfile() {
  
    const [formData, setFormData] = useState(emptyProfile);
    const [profileExists, setProfileExists] = useState(false);
    const [loading, setLoading] = useState(true);
    const [saving, setSaving] = useState(false);
    const [deleting, setDeleting] = useState(false);
    const [error, setError] = useState("");
    const [success, setSuccess] = useState("");

  
    useEffect(() => {
        loadProfile();
    }, []);

    const loadProfile = async () => {
        try {
            setLoading(true);
            setError("");

            const profile = await doctorService.getMyProfile();

            setProfileExists(true);

            setFormData({
                specialization: profile.specialization || "",
                experienceYears: profile.experienceYears ?? "",
                consultationFee: profile.consultationFee ?? "",
                photoUrl: profile.photoUrl || ""
            });

        } catch (error) {
            /*
             * 404 means this doctor has
             * not created a profile yet.
             *
             * This is not treated as a
             * frontend error.
             */
            if (error.response?.status === 404) {
                setProfileExists(false);
                setFormData(emptyProfile);
                return;
            }

            setError(
                error.response?.data?.message ||
                "Unable to load doctor profile"
            );

        } finally {
            setLoading(false);
        }
    };

    const handleChange = (event) => {
        const { name, value } = event.target;
        setFormData(previous => ({
            ...previous,
            [name]: value
        }));
    };

    const handleSubmit = async (event) => {
        event.preventDefault();

        setError("");
        setSuccess("");

        const profileData = {
            specialization: formData.specialization.trim(),
            experienceYears: Number(formData.experienceYears),
            consultationFee: Number(formData.consultationFee),
            photoUrl: formData.photoUrl.trim()
        };

        try {
            setSaving(true);

            let response;

            if (profileExists) {
                response = await doctorService.updateMyProfile(profileData);

                setSuccess(
                    "Doctor profile updated successfully."
                );
            } else {
                response = await doctorService.createMyProfile(profileData);

                setProfileExists(true);

                setSuccess(
                    "Doctor profile created successfully."
                );
            }

            setFormData({
                specialization: response.specialization || "",
                experienceYears: response.experienceYears ?? "",
                consultationFee: response.consultationFee ?? "",
                photoUrl: response.photoUrl || ""
            });

        } catch (error) {
            setError(
                error.response?.data?.message ||
                "Unable to save doctor profile"
            );

        } finally {
            setSaving(false);
        }
    };

    const handleDelete = async () => {
        const confirmed = window.confirm(
            "Are you sure you want to delete your doctor profile?"
        );

        if (!confirmed) {
            return;
        }

        try {
            setDeleting(true);
            setError("");
            setSuccess("");

            await doctorService.deleteMyProfile();

            setProfileExists(false);
            setFormData(emptyProfile);

            setSuccess(
                "Doctor profile deleted successfully."
            );

        } catch (error) {
            setError(
                error.response?.data?.message ||
                "Unable to delete doctor profile"
            );

        } finally {
            setDeleting(false);
        }
    };

    if (loading) {
        return (
            <div className="container py-5 text-center">
                <div
                    className="
                        spinner-border
                        text-primary
                    "
                >
                </div>

                <p className="text-secondary mt-3">
                    Loading doctor profile...
                </p>
            </div>
        );
    }

    return (
        <div className="container py-5">
            <div className="row justify-content-center">
                <div className="col-lg-8">

                    {/* HEADING */}

                    <div className="mb-4">
                        <h2>
                            {
                                profileExists
                                    ? "My Doctor Profile"
                                    : "Create Doctor Profile"
                            }
                        </h2>

                        <p className="text-secondary">
                            Manage the information
                            patients see when
                            searching for doctors.
                        </p>
                    </div>

                    {/* SUCCESS */}

                    {success && (
                        <div className="alert alert-success">
                            {success}
                        </div>
                    )}

                    {/* ERROR */}

                    {error && (
                        <div className="alert alert-danger">
                            {error}
                        </div>
                    )}

                    {/* PROFILE FORM */}
                    <div className="card shadow-sm">
                        <div className="card-body p-4">

                            {/* PHOTO PREVIEW */}

                            {
                                formData.photoUrl
                                && (
                                    <div className="text-center mb-4">
                                        <img
                                            src={formData.photoUrl}
                                            alt="Doctor"
                                            className="rounded"
                                            style={{
                                                width: "140px",
                                                height: "140px",
                                                objectFit: "cover"
                                            }}
                                        />
                                    </div>
                                )
                            }

                            <form onSubmit={handleSubmit}>

                                {/* SPECIALIZATION */}

                                <div className="mb-3">
                                    <label className="form-label"> Specialization
                                    </label>
                                    <select
                                        name="specialization"
                                        className="form-select"
                                        value={formData.specialization}
                                        onChange={handleChange}
                                        required
                                    >
                                        <option value="">
                                            Select specialization
                                        </option>

                                        <option value="Orthopedic">
                                            Orthopedic
                                        </option>

                                        <option value="Dentist">
                                            Dentist
                                        </option>

                                        <option value="General Physician">
                                            General Physician
                                        </option>

                                        <option value="Cardiologist">
                                            Cardiologist
                                        </option>
                                    </select>
                                </div>

                                {/* EXPERIENCE */}
                                <div className="mb-3">
                                    <label className="form-label">
                                        Experience Years
                                    </label>

                                    <input
                                        type="number"
                                        name="experienceYears"
                                        className="form-control"
                                        min="0"
                                        value={formData.experienceYears}
                                        onChange={handleChange}
                                        required
                                    />
                                </div>

                                {/* CONSULTATION FEE */}

                                <div className="mb-3">
                                    <label className="form-label">
                                        Consultation Fee
                                    </label>

                                    <div className="input-group">
                                        <span className="input-group-text">
                                            ₹
                                        </span>

                                        <input
                                            type="number"
                                            name="consultationFee"
                                            className="form-control"
                                            min="1"
                                            step="0.01"
                                            value={formData.consultationFee}
                                            onChange={handleChange}
                                            required
                                        />
                                    </div>
                                </div>

                                {/* PHOTO URL */}

                                <div className="mb-4">
                                    <label className="form-label">
                                        Photo URL
                                    </label>

                                    <input
                                        type="url"
                                        name="photoUrl"
                                        className="form-control"
                                        placeholder="https://example.com/doctor.jpg"
                                        value={formData.photoUrl}
                                        onChange={handleChange}
                                    />
                                </div>

                                <button
                                    type="submit"
                                    className="btn btn-primary w-100"
                                    disabled={saving}
                                >
                                    {
                                        saving
                                            ? "Saving..."
                                            : profileExists
                                                ? "Update Profile"
                                                : "Create Profile"
                                    }
                                </button>

                                {
                                    profileExists
                                    && (
                                        <button
                                            type="button"
                                            className="btn btn-outline-danger 
                                                       w-100 mt-3"
                                            onClick={handleDelete}
                                            disabled={deleting}
                                        >
                                            {
                                                deleting
                                                    ? "Deleting..."
                                                    : "Delete Profile"
                                            }
                                        </button>
                                    )
                                }
                            </form>
                        </div>
                    </div>

                </div>
            </div>
        </div>
    );
}

export default DoctorProfile;
  • You can download the above code file by clicking the link below.

Why One Page Handles Create + Update

  • We could create:
  • But that would be unnecessary for this project.
  • Instead:

Why 404 Is Special Here

  • Suppose a doctor logs in for the first time.
  • React calls: GET /api/doctors/profile/me
  • Backend returns: 404 NOT FOUND
  • Normally 404 is an error.
  • But on this particular page it means:
Doctor account exists 
DoctorProfile doesn't exist yet 
  • So React does:
404 --> Show Create Profile form
  • Instead of showing a red error message.

Add Doctor Profile Route

3

  • Open : src/routes/router.jsx
  • Add:
import DoctorProfile from "../pages/doctor/DoctorProfile";
  • Then add:
{
    path:
        "doctor/profile",

    element: (
        <ProtectedRoute allowedRoles={["DOCTOR"]}>
            <DoctorProfile />
        </ProtectedRoute>
    )
},

Update Doctor Navbar

4

  • Inside the DOCTOR section of Navbar.jsx use:
<>

    <li className="nav-item">
        <Link className="nav-link" to="/doctor/dashboard">
            Dashboard
        </Link>
    </li>


    <li className="nav-item">
        <Link className="nav-link" to="/doctor/schedule">
            My Schedule
        </Link>
    </li>


    <li className="nav-item">
        <Link className="nav-link" to="/doctor/profile">
            My Profile
        </Link>
    </li>

</>

Update Doctor Dashboard

5

  • Your dashboard currently has:
My Schedule
Prescriptions
  • Let's add: My Profile
        </Link>
    </li>


    <li className="nav-item">
        <Link className="nav-link" to="/doctor/profile">
            My Profile
        </Link>
    </li>

</>

Update Doctor Dashboard

5

  • Your dashboard currently has:
My Schedule
Prescriptions
  • Let's add: My Profile
  • If DoctorDashboard.jsx already imports:
import {Link} from "react-router";
  • keep it.
  • Add another card:
<div className="col-md-6 col-lg-4">

    <div className="card h-100 shadow-sm">

        <div className="card-body">

            <h5> My Profile
            </h5>

            <p className="text-secondary">
                Manage specialization,
                experience, consultation
                fee and profile photo.
            </p>

            <Link to="/doctor/profile" className="btn btn-outline-primary">
                Manage Profile
            </Link>
        </div>
    </div>
</div>

Test a Doctor With No Profile

6

  • Login using a DOCTOR account that has no DoctorProfile
  • Open: http://localhost:5173/doctor/profile
  • React sends:
GET /api/doctors/profile/me
Authorization: Bearer <doctor-token>
  • Backend returns: 404 React should show:
POST /api/doctors/profile
Authorization: Bearer <doctor-token>
  • Click: Create Profile. Frontend sends:
Create Doctor Profile
  • Enter:
Specialization: Cardiologist
Experience: 8
Consultation Fee: 1200
Photo URL: https://...

Expected Creation Response

7

  • Approximately:
{
    "id": 3,
    "name": "Dr. Ananya Sharma",
    "specialization": "Cardiologist",
    "experienceYears": 8,
    "consultationFee": 1200,
    "photoUrl": "https://example.com/doctor.jpg"
}
  • React switches automatically from: Create Doctor Profile to: My Doctor Profile and the button becomes: Update Profile

Verify Patient Doctor Search

8

  • Now log in as PATIENT.
  • Open: /patient/doctors
  • The newly created doctor should appear because our doctor search reads from DoctorProfile.
  • You should see:
Dr. Ananya Sharma
Cardiologist
8 years
₹1200
[ Book Appointment ]

Delete Doctor Profile

9

  • Click: Delete Profile Browser asks:
Are you sure you want to delete your doctor profile?
  • If confirmed:
DELETE /api/doctors/profile/me
  • Expected: 204 NO CONTENT React returns to:
Create Doctor Profile

Important Delete Note

  • If this doctor already has appointments, deleting the profile may be rejected by the backend/database depending on your relationship constraints.
  • That's actually preferable to accidentally destroying appointment history
  • So if the backend returns an error, React will simply display:
Unable to delete doctor profile
  • 0r the backend's specific message

Test Second Profile Creation

10

  • Once a doctor has a profile, the frontend no longer uses: POST
  • It switches to: PUT because: profileExists === true
  • So normal UI flow won't accidentally create multiple doctor profiles.
  • The database also protects the relationship through the unique user association.

We are done with this lab. The latest source code has been uploaded to GitHub. You can access the latest commit using the link below: 

 

Great job!

You have successfully implemented the Doctor Profile Management feature in the Medicare application.

Checkpoint

   Git Push

git push origin branchName

Next-Lab Preparation

Module:

1) React Basics & Backend Integration (Spring Boot)​

SpringBoot Lab - 20

By Content ITV

SpringBoot Lab - 20

  • 177