@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,166 +0,0 @@
1
- /**
2
- * Promo row — all layout in this module so it matches on viavia (full Tailwind) and in
3
- * embedded hosts (e.g. provider dashboard) that only load booking-flow.css, not every Tailwind utility.
4
- */
5
-
6
- /* Mobile: label above field (like CheckoutForm). sm+: label left of field. */
7
- .promoRow {
8
- display: flex;
9
- flex-direction: column;
10
- align-items: stretch;
11
- gap: 0.25rem;
12
- padding-top: 1rem;
13
- padding-bottom: 0.5rem;
14
- font-size: 0.875rem;
15
- line-height: 1.25rem;
16
- border-top: 1px solid var(--booking-stone-200, #e7e5e4);
17
- min-width: 0;
18
- }
19
-
20
- @media (min-width: 640px) {
21
- .promoRow {
22
- flex-direction: row;
23
- align-items: center;
24
- gap: 0.5rem;
25
- }
26
- }
27
-
28
- .promoLabel {
29
- flex-shrink: 0;
30
- white-space: nowrap;
31
- color: var(--booking-stone-600, #57534e);
32
- }
33
-
34
- .promoFieldRow {
35
- display: flex;
36
- align-items: center;
37
- gap: 0.5rem;
38
- flex: 1 1 0;
39
- min-width: 0;
40
- }
41
-
42
- .promoDiscount {
43
- margin-left: auto;
44
- flex-shrink: 0;
45
- white-space: nowrap;
46
- font-weight: 500;
47
- color: #dc2626;
48
- }
49
-
50
- .promoErrorInline {
51
- font-size: 0.875rem;
52
- line-height: 1.25rem;
53
- color: #dc2626;
54
- white-space: nowrap;
55
- }
56
-
57
- .inputWrap {
58
- position: relative;
59
- flex: 1 1 0;
60
- min-width: 0;
61
- max-width: 100%;
62
- }
63
-
64
- @media (min-width: 640px) {
65
- .inputWrap {
66
- max-width: 250px;
67
- }
68
- }
69
-
70
- /* Box with icons inside (x and check) */
71
- .input {
72
- width: 100%;
73
- min-width: 0;
74
- padding: 0.375rem 2.25rem 0.375rem 0.5rem;
75
- font-size: 0.875rem;
76
- border-radius: 0.25rem;
77
- border: 1px solid var(--booking-stone-300, #d6d3d1);
78
- background: #fff;
79
- color: var(--booking-stone-900, #1c1917);
80
- box-sizing: border-box;
81
- }
82
-
83
- .input:focus {
84
- outline: none;
85
- border-color: var(--booking-stone-500, #78716c);
86
- }
87
-
88
- .input:read-only {
89
- background: var(--booking-stone-50, #fafaf9);
90
- cursor: default;
91
- padding-right: 3.5rem;
92
- }
93
-
94
- /* X and check icons inside the box - dashboard style */
95
- .removeBtn {
96
- position: absolute;
97
- right: 0.25rem;
98
- top: 50%;
99
- transform: translateY(-50%);
100
- width: 1.5rem;
101
- height: 1.5rem;
102
- padding: 0;
103
- border-radius: 9999px;
104
- color: var(--booking-stone-500, #78716c);
105
- background: var(--booking-stone-200, #e7e5e4);
106
- border: none;
107
- cursor: pointer;
108
- display: flex;
109
- align-items: center;
110
- justify-content: center;
111
- }
112
-
113
- .removeBtn:hover {
114
- background: var(--booking-stone-300, #d6d3d1);
115
- color: var(--booking-stone-700, #44403c);
116
- }
117
-
118
- .loading {
119
- position: absolute;
120
- right: 0.5rem;
121
- top: 50%;
122
- transform: translateY(-50%);
123
- width: 1.25rem;
124
- height: 1.25rem;
125
- border-radius: 9999px;
126
- background: var(--booking-stone-100, #f5f5f4);
127
- animation: pulse 1.5s cubic-bezier(0.4, 0, 0.6, 1) infinite;
128
- }
129
-
130
- @keyframes pulse {
131
- 0%, 100% { opacity: 1; }
132
- 50% { opacity: 0.5; }
133
- }
134
-
135
- .errorIcon {
136
- position: absolute;
137
- right: 0.5rem;
138
- top: 50%;
139
- transform: translateY(-50%);
140
- width: 1.25rem;
141
- height: 1.25rem;
142
- border-radius: 9999px;
143
- background: #ef4444;
144
- color: #fff;
145
- display: flex;
146
- align-items: center;
147
- justify-content: center;
148
- }
149
-
150
- /* Check icon - inside box, next to remove btn */
151
- .appliedBadge {
152
- position: absolute;
153
- right: 2rem;
154
- top: 50%;
155
- transform: translateY(-50%);
156
- width: 1.5rem;
157
- height: 1.5rem;
158
- border-radius: 9999px;
159
- background: var(--booking-emerald-500, #10b981);
160
- color: #fff;
161
- display: flex;
162
- align-items: center;
163
- justify-content: center;
164
- flex-shrink: 0;
165
- }
166
-
@@ -1,99 +0,0 @@
1
- 'use client';
2
-
3
- import { Check, X } from 'lucide-react';
4
- import { formatCurrencyAmount } from '@/lib/currency';
5
- import type { Currency } from './CurrencySwitcher';
6
- import styles from './PromoCodeInput.module.css';
7
-
8
- type TranslationFn = (key: string, params?: Record<string, string>) => string;
9
-
10
- interface PromoCodeInputProps {
11
- promoCodeInput: string;
12
- appliedPromoCode: string | null;
13
- promoCodeError: string;
14
- promoCodeValidating: boolean;
15
- promoDiscountAmount: number;
16
- currency: Currency;
17
- locale: string;
18
- t: TranslationFn;
19
- onInputChange: (value: string) => void;
20
- onApply: () => void;
21
- onRemove: () => void;
22
- locked?: boolean;
23
- }
24
-
25
- export function PromoCodeInput({
26
- promoCodeInput,
27
- appliedPromoCode,
28
- promoCodeError,
29
- promoCodeValidating,
30
- promoDiscountAmount,
31
- currency,
32
- locale,
33
- t,
34
- onInputChange,
35
- onApply,
36
- onRemove,
37
- locked = false,
38
- }: PromoCodeInputProps) {
39
- return (
40
- <div className={styles.promoRow}>
41
- <label htmlFor="booking-promo-code" className={styles.promoLabel}>
42
- {t('booking.optionalPromoCode') || 'Promo / voucher / gift card'}
43
- </label>
44
- <div className={styles.promoFieldRow}>
45
- <div className={styles.inputWrap}>
46
- <input
47
- type="text"
48
- name="promoCode"
49
- id="booking-promo-code"
50
- value={promoCodeInput}
51
- onChange={(e) => onInputChange(e.target.value.toUpperCase())}
52
- onKeyDown={(e) => {
53
- if (e.key === 'Enter') {
54
- e.preventDefault();
55
- onApply();
56
- }
57
- }}
58
- placeholder={t('booking.promoCodePlaceholder')}
59
- autoComplete="off"
60
- readOnly={locked || !!appliedPromoCode}
61
- disabled={locked}
62
- className={styles.input}
63
- />
64
- {appliedPromoCode ? (
65
- <>
66
- <span className={styles.appliedBadge} aria-label={t('booking.promoApplied', { code: appliedPromoCode })}>
67
- <Check className="w-3.5 h-3.5" strokeWidth={3} />
68
- </span>
69
- {!locked && (
70
- <button
71
- type="button"
72
- onClick={onRemove}
73
- className={styles.removeBtn}
74
- aria-label={t('booking.removePromo')}
75
- >
76
- <X className="w-4 h-4" strokeWidth={2.5} />
77
- </button>
78
- )}
79
- </>
80
- ) : promoCodeValidating ? (
81
- <span className={styles.loading} aria-hidden />
82
- ) : promoCodeError ? (
83
- <span className={styles.errorIcon} aria-label={promoCodeError}>
84
- <X className="w-3 h-3" strokeWidth={3} />
85
- </span>
86
- ) : null}
87
- </div>
88
- {promoDiscountAmount > 0 && (
89
- <span className={styles.promoDiscount}>
90
- -{formatCurrencyAmount(promoDiscountAmount, currency, locale as 'en' | 'fr')}
91
- </span>
92
- )}
93
- {promoCodeError && (
94
- <span className={styles.promoErrorInline}>{promoCodeError}</span>
95
- )}
96
- </div>
97
- </div>
98
- );
99
- }
@@ -1,173 +0,0 @@
1
- /**
2
- * Return time selector - list of return options with stay summary
3
- */
4
-
5
- .label {
6
- display: block;
7
- font-size: 0.875rem;
8
- font-weight: 500;
9
- color: var(--booking-stone-700, #44403c);
10
- margin-bottom: 1rem;
11
- }
12
-
13
- .list {
14
- display: flex;
15
- flex-direction: column;
16
- gap: 0.5rem;
17
- }
18
-
19
- .btn {
20
- width: 100%;
21
- padding: 1rem 1.25rem;
22
- border-radius: 0.5rem;
23
- border: 2px solid;
24
- transition: all 0.2s;
25
- text-align: left;
26
- position: relative;
27
- }
28
-
29
- .btnSelected {
30
- border-color: var(--booking-emerald-600, #059669);
31
- background: var(--booking-emerald-600, #059669);
32
- color: #fff;
33
- }
34
-
35
- .btnSelected .time,
36
- .btnSelected .location {
37
- color: rgba(255, 255, 255, 0.95);
38
- }
39
-
40
- .btnSelected .staySummary {
41
- color: rgba(255, 255, 255, 0.85);
42
- }
43
-
44
- .btnSelected .price {
45
- color: rgba(255, 255, 255, 0.95);
46
- }
47
-
48
- .btnAvailable {
49
- border-color: var(--booking-stone-200, #e7e5e4);
50
- background: var(--light-orange-background-dark, #f7e4dc);
51
- }
52
-
53
- .btnAvailable:hover {
54
- border-color: #6ee7b7;
55
- background: var(--light-orange-background, #fff1eb);
56
- }
57
-
58
- .btnDisabled {
59
- border-color: var(--booking-stone-200, #e7e5e4);
60
- background: var(--booking-stone-100, #f5f5f4);
61
- color: var(--booking-stone-400, #a8a29e);
62
- cursor: not-allowed;
63
- }
64
-
65
- .btnSoldOutAdmin {
66
- border-color: #fca5a5;
67
- background: #fee2e2;
68
- color: #b91c1c;
69
- }
70
-
71
- .btnSoldOutAdmin:hover {
72
- border-color: #fca5a5;
73
- background: #fee2e2;
74
- }
75
-
76
- .btnSoldOutLocked {
77
- border-color: #fca5a5;
78
- background: #fee2e2;
79
- color: #b91c1c;
80
- cursor: not-allowed;
81
- }
82
-
83
- .btnSoldOutLocked:hover {
84
- border-color: #fca5a5;
85
- background: #fee2e2;
86
- }
87
-
88
- .badge {
89
- position: absolute;
90
- top: -0.5rem;
91
- right: -0.25rem;
92
- font-family: 'Poppins', sans-serif;
93
- font-size: 0.75rem;
94
- font-weight: 600;
95
- text-transform: lowercase;
96
- color: #fff;
97
- padding: 0.25rem 0.5rem;
98
- border-radius: 9999px;
99
- background-color: #ff4d00;
100
- }
101
-
102
- .content {
103
- display: flex;
104
- align-items: center;
105
- justify-content: space-between;
106
- }
107
-
108
- .details {
109
- flex: 1;
110
- }
111
-
112
- .time {
113
- font-weight: 500;
114
- color: var(--booking-stone-900, #1c1917);
115
- }
116
-
117
- .location {
118
- font-size: 0.75rem;
119
- color: var(--booking-stone-600, #57534e);
120
- margin-top: 0.125rem;
121
- }
122
-
123
- .capacity {
124
- font-size: 0.75rem;
125
- color: var(--booking-stone-500, #78716c);
126
- margin-top: 0.125rem;
127
- font-variant-numeric: tabular-nums;
128
- }
129
-
130
- .btnSelected .capacityDefault {
131
- color: rgba(255, 255, 255, 0.9);
132
- }
133
-
134
- .capacityProjected {
135
- font-size: 0.7rem;
136
- line-height: 1.2;
137
- margin-top: 0.25rem;
138
- font-weight: 600;
139
- color: #b91c1c;
140
- font-variant-numeric: tabular-nums;
141
- }
142
-
143
- .staySummary {
144
- font-size: 0.75rem;
145
- color: var(--booking-stone-500, #78716c);
146
- margin-top: 0.25rem;
147
- font-weight: 600;
148
- }
149
-
150
- .soldOut {
151
- font-size: 0.75rem;
152
- font-weight: 500;
153
- margin-top: 0.25rem;
154
- color: #b91c1c;
155
- }
156
-
157
- .price {
158
- font-size: 0.875rem;
159
- font-weight: 600;
160
- margin-left: 1rem;
161
- }
162
-
163
- .priceIncluded {
164
- color: var(--booking-stone-600, #57534e);
165
- }
166
-
167
- .pricePositive {
168
- color: var(--booking-emerald-600, #059669);
169
- }
170
-
171
- .priceNegative {
172
- color: #b91c1c;
173
- }
@@ -1,145 +0,0 @@
1
- 'use client';
2
-
3
- import { parseISO } from 'date-fns';
4
- import { formatInTimeZone } from 'date-fns-tz';
5
- import { formatCurrencyAmount } from '@/lib/currency';
6
- import type { ReturnOption } from '@/lib/booking-api';
7
- import type { Currency } from './CurrencySwitcher';
8
- import styles from './ReturnTimeSelector.module.css';
9
-
10
- type TranslationFn = (key: string, params?: Record<string, string | number>) => string;
11
-
12
- interface ReturnTimeSelectorProps {
13
- returnOptions: ReturnOption[];
14
- selectedReturnOption: ReturnOption | null;
15
- selectedTicketCount: number;
16
- companyTimezone: string;
17
- currency: Currency;
18
- locale: string;
19
- isAdmin: boolean;
20
- t: TranslationFn;
21
- onReturnSelect: (option: ReturnOption) => void;
22
- getStaySummary: (returnDateTime: Date) => string | null;
23
- }
24
-
25
- export function ReturnTimeSelector({
26
- returnOptions,
27
- selectedReturnOption,
28
- selectedTicketCount,
29
- companyTimezone,
30
- currency,
31
- locale,
32
- isAdmin,
33
- t,
34
- onReturnSelect,
35
- getStaySummary,
36
- }: ReturnTimeSelectorProps) {
37
- const sortedReturnOptions = [...returnOptions].sort((a, b) => {
38
- const timeA = parseISO(a.dateTime).getTime();
39
- const timeB = parseISO(b.dateTime).getTime();
40
- return timeA - timeB;
41
- });
42
-
43
- return (
44
- <div>
45
- <label className={styles.label}>
46
- {t('booking.selectReturnTime') || 'Select Return Time'}
47
- </label>
48
-
49
- <div className={styles.list}>
50
- {sortedReturnOptions.map((returnOption, index) => {
51
- const returnDateTime = parseISO(returnOption.dateTime);
52
- const displayTime = formatInTimeZone(returnDateTime, companyTimezone, 'h:mm a');
53
- const isSelected = selectedReturnOption?.returnAvailabilityId === returnOption.returnAvailabilityId;
54
- const isSoldOut = returnOption.vacancies === 0;
55
- const returnVacancies = returnOption.vacancies;
56
- const returnTotalCap = returnOption.totalCapacity ?? 0;
57
- const returnBooked =
58
- returnOption.bookedCapacity ?? (returnTotalCap - returnVacancies);
59
- const projectedSeats = returnBooked + selectedTicketCount;
60
- const isInsufficientForParty =
61
- selectedTicketCount > 0 && (returnOption.vacancies ?? 0) < selectedTicketCount;
62
- const canSelectReturn = isAdmin || (!isSoldOut && !isInsufficientForParty);
63
- const priceAdjustmentPerPerson = returnOption.priceAdjustmentByCurrency?.[currency] ?? 0;
64
- const returnOptionsCount = returnOptions.length;
65
- const isMostPopular = returnOptionsCount > 1 && index === 0;
66
- const staySummary = getStaySummary(returnDateTime);
67
- const showAdminProjectedLoad =
68
- isSelected && isAdmin && returnTotalCap > 0 && selectedTicketCount > returnVacancies;
69
- const btnClass = (() => {
70
- if (isSelected) return styles.btnSelected;
71
- if (!isAdmin) {
72
- return (isSoldOut || isInsufficientForParty) ? styles.btnSoldOutLocked : styles.btnAvailable;
73
- }
74
- if (isSoldOut && returnVacancies === 0) return styles.btnSoldOutAdmin;
75
- return styles.btnAvailable;
76
- })();
77
- const priceClass = priceAdjustmentPerPerson === 0
78
- ? styles.priceIncluded
79
- : priceAdjustmentPerPerson > 0
80
- ? styles.pricePositive
81
- : styles.priceNegative;
82
-
83
- return (
84
- <button
85
- key={returnOption.returnAvailabilityId}
86
- onClick={() => canSelectReturn && onReturnSelect(returnOption)}
87
- disabled={!canSelectReturn}
88
- className={`${styles.btn} ${btnClass} ${isSelected ? 'booking-return-selected' : ''}`}
89
- >
90
- {isMostPopular && (
91
- <div className={styles.badge}>
92
- {t('booking.mostPopular')}
93
- </div>
94
- )}
95
- <div className={styles.content}>
96
- <div className={styles.details}>
97
- <div className={styles.time}>{displayTime}</div>
98
- <div className={styles.location}>from {returnOption.returnLocation}</div>
99
- {isAdmin && returnTotalCap > 0 && (showAdminProjectedLoad ? (
100
- <div className={styles.capacityProjected} aria-live="polite">
101
- <div>{t('booking.adminBookingLoadLine1', { projected: projectedSeats })}</div>
102
- <div>{t('booking.adminBookingLoadLine2', { total: returnTotalCap })}</div>
103
- </div>
104
- ) : (
105
- <div
106
- className={
107
- isSelected
108
- ? `${styles.capacity} ${styles.capacityDefault}`
109
- : styles.capacity
110
- }
111
- >
112
- {t('calendar.spotsAvailable', { count: returnVacancies })}
113
- </div>
114
- ))}
115
- {staySummary && (
116
- <div className={styles.staySummary}>
117
- {staySummary}
118
- </div>
119
- )}
120
- {isSoldOut && (
121
- <div className={styles.soldOut}>
122
- {t('booking.soldOut')}
123
- </div>
124
- )}
125
- {isInsufficientForParty && !isAdmin && (
126
- <div className={styles.soldOut}>
127
- {`Only ${returnOption.vacancies} spot${returnOption.vacancies === 1 ? '' : 's'} left, decrease your ticket count below to select this time`}
128
- </div>
129
- )}
130
- </div>
131
- <div className={`${styles.price} ${priceClass}`}>
132
- {priceAdjustmentPerPerson === 0
133
- ? 'Included'
134
- : priceAdjustmentPerPerson > 0
135
- ? `+${formatCurrencyAmount(priceAdjustmentPerPerson, currency, locale as 'en' | 'fr')} per person`
136
- : `${formatCurrencyAmount(priceAdjustmentPerPerson, currency, locale as 'en' | 'fr')} per person`}
137
- </div>
138
- </div>
139
- </button>
140
- );
141
- })}
142
- </div>
143
- </div>
144
- );
145
- }
@@ -1,111 +0,0 @@
1
- 'use client';
2
-
3
- import { useState } from 'react';
4
- import { TermsContent } from '@/components/terms/TermsContent';
5
-
6
- export interface TermsAcceptanceProps {
7
- checked: boolean;
8
- onChange: (checked: boolean) => void;
9
- /** Translation function for labels */
10
- t: (key: string) => string;
11
- /** Optional custom terms content (React node). If not provided, uses full TermsContent from terms-conditions page. */
12
- termsContent?: React.ReactNode;
13
- }
14
-
15
- /**
16
- * Checkbox to accept terms & conditions, with a "View terms" link that opens
17
- * a small modal showing the full text (in-page, not a separate page).
18
- * Least intrusive: user can proceed by checking the box; they can open the
19
- * modal to read the full terms if they want.
20
- */
21
- export function TermsAcceptance({
22
- checked,
23
- onChange,
24
- t,
25
- termsContent,
26
- }: TermsAcceptanceProps) {
27
- const [modalOpen, setModalOpen] = useState(false);
28
- const content = termsContent ?? <TermsContent className="text-xs [&_h1]:text-base [&_h2]:text-sm [&_h3]:text-xs [&_p]:text-xs" />;
29
-
30
- return (
31
- <>
32
- <label className="flex items-center gap-3 cursor-pointer group">
33
- <input
34
- type="checkbox"
35
- checked={checked}
36
- onChange={(e) => onChange(e.target.checked)}
37
- className="shrink-0 rounded border-stone-300 text-emerald-600 focus:ring-emerald-500"
38
- aria-describedby="terms-view-link"
39
- />
40
- <span className="text-sm text-stone-700">
41
- {t('terms.acceptPrefix')}{' '}
42
- <button
43
- type="button"
44
- id="terms-view-link"
45
- onClick={(e) => {
46
- e.preventDefault();
47
- setModalOpen(true);
48
- }}
49
- className="text-emerald-600 hover:text-emerald-700 underline focus:outline-none focus:ring-2 focus:ring-emerald-500 focus:ring-offset-1 rounded"
50
- >
51
- {t('terms.viewTerms')}
52
- </button>
53
- </span>
54
- </label>
55
-
56
- {/* Modal: small overlay with scrollable terms */}
57
- {modalOpen && (
58
- <div
59
- className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/40"
60
- role="dialog"
61
- aria-modal="true"
62
- aria-labelledby="terms-modal-title"
63
- onClick={() => setModalOpen(false)}
64
- >
65
- <div
66
- className="bg-white rounded-xl shadow-xl max-w-lg w-full max-h-[80vh] flex flex-col"
67
- onClick={(e) => e.stopPropagation()}
68
- >
69
- <div className="flex items-center justify-between px-5 py-4 border-b border-stone-200 shrink-0">
70
- <h2 id="terms-modal-title" className="text-lg font-semibold text-stone-900">
71
- {t('terms.title')}
72
- </h2>
73
- <button
74
- type="button"
75
- onClick={() => setModalOpen(false)}
76
- className="p-2 rounded-lg text-stone-500 hover:text-stone-700 hover:bg-stone-100 focus:outline-none focus:ring-2 focus:ring-emerald-500"
77
- aria-label={t('common.close')}
78
- >
79
- <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
80
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
81
- </svg>
82
- </button>
83
- </div>
84
- <div className="flex-1 min-h-0 overflow-y-auto px-5 py-4">
85
- {content}
86
- </div>
87
- <div className="px-5 py-4 border-t border-stone-200 shrink-0 flex gap-3 justify-end">
88
- <button
89
- type="button"
90
- onClick={() => setModalOpen(false)}
91
- className="px-4 py-3 text-sm font-medium text-stone-700 bg-stone-100 rounded-lg hover:bg-stone-200"
92
- >
93
- {t('common.close')}
94
- </button>
95
- <button
96
- type="button"
97
- onClick={() => {
98
- onChange(true);
99
- setModalOpen(false);
100
- }}
101
- className="px-4 py-3 text-sm font-medium text-white bg-emerald-600 rounded-lg hover:bg-emerald-700"
102
- >
103
- {t('terms.acceptAndClose')}
104
- </button>
105
- </div>
106
- </div>
107
- </div>
108
- )}
109
- </>
110
- );
111
- }