feat(chatelet): stay calendar, room gallery + house rules, My bookings #156

Merged
padreug merged 4 commits from feat/chatelet-my-bookings into dev 2026-09-21 09:12:07 +00:00
2 changed files with 52 additions and 4 deletions
Showing only changes of commit fb0478c028 - Show all commits

feat(chatelet): photo gallery and house rules on the room page

Swap the single hero image for the shared ImageViewer (thumbnails,
lightbox and cycle controls when a room has more than one photo, as the
marketplace product page does) and add a 'Things to know' card: check-in
from / check-out by, guest cap, minimum stay and the cancellation policy.
The rules come from the room owner's operator settings (chatelet ≥ v0.5.0)
so the card only renders when the extension sends them.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Padreug 2026-09-16 12:17:43 +02:00

View file

@ -2,6 +2,13 @@
// Note: operator-private fields (wallet, checkin_instructions) are stripped
// server-side by public_room_dict and never reach the client.
/** The room owner's rules, from their operator settings (chatelet ≥ v0.5.0). */
export interface HouseRules {
checkin_time: string
checkout_time: string
cancellation_policy: string
}
export interface Room {
id: string
title: string
@ -19,6 +26,10 @@ export interface Room {
listing_event_id: string | null
created_at: string
updated_at: string
/** Absent on chatelet < v0.5.0. */
house_rules?: HouseRules
/** Rails the owner accepts for this room: 'lightning' and maybe 'fiat'. Absent on < v0.5.0. */
payment_methods?: string[]
}
export interface AvailabilityResult {

View file

@ -3,10 +3,11 @@ import { computed, onMounted, ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { toast } from 'vue-sonner'
import { watchDebounced } from '@vueuse/core'
import { MapPin, Users } from 'lucide-vue-next'
import { Clock, MapPin, Moon, Users } from 'lucide-vue-next'
import { Card, CardContent } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { ImageViewer } from '@/components/ui/image'
import { formatSats } from '@/lib/utils/formatting'
import { useAuth } from '@/composables/useAuthService'
import { useChatelet } from '../composables/useChatelet'
@ -129,11 +130,18 @@ watch(showBooking, (open) => {
<div v-else-if="!room" class="text-muted-foreground py-12">Room not found.</div>
<div v-else class="space-y-4">
<img
<!-- Gallery: thumbnails + lightbox when there is more than one photo
(same ImageViewer the marketplace product page uses). -->
<ImageViewer
v-if="room.images?.length"
:src="room.images[0]"
:images="room.images"
:alt="room.title"
class="w-full h-56 object-cover rounded-lg"
container-class="aspect-[4/3] sm:aspect-video rounded-lg overflow-hidden bg-muted"
image-class="w-full h-full object-cover"
:show-thumbnails="room.images.length > 1"
:show-lightbox="true"
:show-badge="room.images.length > 1"
:show-cycle-controls="room.images.length > 1"
/>
<div>
@ -161,6 +169,35 @@ watch(showBooking, (open) => {
{{ room.description }}
</p>
<!-- House rules come from the room owner's operator settings; older
extensions don't send them, so the card is conditional. -->
<Card v-if="room.house_rules">
<CardContent class="p-4 space-y-2">
<h2 class="font-semibold text-foreground">Things to know</h2>
<ul class="text-sm text-muted-foreground space-y-1">
<li class="flex items-center gap-2">
<Clock class="h-4 w-4 shrink-0" />
Check-in from {{ room.house_rules.checkin_time }} · check-out by
{{ room.house_rules.checkout_time }}
</li>
<li class="flex items-center gap-2">
<Users class="h-4 w-4 shrink-0" />
Up to {{ room.max_guests }} guest{{ room.max_guests === 1 ? '' : 's' }}
</li>
<li class="flex items-center gap-2">
<Moon class="h-4 w-4 shrink-0" />
Minimum stay {{ room.min_nights }} night{{ room.min_nights === 1 ? '' : 's' }}
</li>
</ul>
<div v-if="room.house_rules.cancellation_policy" class="pt-1">
<p class="text-sm font-medium text-foreground">Cancellation policy</p>
<p class="text-sm text-muted-foreground whitespace-pre-line">
{{ room.house_rules.cancellation_policy }}
</p>
</div>
</CardContent>
</Card>
<Card>
<CardContent class="p-4 space-y-4">
<StayDatePicker