@ticketboothapp/booking 0.1.23 → 1.2.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. package/package.json +2 -29
  2. package/src/index.ts +0 -79
  3. package/tsconfig.json +2 -8
  4. package/src/assets/icons/minus.svg +0 -7
  5. package/src/assets/icons/partner-logos/getyourguide.svg +0 -8
  6. package/src/assets/icons/plus.svg +0 -3
  7. package/src/colours.css +0 -23
  8. package/src/components/BookingDetails.module.css +0 -1591
  9. package/src/components/BookingDetails.tsx +0 -2264
  10. package/src/components/BookingWidget.tsx +0 -302
  11. package/src/components/ManageBookingView.tsx +0 -437
  12. package/src/components/PhoneInputWithCountry.module.css +0 -131
  13. package/src/components/PhoneInputWithCountry.tsx +0 -44
  14. package/src/components/PickupLocationDialog.module.css +0 -360
  15. package/src/components/PickupLocationDialog.tsx +0 -357
  16. package/src/components/PostBookingDependentAddOnUpsell.module.css +0 -174
  17. package/src/components/PostBookingDependentAddOnUpsell.tsx +0 -407
  18. package/src/components/booking/AddOnsSection.module.css +0 -10
  19. package/src/components/booking/AddOnsSection.tsx +0 -184
  20. package/src/components/booking/AdminPaymentChoiceModal.tsx +0 -98
  21. package/src/components/booking/BookingDialog.module.css +0 -643
  22. package/src/components/booking/BookingDialog.tsx +0 -356
  23. package/src/components/booking/BookingFlow.tsx +0 -4385
  24. package/src/components/booking/BookingFlowCollage.module.css +0 -148
  25. package/src/components/booking/BookingFlowCollage.tsx +0 -184
  26. package/src/components/booking/BookingFlowPlaceholder.module.css +0 -27
  27. package/src/components/booking/BookingFlowPlaceholder.tsx +0 -25
  28. package/src/components/booking/BookingFlowPreview.tsx +0 -51
  29. package/src/components/booking/BookingProductGrid.module.css +0 -359
  30. package/src/components/booking/BookingProductGrid.tsx +0 -497
  31. package/src/components/booking/Calendar.module.css +0 -616
  32. package/src/components/booking/Calendar.tsx +0 -1123
  33. package/src/components/booking/CancellationPolicySelector.module.css +0 -124
  34. package/src/components/booking/CancellationPolicySelector.tsx +0 -142
  35. package/src/components/booking/ChangeBookingDialog.tsx +0 -562
  36. package/src/components/booking/CheckoutForm.module.css +0 -244
  37. package/src/components/booking/CheckoutForm.tsx +0 -364
  38. package/src/components/booking/CheckoutModal.tsx +0 -451
  39. package/src/components/booking/CurrencySwitcher.tsx +0 -81
  40. package/src/components/booking/DapFlowCollage.tsx +0 -88
  41. package/src/components/booking/DapTourDescription.tsx +0 -35
  42. package/src/components/booking/DependentAddOnBookingDialog.tsx +0 -1350
  43. package/src/components/booking/DependentAddOnPaymentForm.tsx +0 -124
  44. package/src/components/booking/ErrorBoundary.tsx +0 -63
  45. package/src/components/booking/InfoTooltip.tsx +0 -108
  46. package/src/components/booking/ItineraryBox.module.css +0 -258
  47. package/src/components/booking/ItineraryBox.tsx +0 -550
  48. package/src/components/booking/ItineraryBuilder.tsx +0 -82
  49. package/src/components/booking/ItineraryPlaceholder.module.css +0 -45
  50. package/src/components/booking/ItineraryPlaceholder.tsx +0 -26
  51. package/src/components/booking/MealDrinkAddOnSelector.tsx +0 -338
  52. package/src/components/booking/PickupLocationSelector.module.css +0 -124
  53. package/src/components/booking/PickupLocationSelector.tsx +0 -1566
  54. package/src/components/booking/PickupTimeSelector.module.css +0 -134
  55. package/src/components/booking/PickupTimeSelector.tsx +0 -112
  56. package/src/components/booking/PriceBreakdown.tsx +0 -154
  57. package/src/components/booking/PriceSummary.tsx +0 -234
  58. package/src/components/booking/PrivateShuttleBookingFlow.module.css +0 -357
  59. package/src/components/booking/PrivateShuttleBookingFlow.tsx +0 -2662
  60. package/src/components/booking/PromoCodeInput.module.css +0 -166
  61. package/src/components/booking/PromoCodeInput.tsx +0 -99
  62. package/src/components/booking/ReturnTimeSelector.module.css +0 -173
  63. package/src/components/booking/ReturnTimeSelector.tsx +0 -145
  64. package/src/components/booking/TermsAcceptance.tsx +0 -111
  65. package/src/components/booking/TicketSelector.module.css +0 -164
  66. package/src/components/booking/TicketSelector.tsx +0 -199
  67. package/src/components/booking/TourDescription.module.css +0 -304
  68. package/src/components/booking/TourDescription.tsx +0 -273
  69. package/src/components/booking/booking-flow-ui.ts +0 -38
  70. package/src/components/booking/booking-flow.css +0 -944
  71. package/src/components/button.css +0 -245
  72. package/src/components/button.tsx +0 -152
  73. package/src/components/colorable-svg.tsx +0 -29
  74. package/src/components/image.css +0 -29
  75. package/src/components/image.tsx +0 -113
  76. package/src/components/partner/PartnerBookingPage.module.css +0 -130
  77. package/src/components/partner/PartnerBookingPage.tsx +0 -390
  78. package/src/components/partner/PartnerBookingPageWithBrowserMetadata.tsx +0 -45
  79. package/src/components/product-tag.module.css +0 -30
  80. package/src/components/product-tag.tsx +0 -34
  81. package/src/components/product-theme-pages/image-modal.tsx +0 -248
  82. package/src/components/product-theme-pages/photo-gallery.module.css +0 -200
  83. package/src/components/terms/TermsContent.tsx +0 -178
  84. package/src/components/value-pill.module.css +0 -59
  85. package/src/components/value-pill.tsx +0 -46
  86. package/src/constants/images.ts +0 -556
  87. package/src/constants/pill-values.ts +0 -210
  88. package/src/constants/products.ts +0 -155
  89. package/src/contexts/AvailabilitiesCacheContext.tsx +0 -125
  90. package/src/contexts/BookingAppContext.tsx +0 -134
  91. package/src/contexts/CompanyContext.tsx +0 -70
  92. package/src/data/dap-descriptions/session-couples-families-friends.en.json +0 -61
  93. package/src/data/dap-descriptions/session-elopements.en.json +0 -60
  94. package/src/data/dap-descriptions/session-proposals.en.json +0 -60
  95. package/src/data/product-descriptions/afternoon-delight.en.json +0 -35
  96. package/src/data/product-descriptions/emerald-lake-escape.en.json +0 -68
  97. package/src/data/product-descriptions/lake-louise-adventure.en.json +0 -74
  98. package/src/data/product-descriptions/moraine-lake-adventure.en.json +0 -78
  99. package/src/data/product-descriptions/moraine-lake-sunrise-lake-louise-golden-hour.en.json +0 -65
  100. package/src/data/product-descriptions/moraine-lake-sunrise.en.json +0 -64
  101. package/src/data/product-descriptions/private-tour.en.json +0 -80
  102. package/src/data/product-descriptions/two-lakes-combo.en.json +0 -65
  103. package/src/data/products-config.json +0 -101
  104. package/src/hooks/useBookingSourceMetadataFromLocation.ts +0 -21
  105. package/src/hooks/useIsBookingLaunchLive.ts +0 -49
  106. package/src/lib/analytics.ts +0 -197
  107. package/src/lib/booking/booking-source.ts +0 -51
  108. package/src/lib/booking/checkout-breakdown.ts +0 -69
  109. package/src/lib/booking/correlation-id.ts +0 -46
  110. package/src/lib/booking/i18n/config.ts +0 -21
  111. package/src/lib/booking/i18n/index.tsx +0 -144
  112. package/src/lib/booking/i18n/messages/en.json +0 -236
  113. package/src/lib/booking/i18n/messages/fr.json +0 -236
  114. package/src/lib/booking/itinerary-display.ts +0 -36
  115. package/src/lib/booking/itinerary-labels.ts +0 -70
  116. package/src/lib/booking/location-calculations.ts +0 -43
  117. package/src/lib/booking/location-utils.ts +0 -165
  118. package/src/lib/booking/map-utils.ts +0 -153
  119. package/src/lib/booking/marker-icons.ts +0 -113
  120. package/src/lib/booking/normalize-booking-product-id.ts +0 -21
  121. package/src/lib/booking/pickup-location-types.ts +0 -25
  122. package/src/lib/booking/places-api.ts +0 -154
  123. package/src/lib/booking/pricing.ts +0 -466
  124. package/src/lib/booking/product-option-id.ts +0 -35
  125. package/src/lib/booking/source-metadata.ts +0 -226
  126. package/src/lib/booking/sunday-week.ts +0 -14
  127. package/src/lib/booking/theme.ts +0 -83
  128. package/src/lib/booking/trace-context.ts +0 -62
  129. package/src/lib/booking/utils.ts +0 -9
  130. package/src/lib/booking-api.ts +0 -1793
  131. package/src/lib/booking-constants.ts +0 -23
  132. package/src/lib/booking-ref.ts +0 -13
  133. package/src/lib/booking-types.ts +0 -36
  134. package/src/lib/currency.ts +0 -81
  135. package/src/lib/dap-descriptions.ts +0 -50
  136. package/src/lib/dap-itinerary-preview.ts +0 -315
  137. package/src/lib/dependent-add-on-api.ts +0 -434
  138. package/src/lib/env.ts +0 -96
  139. package/src/lib/firebase.ts +0 -20
  140. package/src/lib/job-application-api.ts +0 -83
  141. package/src/lib/manage-booking-embed-print.ts +0 -16
  142. package/src/lib/manage-booking-post-checkout.ts +0 -68
  143. package/src/lib/photo-dap-config.ts +0 -228
  144. package/src/lib/photo-packages.ts +0 -75
  145. package/src/lib/pickup/map-utils.ts +0 -56
  146. package/src/lib/pickup/marker-icons.ts +0 -19
  147. package/src/lib/product-descriptions.ts +0 -66
  148. package/src/lib/products-config.ts +0 -73
  149. package/src/providers/booking-dialog-provider.tsx +0 -282
  150. package/src/providers/dependent-add-on-dialog-provider.tsx +0 -105
  151. package/src/radius.css +0 -5
  152. package/src/spacing.css +0 -7
  153. package/src/strings/en.json +0 -1774
  154. package/src/strings/es.json +0 -1573
  155. package/src/strings/fr.json +0 -1573
  156. package/src/strings/index.js +0 -23
  157. package/src/text-style.css +0 -56
  158. package/src/utils/currency-converter.ts +0 -101
@@ -1,437 +0,0 @@
1
- 'use client';
2
-
3
- import { useState, useEffect, useLayoutEffect } from 'react';
4
- import { formatBookingRefForDisplay } from '@/lib/booking-ref';
5
- import BookingDetails, { type BookingData } from '@/components/BookingDetails';
6
- import { type Currency, formatCurrencyAmount } from '@/lib/currency';
7
- import { ENV } from '@/lib/env';
8
-
9
- const API_URL = ENV.API_URL;
10
-
11
- /** Provider-only fields merged after public manage lookup (e.g. embedded in provider dashboard). */
12
- export interface StaffBookingAttribution {
13
- reportingSource: string;
14
- partnerBookingPortal: boolean;
15
- partnerId?: string | null;
16
- partnerName?: string | null;
17
- agentId?: string | null;
18
- agentDisplay?: string | null;
19
- commissionRate?: number | null;
20
- commissionBaseAmount?: number | null;
21
- commissionAmount?: number | null;
22
- bookingCurrency: string;
23
- commissionUsesPartnerRate?: boolean;
24
- }
25
-
26
- function staffChannelLabel(source: string): string {
27
- const labels: Record<string, string> = {
28
- DASHBOARD: 'Dashboard',
29
- GYG: 'GetYourGuide',
30
- WEBSITE: 'Website (main site)',
31
- PARTNER_PORTAL: 'Website (main site)',
32
- WEBSITE_PARTNER_PORTAL: 'Partner booking portal',
33
- AFFILIATE: 'Affiliate',
34
- VIATOR: 'Viator',
35
- };
36
- return labels[source] ?? source;
37
- }
38
-
39
- export interface ManageBookingViewProps {
40
- initialRef?: string;
41
- initialLastName?: string;
42
- initialReservationRef?: string;
43
- initialPaymentSuccess?: boolean;
44
- replaceUrl?: (url: string) => void;
45
- onClose?: () => void;
46
- showCloseButton?: boolean;
47
- fetchStaffAttribution?: (bookingReference: string) => Promise<StaffBookingAttribution | null>;
48
- }
49
-
50
- export function ManageBookingView({
51
- initialRef: initialRefProp = '',
52
- initialLastName: initialLastNameProp = '',
53
- initialReservationRef: initialReservationRefProp = '',
54
- initialPaymentSuccess = false,
55
- replaceUrl,
56
- onClose,
57
- showCloseButton = true,
58
- fetchStaffAttribution,
59
- }: ManageBookingViewProps) {
60
- const [bookingReference, setBookingReference] = useState(initialRefProp);
61
- const [lastName, setLastName] = useState(initialLastNameProp);
62
- const [booking, setBooking] = useState<BookingData | null>(null);
63
- const [staffAttribution, setStaffAttribution] = useState<StaffBookingAttribution | null>(null);
64
- const [loading, setLoading] = useState(false);
65
- const [error, setError] = useState('');
66
-
67
- useLayoutEffect(() => {
68
- setBookingReference(initialRefProp ? formatBookingRefForDisplay(initialRefProp) || initialRefProp : initialRefProp);
69
- setLastName(initialLastNameProp);
70
- }, [initialRefProp, initialLastNameProp]);
71
-
72
- const initialRef = initialRefProp.trim();
73
- const initialLastName = initialLastNameProp.trim();
74
- const initialReservationRef = (initialReservationRefProp || '').trim();
75
-
76
- useEffect(() => {
77
- if (!booking?.bookingReference || !fetchStaffAttribution) {
78
- setStaffAttribution(null);
79
- return;
80
- }
81
- let cancelled = false;
82
- const ref = formatBookingRefForDisplay(booking.bookingReference) || booking.bookingReference;
83
- fetchStaffAttribution(ref)
84
- .then((a) => {
85
- if (!cancelled) setStaffAttribution(a);
86
- })
87
- .catch(() => {
88
- if (!cancelled) setStaffAttribution(null);
89
- });
90
- return () => {
91
- cancelled = true;
92
- };
93
- }, [booking?.bookingReference, fetchStaffAttribution]);
94
-
95
- useEffect(() => {
96
- if (!initialRef || !initialLastName) return;
97
- let cancelled = false;
98
- const POLL_DELAY_MS = 2000;
99
- const MAX_POLL_ATTEMPTS = 3;
100
-
101
- const fetchBooking = async (): Promise<BookingData | null> => {
102
- const response = await fetch(
103
- `${API_URL}/1/public/bookings/${encodeURIComponent(initialRef)}?lastName=${encodeURIComponent(initialLastName)}`,
104
- { method: 'GET', headers: { 'Content-Type': 'application/json' } }
105
- );
106
- if (!response.ok) {
107
- if (response.status === 404) throw new Error('Booking not found or last name does not match');
108
- const err = await response.json();
109
- throw new Error(err.errorMessage || err.error || 'Failed to lookup booking');
110
- }
111
- const data = await response.json();
112
- return data.data as BookingData;
113
- };
114
-
115
- const doLookup = async () => {
116
- setLoading(true);
117
- setError('');
118
- setBooking(null);
119
- try {
120
- let b = await fetchBooking();
121
- if (cancelled) return;
122
- setBooking(b);
123
-
124
- const hasPaymentOwing = (bk: BookingData) => {
125
- const status = bk?.payment?.status;
126
- const balanceAmount = bk?.payment?.plan?.balanceAmount ?? 0;
127
- const depositAmount = bk?.payment?.plan?.depositAmount ?? 0;
128
- return (
129
- (status === 'DEPOSIT_PAID' && balanceAmount > 0) ||
130
- (status === 'AWAITING_PAYMENT' && (depositAmount > 0 || balanceAmount > 0))
131
- );
132
- };
133
-
134
- if (initialPaymentSuccess && b && hasPaymentOwing(b)) {
135
- for (let attempt = 1; attempt < MAX_POLL_ATTEMPTS && !cancelled; attempt++) {
136
- await new Promise((r) => setTimeout(r, POLL_DELAY_MS));
137
- if (cancelled) return;
138
- b = await fetchBooking();
139
- if (cancelled) return;
140
- setBooking(b);
141
- if (!b || !hasPaymentOwing(b)) break;
142
- }
143
- replaceUrl?.(`/manage?ref=${encodeURIComponent(initialRef)}&lastName=${encodeURIComponent(initialLastName)}`);
144
- }
145
- } catch (err) {
146
- if (!cancelled) setError(err instanceof Error ? err.message : 'Failed to lookup booking');
147
- } finally {
148
- if (!cancelled) setLoading(false);
149
- }
150
- };
151
- doLookup();
152
- return () => {
153
- cancelled = true;
154
- };
155
- }, [initialRef, initialLastName, initialPaymentSuccess, replaceUrl]);
156
-
157
- useEffect(() => {
158
- if (!initialReservationRef || !initialLastName || initialRef) return;
159
- let cancelled = false;
160
- const POLL_MS = 1500;
161
- const MAX_MS = 30000;
162
- let attempt = 0;
163
- const doLookup = async () => {
164
- setLoading(true);
165
- setError('');
166
- setBooking(null);
167
- const poll = async (): Promise<void> => {
168
- if (cancelled) return;
169
- try {
170
- const response = await fetch(
171
- `${API_URL}/1/public/bookings/by-reservation?reservationRef=${encodeURIComponent(initialReservationRef)}&lastName=${encodeURIComponent(initialLastName)}`,
172
- { method: 'GET', headers: { 'Content-Type': 'application/json' } }
173
- );
174
- if (cancelled) return;
175
- if (response.ok) {
176
- const data = await response.json();
177
- const b = data.data as BookingData;
178
- if (b?.bookingReference && !cancelled) {
179
- const shortRef = formatBookingRefForDisplay(b.bookingReference);
180
- replaceUrl?.(`/manage?ref=${encodeURIComponent(shortRef)}&lastName=${encodeURIComponent(initialLastName)}`);
181
- setBookingReference(shortRef);
182
- setBooking(b);
183
- setLoading(false);
184
- return;
185
- }
186
- }
187
- } catch {
188
- /* ignore */
189
- }
190
- if (cancelled) return;
191
- attempt += 1;
192
- if (attempt * POLL_MS < MAX_MS) {
193
- setTimeout(poll, POLL_MS);
194
- } else {
195
- if (!cancelled) setError('Booking not found. Enter your booking reference from the confirmation email when it arrives.');
196
- setLoading(false);
197
- }
198
- };
199
- await poll();
200
- };
201
- doLookup();
202
- return () => {
203
- cancelled = true;
204
- };
205
- }, [initialReservationRef, initialLastName, initialRef, replaceUrl]);
206
-
207
- async function handleLookup() {
208
- if (!bookingReference.trim() || !lastName.trim()) {
209
- setError('Please enter both booking reference and last name');
210
- return;
211
- }
212
-
213
- setLoading(true);
214
- setError('');
215
- setBooking(null);
216
-
217
- try {
218
- const response = await fetch(
219
- `${API_URL}/1/public/bookings/${encodeURIComponent(bookingReference.trim())}?lastName=${encodeURIComponent(lastName.trim())}`,
220
- { method: 'GET', headers: { 'Content-Type': 'application/json' } }
221
- );
222
-
223
- if (!response.ok) {
224
- if (response.status === 404) throw new Error('Booking not found or last name does not match');
225
- const err = await response.json();
226
- throw new Error(err.errorMessage || err.error || 'Failed to lookup booking');
227
- }
228
-
229
- const data = await response.json();
230
- setBooking(data.data);
231
- } catch (err) {
232
- setError(err instanceof Error ? err.message : 'Failed to lookup booking');
233
- } finally {
234
- setLoading(false);
235
- }
236
- }
237
-
238
- async function handleRefetch() {
239
- if (!bookingReference.trim() || !lastName.trim()) return;
240
- try {
241
- const response = await fetch(
242
- `${API_URL}/1/public/bookings/${encodeURIComponent(bookingReference.trim())}?lastName=${encodeURIComponent(lastName.trim())}`,
243
- { method: 'GET', headers: { 'Content-Type': 'application/json' } }
244
- );
245
- if (response.ok) {
246
- const data = await response.json();
247
- setBooking(data.data);
248
- }
249
- } catch {
250
- /* ignore */
251
- }
252
- }
253
-
254
- const isEmbed = !!onClose;
255
- const showClose = isEmbed && showCloseButton;
256
- const containerClass = isEmbed ? 'flex flex-col p-4' : 'min-h-screen bg-gradient-to-b from-stone-100 to-stone-200 flex flex-col items-center justify-center gap-4 p-4';
257
- const formOuterClass = isEmbed ? '' : 'min-h-screen bg-gradient-to-b from-stone-100 to-stone-200 flex items-center justify-center p-4';
258
-
259
- if (booking) {
260
- return (
261
- <div className={isEmbed ? 'w-full max-w-2xl' : 'min-h-screen bg-gradient-to-b from-stone-100 to-stone-200'}>
262
- {showClose && (
263
- <div className="flex justify-end mb-2">
264
- <button
265
- type="button"
266
- onClick={onClose}
267
- className="text-sm hover:opacity-80"
268
- style={{ color: 'var(--booking-text-muted)' }}
269
- >
270
- Close
271
- </button>
272
- </div>
273
- )}
274
- {staffAttribution ? (
275
- <div
276
- className="mb-4 rounded-xl border border-stone-200 bg-stone-50 px-4 py-3 text-sm text-stone-800"
277
- role="region"
278
- aria-label="Staff booking attribution"
279
- >
280
- <p className="font-semibold text-stone-900 mb-2">Booking attribution</p>
281
- <p>
282
- <span className="text-stone-600">Channel:</span> {staffChannelLabel(staffAttribution.reportingSource)}
283
- </p>
284
- {staffAttribution.partnerId ? (
285
- <p>
286
- <span className="text-stone-600">Partner:</span>{' '}
287
- {staffAttribution.partnerName
288
- ? `${staffAttribution.partnerName} (${staffAttribution.partnerId})`
289
- : staffAttribution.partnerId}
290
- </p>
291
- ) : null}
292
- {staffAttribution.agentDisplay || staffAttribution.agentId ? (
293
- <p>
294
- <span className="text-stone-600">Agent:</span>{' '}
295
- {staffAttribution.agentDisplay ?? staffAttribution.agentId}
296
- </p>
297
- ) : null}
298
- {staffAttribution.commissionAmount != null && staffAttribution.commissionAmount > 0 ? (
299
- <p>
300
- <span className="text-stone-600">Commission:</span>{' '}
301
- {formatCurrencyAmount(staffAttribution.commissionAmount, staffAttribution.bookingCurrency as Currency)}
302
- {staffAttribution.commissionRate != null && staffAttribution.commissionRate > 0 ? (
303
- <>
304
- {' '}
305
- ({Math.round(staffAttribution.commissionRate * 100)}% of{' '}
306
- {staffAttribution.commissionUsesPartnerRate && staffAttribution.commissionBaseAmount != null
307
- ? `${formatCurrencyAmount(staffAttribution.commissionBaseAmount, staffAttribution.bookingCurrency as Currency)} pre-tax`
308
- : 'booking total'}
309
- )
310
- </>
311
- ) : null}
312
- </p>
313
- ) : null}
314
- </div>
315
- ) : null}
316
- <BookingDetails booking={booking} currency={booking.receipt.currency as Currency} onRefetch={handleRefetch} />
317
- </div>
318
- );
319
- }
320
-
321
- const waitingForBooking = initialReservationRef && initialLastName && !initialRef;
322
- if (waitingForBooking && loading) {
323
- return (
324
- <div className={containerClass}>
325
- <div
326
- className="w-10 h-10 border-2 border-t-transparent rounded-full animate-spin"
327
- style={{ borderColor: 'var(--booking-primary)' }}
328
- aria-hidden
329
- />
330
- <p style={{ color: 'var(--booking-text-muted)' }}>Confirming your booking…</p>
331
- </div>
332
- );
333
- }
334
-
335
- return (
336
- <div className={formOuterClass || undefined}>
337
- <div
338
- className="p-8 max-w-md w-full shadow-xl"
339
- style={{ backgroundColor: 'var(--booking-surface)', borderRadius: 'var(--booking-radius)' }}
340
- >
341
- {isEmbed && (
342
- <div className={`flex justify-between items-center mb-4 ${showClose ? '' : 'justify-start'}`}>
343
- <h1 className="text-2xl font-bold" style={{ color: 'var(--booking-text)' }}>
344
- Manage Your Booking
345
- </h1>
346
- {showClose && (
347
- <button type="button" onClick={onClose} className="text-sm hover:opacity-80" style={{ color: 'var(--booking-text-muted)' }}>
348
- Close
349
- </button>
350
- )}
351
- </div>
352
- )}
353
- {!isEmbed && (
354
- <h1 className="text-2xl font-bold mb-6" style={{ color: 'var(--booking-text)' }}>
355
- Manage Your Booking
356
- </h1>
357
- )}
358
-
359
- <form
360
- onSubmit={(e) => {
361
- e.preventDefault();
362
- handleLookup();
363
- }}
364
- className="space-y-4"
365
- >
366
- <div>
367
- <label htmlFor="manage-booking-ref" className="block text-sm font-medium mb-1" style={{ color: 'var(--booking-text)' }}>
368
- Booking Reference
369
- </label>
370
- <input
371
- id="manage-booking-ref"
372
- type="text"
373
- value={bookingReference}
374
- onChange={(e) => setBookingReference(formatBookingRefForDisplay(e.target.value) || e.target.value)}
375
- className="w-full px-4 py-2 rounded-lg focus:ring-2"
376
- style={{ borderColor: 'var(--booking-border-input)', borderWidth: '1px', borderStyle: 'solid' }}
377
- placeholder="e.g., ABC12345"
378
- required
379
- />
380
- </div>
381
- <div>
382
- <label htmlFor="manage-booking-lastname" className="block text-sm font-medium mb-1" style={{ color: 'var(--booking-text)' }}>
383
- Last Name
384
- </label>
385
- <input
386
- id="manage-booking-lastname"
387
- type="text"
388
- value={lastName}
389
- onChange={(e) => setLastName(e.target.value)}
390
- className="w-full px-4 py-2 rounded-lg focus:ring-2"
391
- style={{ borderColor: 'var(--booking-border-input)', borderWidth: '1px', borderStyle: 'solid' }}
392
- placeholder="As entered at booking"
393
- required
394
- />
395
- </div>
396
- {error && (
397
- <div
398
- className="rounded-lg p-3 text-sm"
399
- style={{
400
- backgroundColor: 'var(--booking-error-bg)',
401
- border: '1px solid var(--booking-error-border)',
402
- color: 'var(--booking-error-text)',
403
- }}
404
- >
405
- {error}
406
- </div>
407
- )}
408
- <button
409
- type="submit"
410
- disabled={loading}
411
- className="w-full py-3 text-white font-semibold rounded-lg transition-colors disabled:cursor-not-allowed"
412
- style={{ backgroundColor: loading ? 'var(--booking-border-input)' : 'var(--booking-primary)' }}
413
- >
414
- {loading ? 'Looking up...' : 'View Booking'}
415
- </button>
416
- </form>
417
-
418
- {!isEmbed && (
419
- <div
420
- className="mt-6 pt-6 text-center text-sm"
421
- style={{
422
- borderColor: 'var(--booking-border)',
423
- borderTopWidth: '1px',
424
- borderTopStyle: 'solid',
425
- color: 'var(--booking-text-muted)',
426
- }}
427
- >
428
- <p>Enter your booking reference and last name to view your booking details.</p>
429
- <p className="mt-1" style={{ color: 'var(--booking-text-muted)' }}>
430
- You can also share a link: /manage?ref=ABC12345&amp;lastName=Smith
431
- </p>
432
- </div>
433
- )}
434
- </div>
435
- </div>
436
- );
437
- }
@@ -1,131 +0,0 @@
1
- /* Clean, minimal phone input matching the reference design */
2
- .wrapper {
3
- width: 100%;
4
- position: relative;
5
- overflow: visible;
6
- --react-international-phone-height: 44px;
7
- --react-international-phone-border-color: rgba(0, 0, 0, 0.12);
8
- --react-international-phone-border-radius: 12px;
9
- --react-international-phone-background-color: #fff;
10
- --react-international-phone-text-color: var(--primary-text, #333);
11
- --react-international-phone-font-size: 1rem;
12
- --react-international-phone-dropdown-shadow: 0 4px 20px rgba(0, 0, 0, 0.12);
13
- --react-international-phone-dropdown-item-height: 40px;
14
- --react-international-phone-dropdown-item-font-size: 14px;
15
- --react-international-phone-dropdown-item-text-color: var(--primary-text, #333);
16
- --react-international-phone-dropdown-item-dial-code-color: var(--grey-text, #6b7280);
17
- --react-international-phone-selected-dropdown-item-background-color: #f5f5f5;
18
- --react-international-phone-country-selector-border-color: transparent;
19
- --react-international-phone-country-selector-background-color: transparent;
20
- }
21
-
22
- .phoneInput {
23
- width: 100%;
24
- }
25
-
26
- /* Container: single unified border around flag + input */
27
- .wrapper :global(.react-international-phone-input-container) {
28
- display: flex;
29
- align-items: center;
30
- width: 100%;
31
- border: 2px solid var(--accent-orange-10, rgba(0, 0, 0, 0.08));
32
- border-radius: 12px;
33
- background: var(--primary-background, #fff);
34
- transition: border-color 0.15s;
35
- overflow: visible;
36
- }
37
-
38
- /* Country selector needs overflow visible so dropdown can escape */
39
- .wrapper :global(.react-international-phone-country-selector) {
40
- overflow: visible;
41
- }
42
-
43
- .wrapper:focus-within :global(.react-international-phone-input-container) {
44
- border-color: var(--accent-orange);
45
- outline: none;
46
- }
47
-
48
- /* Country selector button - no separate border, blends into container */
49
- .wrapper :global(.react-international-phone-country-selector-button) {
50
- border: none;
51
- border-radius: 0;
52
- background: transparent;
53
- padding: 0 0.5rem 0 0.75rem;
54
- height: 100%;
55
- }
56
-
57
- .wrapper :global(.react-international-phone-country-selector-button:hover) {
58
- background: rgba(0, 0, 0, 0.03);
59
- }
60
-
61
- .wrapper :global(.react-international-phone-country-selector-button__dropdown-arrow) {
62
- border-top-color: var(--grey-text, #6b7280);
63
- margin-left: 0.25rem;
64
- }
65
-
66
- /* Input field */
67
- .wrapper :global(.react-international-phone-input) {
68
- flex: 1;
69
- min-width: 0;
70
- border: none;
71
- border-radius: 0;
72
- padding: 0.625rem 0.875rem;
73
- font-size: 1rem;
74
- font-family: inherit;
75
- color: var(--primary-text, #333);
76
- background: transparent;
77
- }
78
-
79
- .wrapper :global(.react-international-phone-input):focus {
80
- outline: none;
81
- }
82
-
83
- .wrapper :global(.react-international-phone-input)::placeholder {
84
- color: var(--grey-text, #878686);
85
- }
86
-
87
- /* Dropdown - floating panel BELOW the input, not inside */
88
- .wrapper :global(.react-international-phone-country-selector-dropdown),
89
- .dropdown {
90
- position: absolute !important;
91
- top: 100% !important;
92
- left: 0 !important;
93
- margin-top: 6px !important;
94
- width: 100% !important;
95
- min-width: 280px;
96
- max-height: 280px;
97
- border-radius: 12px;
98
- border: 1px solid rgba(0, 0, 0, 0.08);
99
- box-shadow: 0 4px 20px rgba(0, 0, 0, 0.12);
100
- background: #fff;
101
- padding: 8px 0;
102
- z-index: 1000;
103
- }
104
-
105
- .wrapper :global(.react-international-phone-country-selector-dropdown__list-item) {
106
- padding: 0.5rem 1rem;
107
- min-height: 40px;
108
- cursor: pointer;
109
- }
110
-
111
- .wrapper :global(.react-international-phone-country-selector-dropdown__list-item):hover,
112
- .wrapper :global(.react-international-phone-country-selector-dropdown__list-item--focused) {
113
- background: #f5f5f5;
114
- }
115
-
116
- .wrapper :global(.react-international-phone-country-selector-dropdown__list-item-country-name) {
117
- font-size: 14px;
118
- color: var(--primary-text, #333);
119
- }
120
-
121
- .wrapper :global(.react-international-phone-country-selector-dropdown__list-item-dial-code) {
122
- font-size: 14px;
123
- color: var(--grey-text, #6b7280);
124
- }
125
-
126
- /* Flag sizing */
127
- .wrapper :global(.react-international-phone-country-selector-button__flag-emoji),
128
- .wrapper :global(.react-international-phone-country-selector-dropdown__list-item-flag-emoji) {
129
- width: 20px;
130
- height: 14px;
131
- }
@@ -1,44 +0,0 @@
1
- 'use client';
2
-
3
- import React from 'react';
4
- import { PhoneInput } from 'react-international-phone';
5
- import 'react-international-phone/style.css';
6
- import styles from './PhoneInputWithCountry.module.css';
7
-
8
- interface PhoneInputWithCountryProps {
9
- id?: string;
10
- value: string;
11
- onChange: (value: string | undefined) => void;
12
- placeholder?: string;
13
- disabled?: boolean;
14
- className?: string;
15
- }
16
-
17
- export default function PhoneInputWithCountryComponent({
18
- id,
19
- value,
20
- onChange,
21
- placeholder = 'Phone number',
22
- disabled = false,
23
- className,
24
- }: PhoneInputWithCountryProps) {
25
- return (
26
- <div className={`${styles.wrapper} ${className ?? ''}`}>
27
- <PhoneInput
28
- defaultCountry="ca"
29
- preferredCountries={['ca', 'us']}
30
- value={value || ''}
31
- onChange={(phone) => onChange(phone || undefined)}
32
- placeholder={placeholder}
33
- disabled={disabled}
34
- inputProps={{ id }}
35
- className={styles.phoneInput}
36
- countrySelectorStyleProps={{
37
- dropdownStyleProps: {
38
- className: styles.dropdown,
39
- },
40
- }}
41
- />
42
- </div>
43
- );
44
- }