Skip to main content

Overview

The user profile is a customer-facing dashboard where registered users can:
  • View active and historical reservations
  • Manage their “virtual garage” (saved vehicles)
  • Edit personal information and password
  • Cancel or modify pending reservations
Route: /users/profile (requires authentication)
Controller: controllers/authController.js:renderProfile() (line 248-278)
View: views/profile.ejs
Client Script: public/javascripts/profile.js

Page Layout

The profile page is divided into two main panels:

Left Panel

Account Information
  • Name, email, phone display
  • “Edit Data” button
  • Current active reservations (PENDIENTE, EN CURSO)

Right Panel

Booking History
  • Past reservations (FINALIZADA, CANCELADA)
  • Click to view details
  • Filtered list with scrolling

Data Loading

When the profile loads, the server fetches all necessary data in parallel for performance:
// authController.js:252-258
const [vehicles, reservations, mainServices, additionalServices] = await Promise.all([
    customerDAO.getVehiclesByCustomerId(userId),
    reservationDAO.getReservationsByCustomerId(userId),
    serviceCatalogDAO.getMainServices(true),
    serviceCatalogDAO.getAllAdditionalServices(true)
]);

Reservation Filtering

// authController.js:261-262
const activeReservations = reservations.filter(r => 
    r.status === 'PENDIENTE' || r.status === 'EN CURSO'
);
const historyReservations = reservations.filter(r => 
    r.status === 'FINALIZADA' || r.status === 'CANCELADA'
);

Active Reservations

Display Cards

Each active reservation shows:
1

Reservation ID

Ticket icon + #42 format
2

Entry Date

Calendar icon + formatted date (e.g., “15/3/2024”)
3

Price

Large bold text: 125.50 €
4

Status Badge

Color-coded badge:
  • PENDIENTE (yellow): Awaiting check-in
  • EN CURSO (blue): Vehicle currently parked

Click Interaction

Clicking a reservation card opens a modal with full details:
// profile.ejs:44-70
<div class="historial-item reserva-clickable" 
     data-id="<%= resActual.id_reservation %>"
     data-status="<%= resActual.status %>"
     data-entry="<%= new Date(resActual.entry_date).toLocaleString('es-ES') %>"
     data-exit="<%= resActual.exit_date ? ... : 'No definida' %>"
     data-service="<%= resActual.main_service_name %>"
     data-vehicle="<%= resActual.license_plate %> (<%= resActual.brand %>)"
     data-price="<%= parseFloat(resActual.total_price).toFixed(2) %>">
Data attributes populate the modal without additional API calls.

Reservation Details Modal

When a reservation is clicked, a modal displays:
  • Reservation ID: #42
  • Entry Date: 15/3/2024, 9:00:00
  • Exit Date: 20/3/2024, 18:00:00
  • Service: “Premium Parking”
  • Vehicle: 1234ABC (Toyota Corolla)
  • Total Price: 125.50 €
  • Status Badge: Color-coded based on state

Action Buttons

Cancel Booking

Only for PENDIENTE reservationsDELETE /users/profile/reservation/:id/cancelValidation (authController.js:366-368):
if (reserva.status !== 'PENDIENTE') {
    return res.status(400).json({
        message: 'Only PENDIENTE reservations can be cancelled'
    });
}
Sets status to CANCELADA and frees parking spot.

Edit Booking

For PENDIENTE and EN CURSOOpens another modal with editable fields:
  • Entry date (locked if EN CURSO)
  • Exit date
  • Main service selector
  • Additional services checkboxes
PUT /users/profile/reservation/:id/editServer recalculates price to prevent tampering.
Entry Date Lock: If reservation is EN CURSO (vehicle already checked in), entry date cannot be changed—only the exit date and services can be modified (authController.js:401).

Edit Reservation Flow

Frontend Modal

<!-- profile.ejs:226-282 -->
<div class="modal-overlay" id="editReservaModal">
  <form id="form-edit-reserva">
    <input type="hidden" id="edit-res-id" name="id_reservation" />
    
    <div class="form-row">
      <input type="datetime-local" id="edit-res-entrada" name="entry_date" />
      <input type="datetime-local" id="edit-res-exit" name="exit_date" />
    </div>
    
    <select id="edit-res-service" name="id_main_service">
      <!-- Main services -->
    </select>
    
    <div class="extras-grid">
      <!-- Additional services checkboxes -->
    </div>
    
    <button type="submit">Request Update</button>
  </form>
</div>

Backend Update Logic

Route: PUT /users/profile/reservation/:id/edit
Controller: authController.js:382-436
1

Validate Ownership

const reserva = await reservationDAO.getInfoReservationByIdReservation(reservationId);
if (!reserva || reserva.id_customer !== userId) {
    return res.status(403).json({ message: 'Not your reservation' });
}
2

Lock Entry Date if EN CURSO

const finalEntryDate = (reserva.status === 'EN CURSO') 
    ? reserva.entry_date  // Use existing date
    : new Date(entry_date); // Allow change
3

Recalculate Price (Server-side)

const newTotalPrice = pricingService.calculateTotalPrice(
    finalEntryDate,
    finalExitDate,
    reserva.type,
    parseInt(id_main_service),
    parsedExtras
);
Never trust client-submitted prices! Always recalculate on server.
4

Update via Transaction

await reservationDAO.updateReservationTransaction(reservationId, {
    entry_date: finalEntryDate,
    exit_date: finalExitDate,
    id_main_service: parseInt(id_main_service),
    total_price: newTotalPrice,
    additional_services: parsedExtras,
    // Keep vehicle/spot unchanged
    brand: reserva.brand,
    model: reserva.model,
    color: reserva.color,
    vehicle_type: reserva.type,
    cod_parking_spot: reserva.cod_parking_spot
});

Account Settings

Users can edit two categories of information via tabbed modal:

Personal Data Tab

Fields:
  • Full Name
  • Email
  • Phone Number
Endpoint: PUT /users/profile/update
Controller: authController.js:281-305
const { nombre, email, telefono } = req.body;

await customerDAO.updateCustomerData(userId, nombre, email, telefono);

// Update session to reflect changes immediately
req.session.user.nombre = nombre;
req.session.user.email = email;
req.session.user.numero = telefono;

req.session.save(() => {
    res.json({ success: true, message: 'Data updated successfully' });
});
Session is updated so the navigation bar immediately reflects the new name without re-login.

Password Tab

Fields:
  • Current Password (verification)
  • New Password
  • Confirm New Password
Endpoint: PUT /users/profile/password
Controller: authController.js:308-345
1

Match Check

if (newPassword !== confirmPassword) {
    return res.status(400).json({
        message: 'New passwords do not match'
    });
}
2

Verify Current Password

const user = await customerDAO.getCustomerById(userId);
const passwordMatch = await bcrypt.compare(currentPassword, user.password_hash);

if (!passwordMatch) {
    return res.status(401).json({
        message: 'Current password is incorrect'
    });
}
3

Hash & Save

const saltRounds = 10;
const newHash = await bcrypt.hash(newPassword, saltRounds);
await customerDAO.updateCustomerPassword(userId, newHash);
Social Login Users: If account was created via Google/Facebook, password change is blocked (password_hash starts with SOCIAL_LOGIN_).

Virtual Garage (Vehicle Management)

The system automatically tracks vehicles associated with the user:

How Vehicles Are Added

  1. During Booking: When user creates a reservation, the vehicle is added to vehicle table and linked via customer_vehicle
  2. Auto-link: Existing vehicles (by license plate) are automatically linked if used in new reservation

Data Structure

-- customer_vehicle table (many-to-many)
CREATE TABLE customer_vehicle (
    id_customer INT REFERENCES customer(id_customer),
    id_vehicle INT REFERENCES vehicle(id_vehicle),
    PRIMARY KEY (id_customer, id_vehicle)
);

Frontend Display

Vehicles are loaded in profile via:
const vehicles = await customerDAO.getVehiclesByCustomerId(userId);
Current Implementation: Vehicles shown in booking form (first vehicle auto-fills). Future versions may show a dedicated “My Vehicles” section.

Booking History

Filtering

History panel shows only completed/cancelled reservations:
// profile.ejs:94-127
<% historyReservations.forEach(function(item) { %>
  <div class="historial-item">
    <div class="historial-fecha">
      <i class="fas fa-calendar-alt"></i>
      <%= new Date(item.entry_date).toLocaleDateString('es-ES') %>
    </div>
    <div class="historial-servicio">
      <i class="fas fa-car"></i>
      <%= item.license_plate %>
    </div>
    <div class="historial-precio">
      <strong><%= parseFloat(item.total_price).toFixed(2) %> €</strong>
    </div>
  </div>
<% }) %>

No Actions Allowed

Unlike active reservations, historical ones are read-only:
  • Cannot cancel (already finished/cancelled)
  • Cannot edit (immutable for accounting)
  • Can view details for records

Security & Permissions

Profile route requires valid session:
// middleware/auth.js
function requireAuth(req, res, next) {
    if (!req.session.user) {
        return res.redirect('/users/login');
    }
    next();
}
All reservation actions verify id_customer:
if (reserva.id_customer !== req.session.user.id) {
    return res.status(403).json({ message: 'Forbidden' });
}
  • PENDIENTE: Can cancel or edit
  • EN CURSO: Can edit (with restrictions) but not cancel
  • FINALIZADA/CANCELADA: View only

UI/UX Features

Scrollable Lists

Active reservations limited to 250px height with overflow scroll (profile.ejs:41)

Status Color Coding

CSS classes:
  • .badge-pendiente (yellow)
  • .badge-en-curso (blue)
  • .badge-finalizada (green)
  • .badge-cancelada (red)

Modal System

3 modals:
  1. Edit Profile (with tabs)
  2. Reservation Details
  3. Edit Reservation
Overlay darkens background, ESC to close

SweetAlert2

Used for:
  • Success messages
  • Error alerts
  • Confirmation dialogs (e.g., “Cancel this booking?”)

Code References

FeatureFileLine Range
Profile Rendercontrollers/authController.js248-278
Update Profilecontrollers/authController.js281-305
Change Passwordcontrollers/authController.js308-345
Cancel Reservationcontrollers/authController.js348-379
Edit Reservationcontrollers/authController.js382-436
View Templateviews/profile.ejsFull file
Client Logicpublic/javascripts/profile.jsModal handlers
Customer DAOmodels/customer-dao.jsCRUD operations
Reservation DAOmodels/reservation-dao.jsgetReservationsByCustomerId() (574-600)

Error Scenarios

// Redirect to login page
if (!req.session.user) {
    return res.redirect('/users/login');
}

Next Steps

Public Booking

How users create reservations

Reservation Lifecycle

State transitions and admin processing

Build docs developers (and LLMs) love