Offer Reservation Page

Full-page reservation template for a concrete offer. Allows users to select dates (check-in/check-out), guest counts (adults, children, babies), rental option (House A / House B / Both), paid extras, and an optional message to the landlord. Includes a live price summary sidebar and a primary Reserve CTA. Designed for the MVP inquiry/approval flow β€” reservation request is sent to the landlord for confirmation. Demonstrates two states: available (form enabled, Reserve active) and unavailable (no option selectable, Reserve disabled, change-dates guidance shown).

Full Page Public / Guest Dates Selector Guest Counts Rental Options A/B/Both Extras Price Summary Reserve CTA

πŸ“‹ Template Context & Composition Notes

This is a standard-context public/guest page reached from the offer detail page ("Reserve" CTA). In the MVP booking flow: user submits the reservation form β†’ landlord receives a request β†’ landlord approves/rejects β†’ user is notified. No payment is collected at this step.

Full Page Preview

Two-column layout: left form column (dates, guests, rental options, extras, message) and right sticky price summary sidebar. Toggle between the available and unavailable states using the button below.

Live Preview
Preview state:

πŸ“Œ Navbar is composed separately β€” see navigations/navbar.html.

Back to offer
<!-- __('offer.title') --> MajΓ³wka w Puszczy

Reservation for:

MajΓ³wka w Puszczy

πŸ—“ Jun 5 – 8, 2026 3 nights

πŸ“… Dates

Check-in date is inclusive (first night). Check-out date is exclusive (departure day, no overnight stay).

πŸ‘₯ Guests

Age 15+

Ages 3 – 14

Under 3 Β· Free Β· Count towards capacity

🏠 Rental Option

Choose one rental unit for your stay. Options unavailable for your dates or guest count are disabled.

✨ Extras

Optional paid add-ons. Extras are priced per stay (added once to your total).

🚲 Bike rental Mountain bike for the duration of your stay
Qty: Γ— 80 PLN / bike
πŸ₯ Breakfast set Fresh bread, cold cuts, cheese, juice & coffee β€” delivered each morning
Qty: Γ— 45 PLN / set / day

πŸ’¬ Message to Landlord (optional)

Supports the inquiry/approval flow β€” your message is included in the reservation request sent to the landlord.

πŸ“‹ Reservation Summary

Dates Jun 5 – 8, 2026
3 nights
Guests 2 adults
Rental option
Lodging
Extras
Total

Final confirmation by landlord required. No payment is collected at this step. You will be notified by email when your reservation is confirmed or rejected.

πŸ‘€ Your Details

By proceeding with the reservation, you agree to provide your email address and phone number, which will be used solely for the purpose of this reservation.

Back to offer

πŸ”’ No payment at this step

Cancellation policy

πŸ“Œ Footer is composed separately β€” see navigations/footer.html.

Copy-Ready Snippet

Core HTML structure of the reservation page. Paste into your app template and replace mock values with dynamic data. CSRF token and offer ref are hidden inputs β€” populate server-side. Remove Alpine attributes when wiring to real backend logic.

Page Header (Offer Context + Back)

HTML
<section class="bg-white dark:bg-neutral-900 border-b border-neutral-200 dark:border-neutral-700 py-8">
  <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
    <a href="/offer/{ref}" class="inline-flex items-center gap-2 t-small t-muted hover:text-primary-600 dark:hover:text-primary-400 mb-4">
      <!-- back arrow svg -->
      <!-- __('reservation.back_to_offer') --> Back to offer
    </a>
    <p class="t-small t-muted mb-1"><!-- __('reservation.context_label') --> Reservation for:</p>
    <h1 class="t-h2 mb-2"><!-- offer title --></h1>
    <div class="badge-group">
      <span class="badge badge--muted"><!-- dates --></span>
      <span class="badge badge--muted"><!-- nights --></span>
      <span class="badge badge--overlay"><!-- __('status.available') --> βœ“ Available</span>
    </div>
  </div>
</section>

A) Dates Section

HTML
<div class="bg-white dark:bg-neutral-800 rounded-2xl shadow-sm border border-neutral-200 dark:border-neutral-700 p-6">
  <h2 class="t-h4 mb-5"><!-- __('reservation.dates.heading') --> πŸ“… Dates</h2>
  <div class="form-layout">
    <div class="form-grid">
      <div class="field">
        <label class="field__label field__label--required"><!-- __('reservation.dates.checkin') --> Check-in</label>
        <div class="field__control">
          <input type="date" class="input" name="checkin" required />
        </div>
      </div>
      <div class="field">
        <label class="field__label field__label--required"><!-- __('reservation.dates.checkout') --> Check-out</label>
        <div class="field__control">
          <input type="date" class="input" name="checkout" required />
        </div>
      </div>
    </div>
    <p class="field__hint"><!-- __('reservation.dates.convention_hint') --> Check-in inclusive Β· Check-out exclusive.</p>
  </div>
</div>

B) Guests Section

HTML
<div class="bg-white dark:bg-neutral-800 rounded-2xl shadow-sm border border-neutral-200 dark:border-neutral-700 p-6">
  <h2 class="t-h4 mb-5"><!-- __('reservation.guests.heading') --> πŸ‘₯ Guests</h2>
  <div class="form-layout">
    <div class="grid grid-cols-1 sm:grid-cols-3 gap-4">
      <div class="field">
        <label class="field__label field__label--required"><!-- __('reservation.guests.adults') --> Adults</label>
        <div class="field__control">
          <select class="select" name="guests_adults">
            <option value="1">1</option>
            <option value="2" selected>2</option>
            <!-- ... -->
          </select>
        </div>
        <p class="field__hint"><!-- __('reservation.guests.adults_hint') --> Age 15+</p>
      </div>
      <div class="field">
        <label class="field__label"><!-- __('reservation.guests.children') --> Children</label>
        <div class="field__control">
          <select class="select" name="guests_children">
            <option value="0" selected>0</option>
            <!-- ... -->
          </select>
        </div>
        <p class="field__hint"><!-- __('reservation.guests.children_hint') --> Ages 3–14</p>
      </div>
      <div class="field">
        <label class="field__label"><!-- __('reservation.guests.babies') --> Babies</label>
        <div class="field__control">
          <select class="select" name="guests_babies">
            <option value="0" selected>0</option>
            <!-- ... -->
          </select>
        </div>
        <p class="field__hint"><!-- __('reservation.guests.babies_hint') --> Under 3 Β· Free Β· Count towards capacity</p>
      </div>
    </div>
  </div>
</div>

C) Rental Option (Radio Cards)

HTML
<div class="bg-white dark:bg-neutral-800 rounded-2xl shadow-sm border border-neutral-200 dark:border-neutral-700 p-6">
  <h2 class="t-h4 mb-5"><!-- __('reservation.rental_option.heading') --> 🏠 Rental Option</h2>
  <div class="space-y-3">

    <!-- Available option -->
    <label class="flex items-start gap-4 p-4 rounded-xl border border-neutral-200 dark:border-neutral-700
                  hover:border-primary-300 dark:hover:border-primary-600 cursor-pointer transition-colors">
      <input type="radio" class="radio mt-1 flex-shrink-0" name="rental_option" value="house_a" />
      <!-- Thumbnail: replace src with actual property photo URL -->
      <div class="w-24 h-20 flex-shrink-0 rounded-lg overflow-hidden">
        <img src="/media/images/house-a-thumb.jpg" alt="House A" class="w-full h-full object-cover" loading="lazy" />
      </div>
      <div class="flex-1 min-w-0">
        <div class="flex flex-wrap items-center justify-between gap-2 mb-1">
          <span class="t-body font-semibold"><!-- __('rental_option.house_a.name') --> House A</span>
          <span class="badge badge--overlay"><!-- __('status.available') --> βœ“ Available</span>
        </div>
        <p class="t-small t-muted mb-1"><!-- capacity --> Up to 6 guests Β· 3 bedrooms</p>
        <p class="t-small font-semibold text-primary-700 dark:text-primary-300">450 PLN / night</p>
      </div>
    </label>

    <!-- Unavailable option (disabled state) -->
    <label class="flex items-start gap-4 p-4 rounded-xl border border-neutral-200 dark:border-neutral-700
                  bg-neutral-50 dark:bg-neutral-800/50 opacity-50 cursor-not-allowed">
      <input type="radio" class="radio mt-1 flex-shrink-0" name="rental_option" value="house_b" disabled />
      <!-- Thumbnail -->
      <div class="w-24 h-20 flex-shrink-0 rounded-lg overflow-hidden">
        <img src="/media/images/house-b-thumb.jpg" alt="House B" class="w-full h-full object-cover" loading="lazy" />
      </div>
      <div class="flex-1 min-w-0">
        <div class="flex flex-wrap items-center justify-between gap-2 mb-1">
          <span class="t-body font-semibold"><!-- __('rental_option.house_b.name') --> House B</span>
          <span class="badge badge--muted"><!-- __('status.unavailable') --> βœ— Not available</span>
        </div>
        <p class="t-small t-muted mb-1">Up to 4 guests Β· 2 bedrooms</p>
        <p class="t-small font-semibold text-primary-700 dark:text-primary-300">390 PLN / night</p>
      </div>
    </label>

  </div>
</div>

D) Extras Section

HTML
<div class="bg-white dark:bg-neutral-800 rounded-2xl shadow-sm border border-neutral-200 dark:border-neutral-700 p-6">
  <h2 class="t-h4 mb-2"><!-- __('reservation.extras.heading') --> ✨ Extras</h2>
  <p class="t-small t-muted mb-5"><!-- __('reservation.extras.hint') --> Extras are priced per stay.</p>
  <div class="form-layout">

    <!-- Simple extra (no quantity) -->
    <div class="field">
      <div class="field__control">
        <label class="form-field__option">
          <input type="checkbox" class="checkbox" name="extra_firepit" value="1" />
          <span class="flex-1">
            <span class="t-body"><!-- __('extra.firepit.name') --> πŸ”₯ Firepit evening package</span>
            <span class="t-small t-muted block mt-0.5">Wood, kindling, fire starter kit</span>
          </span>
          <span class="t-small font-semibold text-primary-700 dark:text-primary-300 ml-4 flex-shrink-0">150 PLN</span>
        </label>
      </div>
    </div>

    <!-- Extra with quantity (Alpine x-data on the field wrapper) -->
    <div class="field" x-data="{ qty: 0 }">
      <div class="field__control">
        <div class="form-field__option items-start">
          <input
            type="checkbox"
            class="checkbox mt-0.5 flex-shrink-0"
            name="extra_breakfast"
            :value="qty > 0 ? qty : ''"
            :checked="qty > 0"
            @change="qty = $event.target.checked ? 1 : 0"
          />
          <span class="flex-1">
            <span class="t-body"><!-- __('extra.breakfast.name') --> πŸ₯ Breakfast set</span>
            <span class="t-small t-muted block mt-0.5">Fresh bread, cold cuts, cheese, juice & coffee</span>
            <!-- Quantity stepper β€” shown when checked -->
            <div class="flex items-center gap-2 mt-2" x-show="qty > 0" x-cloak>
              <span class="t-small t-muted"><!-- __('extra.qty.label') --> Qty:</span>
              <button type="button" class="btn btn--secondary btn--sm"
                style="padding: 0 0.5rem; min-width: 2rem;"
                @click="qty = Math.max(1, qty - 1)">βˆ’</button>
              <span class="t-body font-semibold w-5 text-center" x-text="qty"></span>
              <button type="button" class="btn btn--secondary btn--sm"
                style="padding: 0 0.5rem; min-width: 2rem;"
                @click="qty = Math.min(20, qty + 1)">+</button>
              <span class="t-small t-muted"><!-- __('extra.breakfast.unit_hint') --> Γ— 45 PLN / set / day</span>
            </div>
          </span>
          <span class="t-small font-semibold text-primary-700 dark:text-primary-300 ml-4 flex-shrink-0">
            <template x-if="qty > 0"><span x-text="(qty * 45 * nights) + ' PLN'"></span></template>
            <template x-if="qty === 0"><span>45 PLN / set / day</span></template>
          </span>
        </div>
      </div>
    </div>

  </div>
</div>

Price Summary Sidebar

HTML
<div class="bg-white dark:bg-neutral-800 rounded-2xl shadow-sm border border-neutral-200 dark:border-neutral-700 p-6 lg:sticky lg:top-6">
  <h2 class="t-h4 mb-5"><!-- __('reservation.summary.heading') --> πŸ“‹ Reservation Summary</h2>

  <!-- Summary rows -->
  <div class="flex justify-between items-start py-3 border-b border-neutral-100 dark:border-neutral-700">
    <span class="t-small t-muted"><!-- __('reservation.summary.dates') --> Dates</span>
    <span class="t-small text-right">Jun 5–8, 2026<br><span class="t-muted">3 nights</span></span>
  </div>
  <div class="flex justify-between items-center py-3 border-b border-neutral-100 dark:border-neutral-700">
    <span class="t-small t-muted"><!-- __('reservation.summary.lodging') --> Lodging</span>
    <span class="t-small font-semibold">1 350 PLN</span>
  </div>
  <div class="flex justify-between items-center py-3 border-b border-neutral-100 dark:border-neutral-700">
    <span class="t-small t-muted"><!-- __('reservation.summary.extras') --> Extras</span>
    <span class="t-small font-semibold">150 PLN</span>
  </div>
  <div class="flex justify-between items-center py-4">
    <span class="t-body font-semibold"><!-- __('reservation.summary.total') --> Total</span>
    <span class="text-xl font-bold text-primary-700 dark:text-primary-300">1 500 PLN</span>
  </div>

  <p class="field__hint mb-5"><!-- __('reservation.summary.disclaimer') -->
    Final confirmation by landlord required. No payment collected at this step.
  </p>

  <!-- Primary CTA -->
  <button type="submit" class="btn btn--primary btn--full">
    <!-- __('reservation.cta_reserve') --> Reserve
  </button>

  <!-- Secondary -->
  <a href="#" class="btn btn--secondary btn--full mt-2">
    <!-- __('reservation.cta_back_to_offer') --> Back to offer
  </a>

  <!-- Reassurance -->
  <p class="t-xs t-muted text-center mt-4"><!-- __('reservation.reassurance.no_payment') --> πŸ”’ No payment at this step</p>
  <p class="t-xs t-muted text-center mt-1">
    <a href="#" class="underline hover:text-primary-600 dark:hover:text-primary-400">
      <!-- __('reservation.reassurance.cancellation_link') --> Cancellation policy
    </a>
  </p>
</div>

Usage Notes

Intended use

This page is the reservation step reached from the Offer Detail page. In the MVP flow, submitting this form sends a reservation request to the landlord for approval β€” no payment is collected. The user receives email confirmation when the landlord approves or rejects.

Required inputs

  • Dates β€” check-in (inclusive) and check-out (exclusive); both required.
  • Guest counts β€” adults (required), children and babies (optional, default 0). Babies are free and count towards capacity.
  • Rental option β€” exactly one of: House A, House B, or Both; required. Availability is determined server-side by dates + guest count.

Optional inputs

  • Extras β€” zero or more paid add-ons; priced per stay at MVP.
  • Message β€” free-text note to the landlord; included in the reservation request.

Hidden inputs (server-side)

  • csrf_token β€” CSRF protection token.
  • offer_ref β€” reference code of the offer being reserved; passed from Offer Detail page.

States

  • Available β€” one or more rental options are selectable; Reserve button is active; price summary shows total.
  • Unavailable β€” no rental option is available for the selected dates/guests; all options are disabled; Reserve button is disabled; guidance banner shown with change-dates / reduce-guests / browse-offers CTAs.

Translation keys (i18n)

All visible labels include <!-- __('key') --> comments for PL/EN localisation. Key prefix: reservation.*, rental_option.*, extra.*, status.*.