@ticketboothapp/booking 1.2.100 → 1.2.102
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.
- package/package.json +1 -1
- package/src/components/booking/BookingDialog.module.css +9 -0
- package/src/components/booking/BookingProductGrid.module.css +11 -0
- package/src/components/booking/BookingProductGrid.tsx +54 -28
- package/src/components/booking/CancellationPolicySelector.tsx +4 -1
- package/src/components/booking/CheckoutForm.module.css +108 -3
- package/src/components/booking/CheckoutForm.tsx +13 -1
- package/src/components/booking/CheckoutOptionalPhoneFields.tsx +58 -0
- package/src/components/booking/DapTourDescription.tsx +9 -7
- package/src/components/booking/DependentAddOnBookingDialog.tsx +42 -7
- package/src/components/booking/NewBookingFlow.tsx +137 -55
- package/src/components/booking/PrivateShuttleBookingFlow.module.css +7 -0
- package/src/components/booking/PrivateShuttleBookingFlow.tsx +21 -0
- package/src/components/booking/booking-flow-types.ts +2 -0
- package/src/components/booking/booking-flow-ui.ts +2 -0
- package/src/components/booking/booking-flow.css +72 -4
- package/src/data/dap-descriptions/session-couples-families-friends.en.json +0 -3
- package/src/data/dap-descriptions/session-elopements.en.json +12 -12
- package/src/data/dap-descriptions/session-proposals.en.json +6 -9
- package/src/data/products-config.json +20 -0
- package/src/lib/booking/checkout-contact.ts +8 -0
- package/src/lib/booking/i18n/messages/en.json +6 -0
- package/src/lib/booking/i18n/messages/fr.json +6 -0
- package/src/lib/booking/phone.ts +18 -0
- package/src/lib/booking-api.ts +310 -1
- package/src/lib/booking-types.ts +5 -0
- package/src/lib/dap-descriptions.ts +6 -0
- package/src/lib/dependent-add-on-api.ts +6 -0
- package/src/lib/photo-dap-config.ts +92 -15
- package/src/providers/dependent-add-on-dialog-provider.tsx +6 -0
- package/src/runtime/types.ts +2 -0
- package/src/strings/en.json +6 -6
|
@@ -41,41 +41,48 @@ const COUPLES_SESSION_PRODUCT_OPTIONS = [
|
|
|
41
41
|
const ELOPEMENTS_SESSION_PRODUCT_OPTIONS = [
|
|
42
42
|
{
|
|
43
43
|
dependentAddOnProductOptionId: 'dapo_XTeJt09NyNfX',
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
name: 'Alpine Vows',
|
|
45
|
+
label: '1 hr',
|
|
46
|
+
photosLabel: '60+ photos',
|
|
47
|
+
startingAtLabel: '$999',
|
|
47
48
|
},
|
|
48
49
|
{
|
|
49
50
|
dependentAddOnProductOptionId: 'dapo_Y8AxYt6Tjaam',
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
name: 'The Summit Story',
|
|
52
|
+
label: '4hrs',
|
|
53
|
+
photosLabel: '100+ photos',
|
|
54
|
+
startingAtLabel: '$2799',
|
|
53
55
|
},
|
|
54
56
|
{
|
|
55
57
|
dependentAddOnProductOptionId: 'dapo_YdmxIiQPxEJg',
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
name: 'The Grand Adventure',
|
|
59
|
+
label: '8hrs',
|
|
60
|
+
photosLabel: '100+ photos',
|
|
61
|
+
startingAtLabel: '$3999',
|
|
59
62
|
},
|
|
60
63
|
] as const;
|
|
61
64
|
|
|
62
65
|
const PROPOSALS_SESSION_PRODUCT_OPTIONS = [
|
|
63
66
|
{
|
|
64
67
|
dependentAddOnProductOptionId: 'dapo_SMRxjfDpIvwU',
|
|
68
|
+
name: 'Essentials',
|
|
65
69
|
label: '30 minutes',
|
|
66
|
-
photosLabel: '
|
|
70
|
+
photosLabel: '30 photos',
|
|
67
71
|
startingAtLabel: 'Starting at $799',
|
|
68
72
|
},
|
|
69
73
|
{
|
|
70
74
|
dependentAddOnProductOptionId: 'dapo_FyMbFgrBU4L8',
|
|
75
|
+
name: 'Celebration',
|
|
71
76
|
label: '60 minutes',
|
|
72
|
-
photosLabel: '
|
|
77
|
+
photosLabel: '60 photos',
|
|
73
78
|
startingAtLabel: 'Starting at $999',
|
|
79
|
+
mostPopular: true,
|
|
74
80
|
},
|
|
75
81
|
{
|
|
76
82
|
dependentAddOnProductOptionId: 'dapo_EOohfF0g2i1D',
|
|
83
|
+
name: 'Immersive',
|
|
77
84
|
label: '90 minutes',
|
|
78
|
-
photosLabel: '
|
|
85
|
+
photosLabel: '90 photos',
|
|
79
86
|
startingAtLabel: 'Starting at $1199',
|
|
80
87
|
},
|
|
81
88
|
] as const;
|
|
@@ -90,16 +97,78 @@ export type PhotoDapSlug = (typeof PHOTO_DAP_SLUGS)[number];
|
|
|
90
97
|
|
|
91
98
|
export type PhotoDapProductOption = {
|
|
92
99
|
dependentAddOnProductOptionId: string;
|
|
93
|
-
/**
|
|
100
|
+
/** Package name (e.g. Essentials); when set, `label` is shown as the duration subheading. */
|
|
101
|
+
name?: string;
|
|
102
|
+
/** Duration subheading when `name` is set; otherwise the tile title. */
|
|
94
103
|
label: string;
|
|
95
|
-
/**
|
|
104
|
+
/** Photo count line (e.g. 30 photos). */
|
|
96
105
|
photosLabel?: string;
|
|
97
|
-
/**
|
|
106
|
+
/** Price line (e.g. Starting at $799). */
|
|
98
107
|
startingAtLabel?: string;
|
|
108
|
+
mostPopular?: boolean;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export function defaultPhotoDapProductOptionId(
|
|
112
|
+
options: readonly PhotoDapProductOption[]
|
|
113
|
+
): string | undefined {
|
|
114
|
+
const popular = options.find((option) => option.mostPopular);
|
|
115
|
+
return popular?.dependentAddOnProductOptionId ?? options[0]?.dependentAddOnProductOptionId;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export type PhotoDapUpsellCardLines = {
|
|
119
|
+
startingPrice: string;
|
|
120
|
+
duration: string;
|
|
121
|
+
quantity: string;
|
|
99
122
|
};
|
|
100
123
|
|
|
124
|
+
function parsePriceFromStartingAtLabel(label: string | undefined): number | null {
|
|
125
|
+
if (!label) return null;
|
|
126
|
+
const match = label.match(/\$([\d,]+)/);
|
|
127
|
+
if (!match) return null;
|
|
128
|
+
return Number.parseInt(match[1].replace(/,/g, ''), 10);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function formatPhotosSummary(options: readonly PhotoDapProductOption[]): string {
|
|
132
|
+
const parts = options
|
|
133
|
+
.map((option) => option.photosLabel?.replace(/\s*photos?\s*$/i, '').trim())
|
|
134
|
+
.filter((part): part is string => Boolean(part));
|
|
135
|
+
if (parts.length === 0) return '';
|
|
136
|
+
return `${parts.join(' / ')} photos`;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const UPSELL_STARTING_PRICE_BY_SLUG: Record<
|
|
140
|
+
PhotoDapSlug,
|
|
141
|
+
(minPrice: number) => string
|
|
142
|
+
> = {
|
|
143
|
+
'session-couples-families-friends': (price) => `Sessions from $${price}`,
|
|
144
|
+
'session-elopements': (price) => `Starting at $${price}`,
|
|
145
|
+
'session-proposals': (price) => `Beginning at $${price}`,
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
/** Lines for manage-booking upsell cards — derived from the same options as the booking picker. */
|
|
149
|
+
export function getPhotoDapUpsellCardLines(slug: PhotoDapSlug): PhotoDapUpsellCardLines | null {
|
|
150
|
+
const catalog = getPhotoDapCatalog(slug);
|
|
151
|
+
const options = catalog?.productOptions;
|
|
152
|
+
if (!options?.length) return null;
|
|
153
|
+
|
|
154
|
+
const prices = options
|
|
155
|
+
.map((option) => parsePriceFromStartingAtLabel(option.startingAtLabel))
|
|
156
|
+
.filter((price): price is number => price != null);
|
|
157
|
+
const minPrice = prices.length > 0 ? Math.min(...prices) : null;
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
startingPrice:
|
|
161
|
+
minPrice != null
|
|
162
|
+
? UPSELL_STARTING_PRICE_BY_SLUG[slug](minPrice)
|
|
163
|
+
: (options[0]?.startingAtLabel ?? ''),
|
|
164
|
+
duration: options.map((option) => option.label).join(' / '),
|
|
165
|
+
quantity: formatPhotosSummary(options),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
101
169
|
export type PhotoDapCatalog = {
|
|
102
170
|
dependentAddOnProductId: string;
|
|
171
|
+
receiptDisplayTitle: string;
|
|
103
172
|
/** When set, that option is fixed (no picker). Env `NEXT_PUBLIC_DAP_PHOTO_SESSION_COUPLES_OPTION_ID` forces this for couples. */
|
|
104
173
|
dependentAddOnProductOptionId?: string;
|
|
105
174
|
/** When multiple entries and no fixed option id, the dialog shows a session-length control */
|
|
@@ -111,6 +180,8 @@ export type PhotoDapCatalog = {
|
|
|
111
180
|
* Should match TicketBooth dependent add-on product config; availability API may override when present.
|
|
112
181
|
*/
|
|
113
182
|
cancellationDaysBeforeSession: number;
|
|
183
|
+
learnMoreUrl?: string;
|
|
184
|
+
learnMoreLabel?: string;
|
|
114
185
|
};
|
|
115
186
|
|
|
116
187
|
/** Image sets for the dependent add-on dialog collage (photos only; no video). */
|
|
@@ -163,6 +234,7 @@ export function getPhotoDapCatalog(slug: PhotoDapSlug): PhotoDapCatalog | null {
|
|
|
163
234
|
if (forcedOptionId) {
|
|
164
235
|
return {
|
|
165
236
|
dependentAddOnProductId: productId,
|
|
237
|
+
receiptDisplayTitle: 'Friends, family, and couples photography session',
|
|
166
238
|
dependentAddOnProductOptionId: forcedOptionId,
|
|
167
239
|
collageImageIds,
|
|
168
240
|
cancellationDaysBeforeSession: DEFAULT_PHOTO_DAP_CANCELLATION_DAYS_BEFORE_SESSION,
|
|
@@ -170,6 +242,7 @@ export function getPhotoDapCatalog(slug: PhotoDapSlug): PhotoDapCatalog | null {
|
|
|
170
242
|
}
|
|
171
243
|
return {
|
|
172
244
|
dependentAddOnProductId: productId,
|
|
245
|
+
receiptDisplayTitle: 'Friends, family, and couples photography session',
|
|
173
246
|
productOptions: [...COUPLES_SESSION_PRODUCT_OPTIONS],
|
|
174
247
|
collageImageIds,
|
|
175
248
|
cancellationDaysBeforeSession: DEFAULT_PHOTO_DAP_CANCELLATION_DAYS_BEFORE_SESSION,
|
|
@@ -187,6 +260,7 @@ export function getPhotoDapCatalog(slug: PhotoDapSlug): PhotoDapCatalog | null {
|
|
|
187
260
|
if (forcedOptionId) {
|
|
188
261
|
return {
|
|
189
262
|
dependentAddOnProductId: productId,
|
|
263
|
+
receiptDisplayTitle: 'Elopement photography session',
|
|
190
264
|
dependentAddOnProductOptionId: forcedOptionId,
|
|
191
265
|
collageImageIds,
|
|
192
266
|
cancellationDaysBeforeSession: DEFAULT_PHOTO_DAP_CANCELLATION_DAYS_BEFORE_SESSION,
|
|
@@ -194,6 +268,7 @@ export function getPhotoDapCatalog(slug: PhotoDapSlug): PhotoDapCatalog | null {
|
|
|
194
268
|
}
|
|
195
269
|
return {
|
|
196
270
|
dependentAddOnProductId: productId,
|
|
271
|
+
receiptDisplayTitle: 'Elopement photography session',
|
|
197
272
|
productOptions: [...ELOPEMENTS_SESSION_PRODUCT_OPTIONS],
|
|
198
273
|
collageImageIds,
|
|
199
274
|
cancellationDaysBeforeSession: DEFAULT_PHOTO_DAP_CANCELLATION_DAYS_BEFORE_SESSION,
|
|
@@ -211,6 +286,7 @@ export function getPhotoDapCatalog(slug: PhotoDapSlug): PhotoDapCatalog | null {
|
|
|
211
286
|
if (forcedOptionId) {
|
|
212
287
|
return {
|
|
213
288
|
dependentAddOnProductId: productId,
|
|
289
|
+
receiptDisplayTitle: 'Proposal photography session',
|
|
214
290
|
dependentAddOnProductOptionId: forcedOptionId,
|
|
215
291
|
collageImageIds,
|
|
216
292
|
cancellationDaysBeforeSession: DEFAULT_PHOTO_DAP_CANCELLATION_DAYS_BEFORE_SESSION,
|
|
@@ -218,6 +294,7 @@ export function getPhotoDapCatalog(slug: PhotoDapSlug): PhotoDapCatalog | null {
|
|
|
218
294
|
}
|
|
219
295
|
return {
|
|
220
296
|
dependentAddOnProductId: productId,
|
|
297
|
+
receiptDisplayTitle: 'Proposal photography session',
|
|
221
298
|
productOptions: [...PROPOSALS_SESSION_PRODUCT_OPTIONS],
|
|
222
299
|
collageImageIds,
|
|
223
300
|
cancellationDaysBeforeSession: DEFAULT_PHOTO_DAP_CANCELLATION_DAYS_BEFORE_SESSION,
|
|
@@ -13,14 +13,18 @@ import type { PhotoDapSlug } from '../lib/photo-dap-config';
|
|
|
13
13
|
|
|
14
14
|
export type DependentAddOnProductOptionChoice = {
|
|
15
15
|
dependentAddOnProductOptionId: string;
|
|
16
|
+
name?: string;
|
|
16
17
|
label: string;
|
|
17
18
|
photosLabel?: string;
|
|
18
19
|
startingAtLabel?: string;
|
|
20
|
+
mostPopular?: boolean;
|
|
19
21
|
};
|
|
20
22
|
|
|
21
23
|
export type DependentAddOnDialogOpenPayload = {
|
|
22
24
|
/** Card title shown in dialog header */
|
|
23
25
|
productDisplayTitle: string;
|
|
26
|
+
/** Customer-facing line label for receipts/payment summaries. */
|
|
27
|
+
receiptDisplayTitle?: string;
|
|
24
28
|
dependentAddOnProductId: string;
|
|
25
29
|
/** Fixed catalog option (no picker) */
|
|
26
30
|
dependentAddOnProductOptionId?: string;
|
|
@@ -35,6 +39,8 @@ export type DependentAddOnDialogOpenPayload = {
|
|
|
35
39
|
collageImageIds?: string[];
|
|
36
40
|
/** Loads expandable copy from dap-descriptions */
|
|
37
41
|
dapDescriptionSlug?: PhotoDapSlug;
|
|
42
|
+
learnMoreUrl?: string;
|
|
43
|
+
learnMoreLabel?: string;
|
|
38
44
|
/**
|
|
39
45
|
* From DAP catalog / TicketBooth product — days before the photo session for full-refund cancellation.
|
|
40
46
|
* Availability API may override when it returns the same field.
|
package/src/runtime/types.ts
CHANGED
|
@@ -53,6 +53,8 @@ export interface BookingRuntimeSlots {
|
|
|
53
53
|
ImageModal: BookingSlotComponent;
|
|
54
54
|
/** Optional override; package supplies a full default terms component when omitted. */
|
|
55
55
|
TermsContent?: BookingSlotComponent;
|
|
56
|
+
/** Optional phone input with country selector (e.g. Via Via `PhoneInputWithCountry`). */
|
|
57
|
+
PhoneInput?: BookingSlotComponent;
|
|
56
58
|
PlusIcon: ComponentType<SVGProps<SVGSVGElement>>;
|
|
57
59
|
MinusIcon: ComponentType<SVGProps<SVGSVGElement>>;
|
|
58
60
|
/** Via Via `ButtonHoverColor` enum from `@/components/button`. */
|
package/src/strings/en.json
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
},
|
|
17
17
|
|
|
18
18
|
"partnerPortal": {
|
|
19
|
-
"pageTitle": "Partner
|
|
19
|
+
"pageTitle": "Partner Booking Portal",
|
|
20
20
|
"tabBook": "Book",
|
|
21
21
|
"tabBookings": "Your bookings",
|
|
22
22
|
"tabLivePickups": "Live pickups",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"bookTabAttributionConfirmLabelNoAgent": "I confirm the partner is correct.",
|
|
50
50
|
"bookSuccessToast": "Booking made under partner {{partner}} with agent {{agent}}.",
|
|
51
51
|
"bookSuccessToastNoAgent": "Booking made under partner {{partner}}.",
|
|
52
|
-
"signInTitle": "Partner sign-in",
|
|
52
|
+
"signInTitle": "Partner Booking Portal sign-in",
|
|
53
53
|
"signInDescription": "Use the email on file for your organization (partner contact, account email, or an agent payout email). We will email you a one-time code — no password to remember.",
|
|
54
54
|
"signInEmailLabel": "Email",
|
|
55
55
|
"signInSelectEmailPlaceholder": "Choose an email",
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
"sessionLoading": "Restoring your session…",
|
|
74
74
|
"bookingsLoading": "Loading your bookings…",
|
|
75
75
|
"bookingsLoadError": "We could not load your bookings. Try again in a moment.",
|
|
76
|
-
"bookingsEmpty": "No partner portal bookings yet. When you complete a booking while signed in, it will appear here.",
|
|
76
|
+
"bookingsEmpty": "No partner booking portal bookings yet. When you complete a booking while signed in, it will appear here.",
|
|
77
77
|
"livePickupsEmptyToday": "No tour pickups scheduled for today.",
|
|
78
78
|
"livePickupsGuestPageLink": "Open guest live pickups page",
|
|
79
79
|
"bookingsColReference": "Reference",
|
|
@@ -191,9 +191,9 @@
|
|
|
191
191
|
"orgAddAgentDuplicateName": "An agent with this name already exists. Choose a different name.",
|
|
192
192
|
"orgNone": "—",
|
|
193
193
|
"agentAddNewOption": "Add an agent…",
|
|
194
|
-
"partnerLoginPageTitle": "Partner sign-in",
|
|
195
|
-
"partnerLoginPageDescription": "Enter the email on file for your partner account. We will send you a one-time code to complete sign-in.",
|
|
196
|
-
"backToPortal": "Back to partner portal"
|
|
194
|
+
"partnerLoginPageTitle": "Partner Booking Portal sign-in",
|
|
195
|
+
"partnerLoginPageDescription": "Enter the email on file for your partner booking account. We will send you a one-time code to complete sign-in.",
|
|
196
|
+
"backToPortal": "Back to partner booking portal"
|
|
197
197
|
},
|
|
198
198
|
|
|
199
199
|
"staffPortal": {
|