Skip to main content

Overview

The useReservar hook provides comprehensive reservation management functionality including spot selection, date/time validation, NIP verification, email confirmation codes, and push notification scheduling. It integrates with Firebase Firestore for real-time spot availability and uses EmailJS for sending confirmation codes and tickets.

Import

import { useReservar } from '@/hooks/useReservar';

Usage

const {
  displayDate,
  displayTime,
  selectedSpot,
  setSelectedSpot,
  selectedVehicle,
  setSelectedVehicle,
  selectedCard,
  setSelectedCard,
  myVehicles,
  myCards,
  occupiedSpots,
  handleInitiateReservation,
  modalVisible,
  inputCode,
  setInputCode,
  handleConfirmReservation,
  loading
} = useReservar();

// Select a parking spot
setSelectedSpot("A12");

// Select vehicle and payment card
setSelectedVehicle(myVehicles[0]);
setSelectedCard(myCards[0]);

// Initiate reservation (sends verification code)
await handleInitiateReservation();

// Confirm with the code received via email
setInputCode("1234");
await handleConfirmReservation();

Constants

HORA_APERTURA
number
default:"7"
Opening hour (7:00 AM)
HORA_CIERRE
number
default:"23"
Closing hour (11:00 PM)
SALDO_MINIMO
number
default:"120"
Minimum credit balance required to make a reservation

State Values

Date & Time

selectedDate
Date
Currently selected date for the reservation
selectedTime
Date
Currently selected time for the reservation
displayDate
string
Formatted date string in ‘es-MX’ locale (e.g., “04/03/2026”)
displayTime
string
Formatted time string in 12-hour format (e.g., “02:30 PM”)
showDatePicker
boolean
Controls visibility of the date picker modal
showTimePicker
boolean
Controls visibility of the time picker modal

Selection State

selectedSpot
string | null
ID of the currently selected parking spot (e.g., “A12”)
selectedVehicle
object | null
The vehicle object selected for this reservation
{
  id: string;
  plate: string;
  alias?: string;
  userId: string;
}
selectedCard
object | null
The payment card object selected for this reservation
{
  id: string;
  last4: string;
  holderName: string;
  type: string;
  userId: string;
}
nip
string[]
Array of 4 strings representing the security NIP digits

Data Collections

currentCredits
number
User’s current credit balance, updated in real-time via Firestore snapshot
myVehicles
array
Array of user’s registered vehicles from Firestore
myCards
array
Array of user’s registered payment cards from Firestore
occupiedSpots
string[]
Array of spot IDs that are currently occupied (status: “active” or “pending”)

UI State

loading
boolean
Indicates if an async operation is in progress
modalVisible
boolean
Controls visibility of the verification code modal
generatedCode
string | null
The 4-digit verification code sent to user’s email (internal use)
inputCode
string
User’s input for the verification code

Functions

setShowDatePicker

setShowDatePicker: (visible: boolean) => void
Controls the visibility of the date picker component.

setShowTimePicker

setShowTimePicker: (visible: boolean) => void
Controls the visibility of the time picker component.

onChangeDate

onChangeDate: (event: any, date?: Date) => void
Handles date selection with validation:
  • Prevents selection of past dates
  • Limits reservations to maximum 4 days in advance
  • Updates both selectedDate and displayDate
event
any
Event object from the date picker
date
Date
The selected date

onChangeTime

onChangeTime: (event: any, time?: Date) => void
Handles time selection with business hours validation:
  • Enforces operating hours (7:00 AM - 11:00 PM)
  • Shows alert if time is outside business hours
  • Updates both selectedTime and displayTime
event
any
Event object from the time picker
time
Date
The selected time

setSelectedSpot

setSelectedSpot: (spotId: string | null) => void
Sets the selected parking spot ID.

setSelectedVehicle

setSelectedVehicle: (vehicle: any | null) => void
Sets the selected vehicle for the reservation.

setSelectedCard

setSelectedCard: (card: any | null) => void
Sets the selected payment card for the reservation.

handleNipChange

handleNipChange: (value: string, index: number) => void
Updates a single digit in the NIP array.
value
string
The digit value (0-9)
index
number
Position in the NIP array (0-3)

handleInitiateReservation

handleInitiateReservation: () => Promise<void>
Initiates the reservation process with validation and email verification: Validation checks:
  1. Ensures spot, vehicle, and card are selected
  2. Verifies sufficient credit balance (minimum 120 credits)
  3. Validates 4-digit NIP is complete
  4. Verifies NIP against stored security NIP in Firestore
On success:
  • Generates a random 4-digit verification code
  • Sends code to user’s email via EmailJS
  • Opens verification modal
Error handling:
  • Shows alerts for validation failures
  • Handles email sending errors

handleConfirmReservation

handleConfirmReservation: () => Promise<void>
Confirms and creates the reservation after code verification: Process:
  1. Validates verification code matches generated code
  2. Constructs reservation ID: {date}_{time}_Spot{spotId}
  3. Creates Firestore document with transaction to prevent double-booking
  4. Schedules push notification 15 minutes before reservation start
  5. Sends email ticket with QR code
  6. Navigates to home screen on success
Reservation data structure:
{
  clientId: string;
  startTime: Date;
  spotId: string;
  vehicleId: string;
  cardId: string;
  vehiclePlate: string;
  cardLast4: string;
  status: "pending";
  createdAt: Date;
  date: string;
  time: string;
  customId: string;
}

setModalVisible

setModalVisible: (visible: boolean) => void
Controls the visibility of the verification code modal.

setInputCode

setInputCode: (code: string) => void
Sets the user’s input for the verification code.

Side Effects

Notification Permissions

On mount, the hook requests notification permissions from the user to enable reservation reminders.

Real-time Credit Balance

Listens to Firestore changes on the current user’s document to update credit balance in real-time:
useEffect(() => {
  if (!auth.currentUser) return;
  const unsub = onSnapshot(doc(db, "users", auth.currentUser.uid), (doc) => {
    setCurrentCredits(doc.data()?.credits_balance || 0);
  });
  return () => unsub();
}, []);

Real-time Occupied Spots

Listens to all active and pending reservations to maintain an up-to-date list of occupied spots:
useEffect(() => {
  const q = query(
    collection(db, "reservations"), 
    where("status", "in", ["active", "pending"])
  );
  const unsubscribe = onSnapshot(q, (snapshot) => {
    const occupied: string[] = [];
    snapshot.forEach((doc) => { 
      if (doc.data().spotId) occupied.push(doc.data().spotId.toString()); 
    });
    setOccupiedSpots(occupied);
  });
  return () => unsubscribe();
}, []);

Initial Data Loading

On mount, fetches user’s vehicles and payment cards, and initializes date/time to current time (or opening hour if before 7 AM).

Push Notifications

The hook schedules a local push notification 15 minutes before the reservation start time:
const programarRecordatorio = async (fechaInicio: Date) => {
  const triggerDate = new Date(fechaInicio.getTime() - 15 * 60 * 1000);
  const now = new Date();
  const diffInSeconds = Math.floor((triggerDate.getTime() - now.getTime()) / 1000);

  if (diffInSeconds <= 0) {
    console.log("Reserva muy próxima, no se programa alerta anticipada.");
    return;
  }

  await Notifications.scheduleNotificationAsync({
    content: {
      title: "⏳ Tu reserva está por iniciar",
      body: `Faltan 15 min para tu reserva en el cajón ${selectedSpot}. ¡Prepara tu código QR!`,
      sound: true,
      data: { url: '/mis-reservas' },
    },
    trigger: {
      type: 'timeInterval', 
      seconds: diffInSeconds,
      repeats: false,
    } as any, 
  });
};

Email Integration

Uses EmailJS to send two types of emails:

Verification Code Email

Sent during handleInitiateReservation:
await emailjs.send(
  EMAILJS_SERVICE_ID, 
  EMAILJS_TEMPLATE_ID, 
  { 
    to_email: userEmail, 
    to_name: userName, 
    view_ticket: "none", 
    view_msg: "block", 
    message: code, 
    reservation_id: "NA", 
    date: "NA", 
    time: "NA", 
    spot: "NA", 
    plate: "NA"
  }, 
  { publicKey: EMAILJS_PUBLIC_KEY }
);

Reservation Ticket Email

Sent after successful reservation:
await emailjs.send(
  EMAILJS_SERVICE_ID, 
  EMAILJS_TEMPLATE_ID, 
  {
    to_email: userEmail, 
    to_name: userName,
    view_ticket: "block", 
    view_msg: "none", 
    reservation_id: reservationID, 
    qr_data: qrDataEncoded, 
    date: displayDate, 
    time: displayTime, 
    spot: selectedSpot, 
    plate: selectedVehicle.plate, 
    message: "Confirmado" 
  }, 
  { publicKey: EMAILJS_PUBLIC_KEY }
);

Example: Complete Reservation Flow

function ReservationScreen() {
  const {
    displayDate,
    displayTime,
    selectedSpot,
    setSelectedSpot,
    selectedVehicle,
    setSelectedVehicle,
    selectedCard,
    setSelectedCard,
    myVehicles,
    myCards,
    occupiedSpots,
    nip,
    handleNipChange,
    handleInitiateReservation,
    modalVisible,
    setModalVisible,
    inputCode,
    setInputCode,
    handleConfirmReservation,
    loading
  } = useReservar();

  return (
    <View>
      {/* Date and Time Display */}
      <Text>Fecha: {displayDate}</Text>
      <Text>Hora: {displayTime}</Text>

      {/* Parking Map - Show available spots */}
      <ParkingMap
        occupiedSpots={occupiedSpots}
        selectedSpot={selectedSpot}
        onSelectSpot={setSelectedSpot}
      />

      {/* Vehicle Selection */}
      <Picker
        selectedValue={selectedVehicle?.id}
        onValueChange={(value) => {
          const vehicle = myVehicles.find(v => v.id === value);
          setSelectedVehicle(vehicle);
        }}
      >
        {myVehicles.map(v => (
          <Picker.Item key={v.id} label={v.plate} value={v.id} />
        ))}
      </Picker>

      {/* Card Selection */}
      <Picker
        selectedValue={selectedCard?.id}
        onValueChange={(value) => {
          const card = myCards.find(c => c.id === value);
          setSelectedCard(card);
        }}
      >
        {myCards.map(c => (
          <Picker.Item key={c.id} label={`**** ${c.last4}`} value={c.id} />
        ))}
      </Picker>

      {/* NIP Input */}
      <View style={{ flexDirection: 'row' }}>
        {nip.map((digit, index) => (
          <TextInput
            key={index}
            value={digit}
            onChangeText={(value) => handleNipChange(value, index)}
            maxLength={1}
            keyboardType="numeric"
            secureTextEntry
          />
        ))}
      </View>

      {/* Initiate Button */}
      <Button
        title="Confirmar Reserva"
        onPress={handleInitiateReservation}
        disabled={loading}
      />

      {/* Verification Modal */}
      <Modal visible={modalVisible}>
        <Text>Ingresa el código enviado a tu email</Text>
        <TextInput
          value={inputCode}
          onChangeText={setInputCode}
          keyboardType="numeric"
          maxLength={4}
        />
        <Button
          title="Verificar"
          onPress={handleConfirmReservation}
          disabled={loading}
        />
        <Button
          title="Cancelar"
          onPress={() => setModalVisible(false)}
        />
      </Modal>
    </View>
  );
}

Error Handling

The hook handles multiple error scenarios:
  • Past dates: Shows alert and prevents selection
  • Dates beyond 4 days: Shows alert with limit message
  • Time outside business hours: Shows alert with operating hours
  • Insufficient credits: Alert shown during initiation
  • Incomplete NIP: Alert during initiation
  • Incorrect NIP: Alert after validation against Firestore
  • Wrong verification code: Alert during confirmation
  • Spot already taken: Transaction fails with custom error message
  • Email sending failures: Logged but doesn’t prevent reservation

Firebase Dependencies

  • Firestore Collections: users, reservations, vehicles, cards
  • Authentication: Requires authenticated user (auth.currentUser)
  • Real-time Listeners: Uses onSnapshot for live updates
  • Transactions: Uses runTransaction to prevent race conditions

Build docs developers (and LLMs) love