# Frontend Contract Changes — 2026-08

This document lists every API change that affects the frontend, shipped on the `dev` branch in August 2026. Implement them in this order — item 1 is the only hard breaking change.

## 1. Units: `base_price` renamed to `cost` (BREAKING)

`base_price` no longer exists anywhere — not in requests, not in responses.

- **Unit create/edit forms**: rename the field to `cost` and relabel it — it is the owner's cost per night (what the owner pays, e.g. for sub-rented units), never shown as a selling price.
- **`offer_price` is now REQUIRED** on unit create (single and bulk). It is the nightly selling price customers pay. Validate it as required before submit.
- **Reading prices**: use `offer_price` for anything customer-facing; `cost` only where you show owner cost.
- **Price filters/sorts** (`min_price`, `max_price`, `price_asc`, `price_desc`) now match `offer_price` only.

```jsonc
// POST /owner/units (and bulk) — required fields now:
{
  "cost": 40,          // owner's cost per night
  "offer_price": 90    // selling price per night (REQUIRED)
}

// Unit response:
{ "id": 1, "cost": "40.00", "offer_price": "90.00", ... }
```

## 2. Live price quote (NEW — wire into the booking form)

`POST /api/v1/customer/reservations/price-quote` (customer auth).

The same quote is available to owners and employees for the manual (on-arrival) flow at `POST /api/v1/reservations/price-quote` — identical request and response.

Call it debounced (~300–500 ms) whenever unit, dates, occasion, or promo code change:

```jsonc
// Request
{
  "unit_id": 1,
  "check_in_date": "2026-08-20",
  "check_out_date": "2026-08-23",
  "occasion_id": null,
  "promo_code": "SAVE10"
}

// Response 200
{
  "data": {
    "unit_id": 1,
    "check_in_date": "2026-08-20",
    "check_out_date": "2026-08-23",
    "nights": 3,
    "occasion_nights": 0,
    "price_per_night": 90,
    "occasion_price": null,
    "original_subtotal": 270,   // before any discounts
    "discount_amount": 0,       // discount templates
    "subtotal": 270,            // after templates, before promo
    "promo_code": {             // null when no code was sent
      "code": "SAVE10",
      "valid": true,            // false = show "invalid code", total stays no-promo
      "discount_type": "percentage",
      "discount_value": 10,
      "discount_amount": 27
    },
    "total": 243,               // ← the number to display
    "currency": { "code": "USD", "symbol": "$" }
  }
}
```

Notes:

- Invalid/expired promo codes do NOT fail the request — check `promo_code.valid` and show a hint; `total` remains the no-promo price.
- This endpoint never creates or blocks anything.

## 3. Reservation responses: new fields

Every single-reservation response (create, show, update, check-in/out) now includes:

```jsonc
{
  "price": "90.00",                                  // booked price per night
  "currency": { "code": "LYD", "symbol": "د.ل" },
  "photo": "https://{domain}/api/v1/media/3/document" // or null
}
```

- `photo` is the guest's ID document. It is an **authenticated** URL — fetch it with the `Authorization: Bearer` header (blob fetch, not a bare `<img src>`). Access is restricted to the booking customer, the owner, assigned employees, and admins.
- The ID document is **updatable**: `PUT /api/v1/customer/reservations/{id}` (and the owner/employee reservation update) accept an optional `photo` file that replaces the document. Because HTML/multipart forms can't use PUT, send `POST` with `_method=PUT` plus the multipart fields. Omitting `photo` keeps the existing document.

## 4. Revenue screens: new `profit` field

`GET /owner/revenue/summary`, `/owner/revenue/by-building`, `/owner/revenue/by-unit` now include:

```jsonc
"profit": 150  // final reservation price (after discounts/promo) − cost × nights
```

## 5. Units: Wi-Fi password + Buildings: client services numbers (NEW)

- **Wi-Fi password is per UNIT** — `wifi_password` on unit create/edit (`POST /api/v1/owner/units`, `PUT /api/v1/owner/units/{id}`, bulk: `POST /api/v1/buildings/{building}/units/bulk`). String, max 255, optional; stored encrypted server-side. Included in unit responses only for users who manage the unit.
- **Client services numbers are per BUILDING** — `client_services_numbers` on building create/edit (`POST /api/v1/buildings`, `PUT /api/v1/buildings/{id}`): array of WhatsApp-format numbers (10–15 digits, optional leading `+`). In multipart forms send `client_services_numbers[0]`, `[1]`, etc.
- **Visibility**: these keys appear in responses ONLY when the logged-in user manages that unit/building (owner, assigned employee, admin). The keys are absent for everyone else — check for key presence, don't assume null.
- Guests receive both values automatically in the check-in WhatsApp message; no frontend action needed there.

## 6. WhatsApp numbers: canonical format (non-breaking)

The API accepts numbers with or without a leading `+` everywhere (register, login, verify, resend, reset) and normalizes them. Responses always return numbers **without** the `+`. Don't rely on the `+` when displaying or comparing numbers.

## 7. Photo uploads (non-breaking)

- Accepted types everywhere (buildings, units, reservation ID photos, owner logo): **jpg, jpeg, png, webp, heif, heic, gif, bmp, avif**.
- App-side size cap: 5 MB for reservation ID photos and logos, 20 MB for building/unit photos. Larger files are rejected with a clear message (`errors.photo` / `errors.photos`) — surface it directly.
- Uploads are compressed server-side after validation.

## 8. Owner verification journey (clarification)

Owner approval and user verification are now fully independent:

1. Register → 2. OTP verify (`users.is_verified`) → 3. Admin approves (`owners.status = active`) → full access.

An admin-approved owner who hasn't done OTP is still blocked by the `verified` middleware; on login they get the `needs_verification` 403 response with a fresh OTP sent — handle that state (it already exists).

## 9. Booking time rules (CHANGED)

- The 22:00 same-day online cutoff is **gone** — today is bookable at any hour, on any flow.
- **Yesterday is now a valid check-in date** (for late-night arrivals whose night belongs to yesterday). Applies to online booking, manual/on-arrival, price quote, promo preview, and reservation updates. Older dates are still rejected with a 422 on `check_in_date`.
- Date pickers should enable yesterday, today, and future dates — and nothing before yesterday.
