Skip to main content

Overview

Patients can book appointments by selecting a date, time slot, and session type (in-person or virtual). The booking flow guides patients through availability checking, time selection, and confirmation.

Booking Flow

1

Access the Booking Page

Navigate to /dashboard/reserve to start the booking process. The page displays:
  • Calendar for date selection
  • Available time slots
  • Session modality options
// Route: app/dashboard/reserve/page.tsx
export default function Reserve() {
  return (
    <LayoutDashboardUser
      titleSection="Seleccioná fecha y horario"
      subtitleSection="Elegí el día, horario y modalidad para tu sesión"
      route="reserve"
      routeBack="/dashboard"
    >
      <ReserveUser />
    </LayoutDashboardUser>
  )
}
2

Select Date and Time

Choose your preferred date from the calendar. The system automatically fetches available time slots for the selected date.The availability check happens with a 300ms debounce to optimize performance:
// From: app/core/dashboard/components/reserve/ReserveUser.tsx
const handleDate = (date: Date | undefined) => {
  setSelectedDate(date)
  setSelectedTime("")
  setSlots([])

  if (debounceRef.current) clearTimeout(debounceRef.current)

  debounceRef.current = setTimeout(async () => {
    if (!date) return

    const response = await getAvailabilityesReservations({
      date,
      professional_id: currentProfessionalSettings?.user_id || "",
    })

    setSlots(response.occupiedSlots)
  }, 300)
}
3

Choose Session Modality

Select the session type based on professional settings:
  • Virtual: Online video session
  • Presencial: In-person at the office
  • BOTH: Patient can choose either option
const handleSessionModalitie = ({
  modalitie,
}: {
  modalitie: SessionModalityToUser
}) => {
  setLocalReservation({
    ...localReservation,
    session_modality: modalitie,
  })
}
If the professional only offers one modality, it will be automatically selected.
4

Confirm and Proceed

After selecting date, time, and modality, click “Continue”. The system:
  • Calculates the end time based on session duration
  • Creates a reservation object
  • Routes you to payment (if deposit required) or confirmation page
const handleContinue = () => {
  if (!selectedDate || !selectedTime) return

  const endTime = getEndTimeReservation({
    start_time: selectedTime,
    session_duration_minutes:
      currentProfessionalSettings?.session_duration_minutes || 45,
  })

  const updatedReservation: Reservation = {
    ...localReservation,
    start_time: selectedTime,
    end_time: endTime,
    client_id: currentSession?.user.id || "",
    professional_id: currentProfessionalSettings?.user_id || "",
    session_modality: effectiveSessionModality,
  }

  setCurrentReservation(updatedReservation)

  if (currentProfessionalSettings?.requires_deposit) {
    router.push("/dashboard/reserve/payment")
  } else {
    router.push("/dashboard/reserve/confirm")
  }
}

Checking Availability

API Endpoint

The system checks professional availability using the /reservations/availability endpoint:
GET /reservations/availability?date=2026-03-15&professional_id=prof456
Query Parameters:
  • date (string): Date in format YYYY-MM-DD
  • professional_id (string): ID of the professional
Response:
{
  "date": "2026-03-15",
  "timezone": "",
  "occupiedSlots": [
    "2026-03-15T09:00:00",
    "2026-03-15T10:00:00",
    "2026-03-15T14:00:00"
  ]
}

Implementation

The backend filters reservations for the selected date:
// From: server/src/modules/reservations/reservations.service.ts
async getAvailability(date: string, professionalId: string) {
  const day = date.split(" ")[0];
  const nextDay = new Date(day);
  nextDay.setDate(nextDay.getDate() + 1);
  const nextDayStr = nextDay.toISOString().split("T")[0];

  const response = await this.supabaseService
    .getClient()
    .from("reservations")
    .select('*')
    .eq('professional_id', professionalId)
    .gte('start_time', `${day}T00:00:00`)
    .lt('start_time', `${nextDayStr}T00:00:00`);

  const finalSlots: string[] = []

  response.data?.forEach((subDate: Reservation) => {
    finalSlots.push(subDate.start_time);
  })

  return {
    date: day,
    timezone: "",
    occupiedSlots: finalSlots,
  };
}

Reservation Data Structure

When booking an appointment, the following reservation object is created:
export interface Reservation {
  id: string;
  client_id: string;        // Patient's user ID
  professional_id: string;  // Professional's user ID
  start_time: string;       // ISO timestamp
  end_time: string;         // ISO timestamp
  status: ReservationStatus; // "PENDING" | "CONFIRMED" | "CANCELLED"
  session_modality: SessionModalityToUser; // "Virtual" | "Presencial" | ""
  created_at: string;       // ISO timestamp
}

Client Configuration

The frontend uses a centralized server configuration for API calls:
// From: lib/serverConfig.ts
export const serverConfig = {
  reservations: {
    common: `${serverUrl}/reservations`,
    getAvailability: ({ date, professional_id }: {
      date: string | undefined;
      professional_id: string;
    }) => {
      return `${serverUrl}/reservations/availability?date=${date}&professional_id=${professional_id}`;
    },
    getPayment: `${serverUrl}/reservations/payment`,
    create: `${serverUrl}/reservations/create-without-payment`,
  }
}

Best Practices

Date Selection

Always check availability before allowing time slot selection to prevent double bookings

Session Duration

End time is automatically calculated based on professional’s session_duration_minutes setting

Admin Blocks

The system filters out time slots that professionals have manually blocked

Validation

The “Continue” button only activates when date, time, and modality are selected
Reservations are not saved to the database until payment is confirmed (if required) or the final confirmation step is completed.

Next Steps

Make Payment

Learn about the payment process for appointments requiring a deposit

Manage Reservations

View and manage your existing appointments

Build docs developers (and LLMs) love