diff --git a/src/lib/router-helpers.ts b/src/lib/router-helpers.ts
index 84bec63..f80f4ab 100644
--- a/src/lib/router-helpers.ts
+++ b/src/lib/router-helpers.ts
@@ -77,16 +77,3 @@ export const catchAllRoute: RouteRecordRaw = {
path: '/:pathMatch(.*)*',
redirect: '/',
}
-
-/**
- * Where to send the user after a successful login. Honours a `?redirect=`
- * query param so a page can bounce an anonymous user through /login and
- * back (e.g. chatelet's Book button). Only same-origin absolute paths are
- * accepted — anything else (external URLs, protocol-relative `//host`)
- * falls back to home.
- */
-export function postLoginTarget(redirect: unknown): string {
- if (typeof redirect !== 'string') return '/'
- if (!redirect.startsWith('/') || redirect.startsWith('//')) return '/'
- return redirect
-}
diff --git a/src/modules/chatelet/components/BookRoomDialog.vue b/src/modules/chatelet/components/BookRoomDialog.vue
deleted file mode 100644
index 61381ff..0000000
--- a/src/modules/chatelet/components/BookRoomDialog.vue
+++ /dev/null
@@ -1,353 +0,0 @@
-
-
-
-
-
diff --git a/src/modules/chatelet/composables/useBookingFlow.ts b/src/modules/chatelet/composables/useBookingFlow.ts
deleted file mode 100644
index ea1d8af..0000000
--- a/src/modules/chatelet/composables/useBookingFlow.ts
+++ /dev/null
@@ -1,150 +0,0 @@
-import { computed, onUnmounted, ref } from 'vue'
-import { toast } from 'vue-sonner'
-import { SERVICE_TOKENS, tryInjectService } from '@/core/di-container'
-import type { PaymentService } from '@/core/services/PaymentService'
-import type { ChateletApiService } from '../services/ChateletApiService'
-import { LOST_BOOKING_STATUSES, type BookingQuote, type BookingRequest, type BookingStatus } from '../types/booking'
-
-const POLL_MS = 2000
-
-/**
- * Drives one booking from request → invoice → paid. Modeled on events'
- * useTicketPurchase: the backend holds the dates and returns a bolt11;
- * this composable renders it (QR / copy / open-in-wallet / pay from the
- * LNbits wallet) and polls the booking until the extension's paid-invoice
- * listener flips it to `confirmed` — or the hold lapses (`expired`).
- */
-export function useBookingFlow() {
- // DI, never a direct import (workspace rule).
- const api = tryInjectService(SERVICE_TOKENS.CHATELET_API)
- const paymentService = tryInjectService(SERVICE_TOKENS.PAYMENT_SERVICE)
-
- const quote = ref(null)
- const qrCode = ref(null)
- const isRequesting = ref(false)
- const isPaymentPending = ref(false)
- const isPaid = ref(false)
- /** Set when the hold is gone (expired / declined / cancelled) before payment. */
- const lostStatus = ref(null)
- const copiedInvoice = ref(false)
-
- let pollTimer: ReturnType | null = null
-
- const paymentRequest = computed(() => quote.value?.payment_request ?? '')
- const booking = computed(() => quote.value?.booking ?? null)
-
- const userWallets = computed(() => paymentService?.userWallets ?? [])
- const hasWalletWithBalance = computed(() => paymentService?.hasWalletWithBalance ?? false)
- const isPayingWithWallet = computed(() => paymentService?.isProcessingPayment.value ?? false)
-
- /** Hold the dates and get the invoice; starts the settlement poll. */
- async function request(data: BookingRequest): Promise {
- if (!api) return null
- isRequesting.value = true
- try {
- const q = await api.requestBooking(data)
- quote.value = q
- try {
- qrCode.value = paymentService ? await paymentService.generateQRCode(q.payment_request) : null
- } catch (err) {
- console.error('Error generating QR code:', err)
- }
- startPolling(q.booking.id)
- return q
- } finally {
- isRequesting.value = false
- }
- }
-
- function startPolling(bookingId: string) {
- stopPolling()
- isPaymentPending.value = true
-
- const check = async () => {
- try {
- const b = await api?.getPublicBooking(bookingId)
- if (!b) return
- if (quote.value) quote.value = { ...quote.value, booking: { ...quote.value.booking, ...b } }
- if (b.status === 'confirmed' || b.status === 'checked_in' || b.status === 'completed') {
- isPaid.value = true
- stopPolling()
- } else if (LOST_BOOKING_STATUSES.has(b.status)) {
- lostStatus.value = b.status
- stopPolling()
- }
- } catch (err) {
- // Transient network errors just mean "try again next tick".
- console.error('Error checking booking status:', err)
- }
- }
-
- void check()
- pollTimer = setInterval(check, POLL_MS)
- }
-
- function stopPolling() {
- if (pollTimer) {
- clearInterval(pollTimer)
- pollTimer = null
- }
- isPaymentPending.value = false
- }
-
- async function copyInvoice() {
- if (!paymentRequest.value) return
- try {
- await navigator.clipboard.writeText(paymentRequest.value)
- copiedInvoice.value = true
- setTimeout(() => (copiedInvoice.value = false), 1500)
- } catch {
- // Insecure context / old browser — "Open in wallet" still works.
- }
- }
-
- function openExternalWallet() {
- if (paymentRequest.value) paymentService?.openExternalWallet(paymentRequest.value)
- }
-
- /** Pay from the logged-in user's LNbits wallet; the poll picks up settlement. */
- async function payWithWallet() {
- if (!paymentService || !paymentRequest.value) return
- try {
- await paymentService.payWithWallet(paymentRequest.value, booking.value?.deposit_sat, {
- showToast: false,
- })
- } catch (err) {
- toast.error(err instanceof Error ? err.message : 'Wallet payment failed')
- }
- }
-
- function reset() {
- stopPolling()
- quote.value = null
- qrCode.value = null
- isPaid.value = false
- lostStatus.value = null
- copiedInvoice.value = false
- }
-
- onUnmounted(stopPolling)
-
- return {
- quote,
- booking,
- paymentRequest,
- qrCode,
- isRequesting,
- isPaymentPending,
- isPaid,
- lostStatus,
- copiedInvoice,
- userWallets,
- hasWalletWithBalance,
- isPayingWithWallet,
- request,
- copyInvoice,
- openExternalWallet,
- payWithWallet,
- reset,
- }
-}
diff --git a/src/modules/chatelet/index.ts b/src/modules/chatelet/index.ts
index 8980b69..60653c9 100644
--- a/src/modules/chatelet/index.ts
+++ b/src/modules/chatelet/index.ts
@@ -8,7 +8,7 @@ export interface ChateletModuleConfig {
/**
* Chatelet module — guest booking UI for the LNbits `chatelet` room-rentals
- * extension: discovery, availability and Lightning-paid booking over HTTP.
+ * extension. Slice 1: read-only discovery + availability over HTTP.
*/
export const chateletModule = createModulePlugin({
name: 'chatelet',
@@ -52,4 +52,3 @@ export const chateletModule = createModulePlugin({
export default chateletModule
export type { Room, AvailabilityResult } from './types/room'
-export type { Booking, BookingQuote, BookingRequest, BookingStatus } from './types/booking'
diff --git a/src/modules/chatelet/services/ChateletApiService.ts b/src/modules/chatelet/services/ChateletApiService.ts
index 2be1400..015252a 100644
--- a/src/modules/chatelet/services/ChateletApiService.ts
+++ b/src/modules/chatelet/services/ChateletApiService.ts
@@ -1,5 +1,4 @@
import type { AvailabilityResult, Room } from '../types/room'
-import type { BookingQuote, BookingRequest, PublicBooking } from '../types/booking'
export interface ChateletApiConfig {
baseUrl: string
@@ -10,8 +9,8 @@ export interface ChateletApiConfig {
* HTTP client for the LNbits `chatelet` extension's guest surface.
*
* Plain config-wrapper (same pattern as events' TicketApiService) — not a
- * BaseService. Guest browsing, availability, the booking request and the
- * booking read-back are all keyless public endpoints.
+ * BaseService. Slice 1 is read-only guest browsing over public endpoints;
+ * booking (POST /bookings) + status polling arrive in slice 2.
*
* Transport note: this is HTTP-for-now. The extension also exposes a
* kind-21000 Nostr-RPC surface (chatelet_room_list / _availability / …), but
@@ -42,28 +41,6 @@ export class ChateletApiService {
})
}
- /**
- * Hold the dates and get the invoice that confirms them. The backend
- * quotes the amount (canonical `booking.deposit_sat`) — the guest never
- * recomputes what they owe. 502 = invoice creation failed server-side.
- */
- async requestBooking(data: BookingRequest): Promise {
- return this.request('/chatelet/api/v1/bookings', {
- method: 'POST',
- headers: { 'content-type': 'application/json' },
- body: JSON.stringify(data),
- })
- }
-
- /**
- * Keyless read-back of the guest's own booking — what the invoice screen
- * polls. `confirmed` means the extension's paid-invoice listener has
- * run; `expired` / `declined` mean the hold is gone.
- */
- async getPublicBooking(bookingId: string): Promise {
- return this.request(`/chatelet/api/v1/public/bookings/${bookingId}`, { method: 'GET' })
- }
-
private async request(path: string, init: RequestInit = {}): Promise {
const headers: Record = {
accept: 'application/json',
diff --git a/src/modules/chatelet/types/booking.ts b/src/modules/chatelet/types/booking.ts
deleted file mode 100644
index 83c0831..0000000
--- a/src/modules/chatelet/types/booking.ts
+++ /dev/null
@@ -1,78 +0,0 @@
-/**
- * Booking wire types — mirror `models.py` in the LNbits `chatelet`
- * extension (BookingRequestData / Booking / BookingQuote). Field names are
- * kept as the backend spells them so the JSON maps 1:1.
- */
-
-export type BookingStatus =
- | 'held'
- | 'awaiting_payment'
- | 'confirmed'
- | 'declined'
- | 'cancelled'
- | 'expired'
- | (string & {})
-
-/** What a guest sends to `POST /chatelet/api/v1/bookings`. */
-export interface BookingRequest {
- room_id: string
- /** Hex pubkey of the guest — the identity the check-in DM goes to. */
- guest_pubkey: string
- /** YYYY-MM-DD, inclusive. */
- check_in: string
- /** YYYY-MM-DD, exclusive. */
- check_out: string
- num_guests: number
- guest_contact?: string
- message?: string
-}
-
-export interface Booking {
- id: string
- room_id: string
- guest_pubkey: string
- guest_contact: string | null
- check_in: string
- check_out: string
- nights: number
- num_guests: number
- /** Snapshot of the room's price currency. */
- currency: string
- /** nights × room price, display only. */
- price_fiat: number
- /** Canonical total due, in sats. */
- amount_sat: number
- /** Portion invoiced now (== amount_sat at 100 % prepay). */
- deposit_sat: number
- status: BookingStatus
- payment_hash: string | null
- /** Hold expiry while held / awaiting_payment; null once confirmed. */
- expires_at: string | null
- created_at: string
- updated_at: string
-}
-
-/** Response to a booking request: the held booking + the bolt11 that confirms it. */
-export interface BookingQuote {
- booking: Booking
- payment_request: string
- payment_hash: string
-}
-
-/**
- * Keyless read-back of the guest's own booking
- * (`GET /chatelet/api/v1/public/bookings/{id}`, chatelet ≥ v0.4.0): the
- * Booking minus the guest's pubkey/contact and the Lightning/Nostr plumbing.
- * The booking id from the quote is the capability.
- */
-export type PublicBooking = Omit<
- Booking,
- 'guest_pubkey' | 'guest_contact' | 'payment_hash'
->
-
-/** Statuses that mean the hold is gone and the invoice will never confirm. */
-export const LOST_BOOKING_STATUSES: ReadonlySet = new Set([
- 'expired',
- 'declined',
- 'cancelled',
-])
diff --git a/src/modules/chatelet/views/ChateletDetailPage.vue b/src/modules/chatelet/views/ChateletDetailPage.vue
index ae43195..2000bdb 100644
--- a/src/modules/chatelet/views/ChateletDetailPage.vue
+++ b/src/modules/chatelet/views/ChateletDetailPage.vue
@@ -1,6 +1,6 @@
@@ -186,22 +138,12 @@ watch(showBooking, (open) => {
(≈ {{ result.quote_fiat }} {{ result.currency }})
-
+
Not available for those dates.
-
-
diff --git a/src/pages/Login.vue b/src/pages/Login.vue
index 7a440c7..ff89413 100644
--- a/src/pages/Login.vue
+++ b/src/pages/Login.vue
@@ -111,8 +111,7 @@