@ticketboothapp/booking 1.2.55 → 1.2.58
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/BookingFlow.tsx +691 -169
- package/src/components/booking/Calendar.module.css +6 -1
- package/src/components/booking/ChangeBookingDialog.tsx +41 -178
- package/src/components/booking/CheckoutForm.tsx +29 -4
- package/src/components/booking/DefaultTermsContent.tsx +178 -0
- package/src/components/booking/ItineraryBox.tsx +2 -2
- package/src/components/booking/PriceBreakdown.tsx +92 -27
- package/src/components/booking/PriceSummary.tsx +77 -4
- package/src/components/booking/PrivateShuttleBookingFlow.tsx +83 -27
- package/src/components/booking/TermsAcceptance.tsx +2 -1
- package/src/components/booking/booking-flow-ui.ts +42 -0
- package/src/components/booking/booking-flow.css +37 -2
- package/src/components/booking/change-booking-compare.module.css +97 -0
- package/src/components/booking/change-booking-compare.tsx +228 -0
- package/src/components/booking/provider-dashboard-change-booking.ts +47 -0
- package/src/index.ts +19 -0
- package/src/runtime/types.ts +2 -1
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compact compare cards: Figtree only (no Poppins). Small type + tight spacing for dialog embeds.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
.column {
|
|
6
|
+
overflow: hidden;
|
|
7
|
+
border-radius: 0.5rem;
|
|
8
|
+
padding: 0.625rem 0.75rem;
|
|
9
|
+
background: var(--light-orange-background);
|
|
10
|
+
/* Cascade to itinerary `<span>`s that only use Tailwind weight/color classes */
|
|
11
|
+
font-family: 'Figtree', var(--booking-font-sans, ui-sans-serif), sans-serif;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.kicker {
|
|
15
|
+
margin: 0 0 0.375rem 0;
|
|
16
|
+
font-family: 'Figtree', var(--booking-font-sans, ui-sans-serif), sans-serif;
|
|
17
|
+
font-size: 0.625rem;
|
|
18
|
+
font-weight: 600;
|
|
19
|
+
letter-spacing: 0.07em;
|
|
20
|
+
text-transform: uppercase;
|
|
21
|
+
line-height: 1.2;
|
|
22
|
+
color: var(--grey-text, var(--booking-text-muted, #78716c));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.row {
|
|
26
|
+
display: flex;
|
|
27
|
+
flex-wrap: wrap;
|
|
28
|
+
align-items: baseline;
|
|
29
|
+
gap: 0;
|
|
30
|
+
margin: 0 0 0.2rem 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.label {
|
|
34
|
+
font-family: 'Figtree', var(--booking-font-sans, ui-sans-serif), sans-serif;
|
|
35
|
+
font-size: 0.8125rem;
|
|
36
|
+
font-weight: 600;
|
|
37
|
+
line-height: 1.3;
|
|
38
|
+
color: var(--primary-text, var(--booking-text, #1c1917));
|
|
39
|
+
margin-right: 0.3em;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.value {
|
|
43
|
+
font-family: 'Figtree', var(--booking-font-sans, ui-sans-serif), sans-serif;
|
|
44
|
+
font-size: 0.8125rem;
|
|
45
|
+
font-weight: 400;
|
|
46
|
+
line-height: 1.3;
|
|
47
|
+
color: var(--primary-text, var(--booking-text, #1c1917));
|
|
48
|
+
flex: 1;
|
|
49
|
+
min-width: 0;
|
|
50
|
+
overflow-wrap: break-word;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.itineraryBlock {
|
|
54
|
+
margin: 0.15rem 0 0.2rem 0;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.itineraryList {
|
|
58
|
+
margin: 0.2rem 0 0 0;
|
|
59
|
+
padding: 0;
|
|
60
|
+
list-style: none;
|
|
61
|
+
font-family: 'Figtree', var(--booking-font-sans, ui-sans-serif), sans-serif;
|
|
62
|
+
font-size: 0.8125rem;
|
|
63
|
+
font-weight: 400;
|
|
64
|
+
line-height: 1.3;
|
|
65
|
+
color: var(--primary-text, var(--booking-text, #1c1917));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.itineraryList li + li {
|
|
69
|
+
margin-top: 0.125rem;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.totalRow {
|
|
73
|
+
display: flex;
|
|
74
|
+
flex-wrap: wrap;
|
|
75
|
+
align-items: baseline;
|
|
76
|
+
justify-content: space-between;
|
|
77
|
+
gap: 0.5rem 0.75rem;
|
|
78
|
+
margin-top: 0.5rem;
|
|
79
|
+
padding-top: 0.5rem;
|
|
80
|
+
border-top: 1px solid rgba(231, 229, 228, 0.8);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.totalLabel {
|
|
84
|
+
font-family: 'Figtree', var(--booking-font-sans, ui-sans-serif), sans-serif;
|
|
85
|
+
font-size: 0.8125rem;
|
|
86
|
+
font-weight: 600;
|
|
87
|
+
color: var(--primary-text, var(--booking-text, #1c1917));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.totalAmount {
|
|
91
|
+
font-family: 'Figtree', var(--booking-font-sans, ui-sans-serif), sans-serif;
|
|
92
|
+
font-size: 0.875rem;
|
|
93
|
+
font-weight: 600;
|
|
94
|
+
font-variant-numeric: tabular-nums;
|
|
95
|
+
line-height: 1.2;
|
|
96
|
+
color: var(--primary-text, var(--booking-text, #1c1917));
|
|
97
|
+
}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import styles from './change-booking-compare.module.css';
|
|
4
|
+
|
|
5
|
+
export type ItineraryStepLine = { time: string | null; label: string };
|
|
6
|
+
|
|
7
|
+
export type ChangeHighlightVariant = 'current' | 'new';
|
|
8
|
+
|
|
9
|
+
/** Same date formatting as manage-booking / ChangeBookingDialog (Mountain Time label). */
|
|
10
|
+
export function formatBookingDateForChangeCompare(dateTime: string | null | undefined): string {
|
|
11
|
+
if (!dateTime) return '—';
|
|
12
|
+
try {
|
|
13
|
+
const date = new Date(dateTime);
|
|
14
|
+
return date.toLocaleDateString('en-US', {
|
|
15
|
+
weekday: 'short',
|
|
16
|
+
year: 'numeric',
|
|
17
|
+
month: 'short',
|
|
18
|
+
day: 'numeric',
|
|
19
|
+
timeZone: 'America/Denver',
|
|
20
|
+
});
|
|
21
|
+
} catch {
|
|
22
|
+
return dateTime;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function formatBookingItemsForCompare(
|
|
27
|
+
items: Array<{ category: string; count: number }> | null | undefined,
|
|
28
|
+
): string {
|
|
29
|
+
if (!items?.length) return '—';
|
|
30
|
+
const labels: Record<string, string> = {
|
|
31
|
+
ADULT: 'adult',
|
|
32
|
+
CHILD: 'child',
|
|
33
|
+
INFANT: 'infant',
|
|
34
|
+
SENIOR: 'senior',
|
|
35
|
+
STUDENT: 'student',
|
|
36
|
+
};
|
|
37
|
+
const parts = items
|
|
38
|
+
.filter((item) => item.count > 0)
|
|
39
|
+
.map((item) => {
|
|
40
|
+
const label = labels[item.category] || item.category.toLowerCase();
|
|
41
|
+
return `${item.count} ${label}${item.count !== 1 ? 's' : ''}`;
|
|
42
|
+
});
|
|
43
|
+
return parts.length > 0 ? parts.join(', ') : '—';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function computeItineraryStepChanged(
|
|
47
|
+
stepsA: ItineraryStepLine[] | null,
|
|
48
|
+
stepsB: ItineraryStepLine[] | null,
|
|
49
|
+
): boolean[] | undefined {
|
|
50
|
+
if (!stepsA) return undefined;
|
|
51
|
+
return stepsA.map((a, i) => {
|
|
52
|
+
const b = stepsB?.[i];
|
|
53
|
+
return !b || a.time !== b.time || a.label !== b.label;
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** True when the two rendered itinerary lines are the same. */
|
|
58
|
+
export function itinerariesEqualForChangeCompare(
|
|
59
|
+
a: ItineraryStepLine[] | null,
|
|
60
|
+
b: ItineraryStepLine[] | null,
|
|
61
|
+
): boolean {
|
|
62
|
+
const lenA = a?.length ?? 0;
|
|
63
|
+
const lenB = b?.length ?? 0;
|
|
64
|
+
if (lenA === 0 && lenB === 0) return true;
|
|
65
|
+
if (lenA !== lenB || !a || !b) return false;
|
|
66
|
+
return a.every((step, i) => {
|
|
67
|
+
const o = b[i];
|
|
68
|
+
return step.time === o.time && step.label === o.label;
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* When the flow reports “changes” but the compare cards show the same date, tickets line,
|
|
74
|
+
* itinerary, and total, hide the second column and skip strike-through/bold.
|
|
75
|
+
*/
|
|
76
|
+
export function changeBookingCompareColumnsVisuallyMatch(params: {
|
|
77
|
+
dateCurrent: string;
|
|
78
|
+
dateNew: string;
|
|
79
|
+
ticketsCurrent: string;
|
|
80
|
+
ticketsNew: string;
|
|
81
|
+
itineraryCurrent: ItineraryStepLine[] | null;
|
|
82
|
+
itineraryNew: ItineraryStepLine[] | null;
|
|
83
|
+
totalCurrent: string;
|
|
84
|
+
totalNew: string;
|
|
85
|
+
}): boolean {
|
|
86
|
+
return (
|
|
87
|
+
params.dateCurrent === params.dateNew &&
|
|
88
|
+
params.ticketsCurrent === params.ticketsNew &&
|
|
89
|
+
params.totalCurrent === params.totalNew &&
|
|
90
|
+
itinerariesEqualForChangeCompare(params.itineraryCurrent, params.itineraryNew)
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function BookingChangeSummaryColumn({
|
|
95
|
+
kicker,
|
|
96
|
+
tourName,
|
|
97
|
+
dateStr,
|
|
98
|
+
ticketsStr,
|
|
99
|
+
itinerarySteps,
|
|
100
|
+
highlightVariant,
|
|
101
|
+
totalFormatted,
|
|
102
|
+
dateChanged = false,
|
|
103
|
+
ticketsChanged = false,
|
|
104
|
+
itineraryStepChanged,
|
|
105
|
+
}: {
|
|
106
|
+
kicker: string;
|
|
107
|
+
tourName: string;
|
|
108
|
+
dateStr: string;
|
|
109
|
+
ticketsStr: string;
|
|
110
|
+
itinerarySteps: ItineraryStepLine[] | null;
|
|
111
|
+
highlightVariant: ChangeHighlightVariant;
|
|
112
|
+
/** e.g. "C$123.45" */
|
|
113
|
+
totalFormatted: string;
|
|
114
|
+
dateChanged?: boolean;
|
|
115
|
+
ticketsChanged?: boolean;
|
|
116
|
+
itineraryStepChanged?: boolean[];
|
|
117
|
+
}) {
|
|
118
|
+
const dateClass =
|
|
119
|
+
highlightVariant === 'current' && dateChanged
|
|
120
|
+
? 'line-through text-stone-500'
|
|
121
|
+
: highlightVariant === 'new' && dateChanged
|
|
122
|
+
? 'font-semibold text-stone-900'
|
|
123
|
+
: '';
|
|
124
|
+
const ticketsClass =
|
|
125
|
+
highlightVariant === 'current' && ticketsChanged
|
|
126
|
+
? 'line-through text-stone-500'
|
|
127
|
+
: highlightVariant === 'new' && ticketsChanged
|
|
128
|
+
? 'font-semibold text-stone-900'
|
|
129
|
+
: '';
|
|
130
|
+
|
|
131
|
+
return (
|
|
132
|
+
<div className={`booking-change-summary-column ${styles.column}`}>
|
|
133
|
+
<p className={styles.kicker}>{kicker}</p>
|
|
134
|
+
|
|
135
|
+
{/* Same pattern as manage-booking `BookingDetails`: label + value on one baseline row */}
|
|
136
|
+
<div className={styles.row}>
|
|
137
|
+
<span className={styles.label}>Tour:</span>
|
|
138
|
+
<span className={styles.value}>{tourName.trim()}</span>
|
|
139
|
+
</div>
|
|
140
|
+
<div className={styles.row}>
|
|
141
|
+
<span className={styles.label}>Date:</span>
|
|
142
|
+
<span className={styles.value}>
|
|
143
|
+
{dateClass ? <span className={dateClass}>{dateStr}</span> : dateStr}
|
|
144
|
+
</span>
|
|
145
|
+
</div>
|
|
146
|
+
<div className={styles.row}>
|
|
147
|
+
<span className={styles.label}>Tickets:</span>
|
|
148
|
+
<span className={styles.value}>
|
|
149
|
+
{ticketsClass ? <span className={ticketsClass}>{ticketsStr}</span> : ticketsStr}
|
|
150
|
+
</span>
|
|
151
|
+
</div>
|
|
152
|
+
|
|
153
|
+
<div className={styles.itineraryBlock}>
|
|
154
|
+
<div className={styles.row}>
|
|
155
|
+
<span className={styles.label}>Itinerary:</span>
|
|
156
|
+
</div>
|
|
157
|
+
{itinerarySteps && itinerarySteps.length > 0 ? (
|
|
158
|
+
<ul className={styles.itineraryList}>
|
|
159
|
+
{itinerarySteps.map((step, i) => (
|
|
160
|
+
<li
|
|
161
|
+
key={i}
|
|
162
|
+
className={
|
|
163
|
+
itineraryStepChanged?.[i]
|
|
164
|
+
? highlightVariant === 'current'
|
|
165
|
+
? 'line-through text-stone-500'
|
|
166
|
+
: 'font-semibold text-stone-900'
|
|
167
|
+
: undefined
|
|
168
|
+
}
|
|
169
|
+
>
|
|
170
|
+
{step.time ? (
|
|
171
|
+
<>
|
|
172
|
+
<span
|
|
173
|
+
className={
|
|
174
|
+
itineraryStepChanged?.[i]
|
|
175
|
+
? highlightVariant === 'current'
|
|
176
|
+
? 'font-medium text-stone-500'
|
|
177
|
+
: 'font-semibold text-stone-900'
|
|
178
|
+
: 'font-medium text-stone-800'
|
|
179
|
+
}
|
|
180
|
+
>
|
|
181
|
+
{step.time}
|
|
182
|
+
</span>
|
|
183
|
+
<span
|
|
184
|
+
className={
|
|
185
|
+
itineraryStepChanged?.[i] && highlightVariant === 'new'
|
|
186
|
+
? 'text-stone-400'
|
|
187
|
+
: 'text-stone-500'
|
|
188
|
+
}
|
|
189
|
+
>
|
|
190
|
+
{' '}
|
|
191
|
+
·{' '}
|
|
192
|
+
</span>
|
|
193
|
+
<span
|
|
194
|
+
className={
|
|
195
|
+
itineraryStepChanged?.[i] && highlightVariant === 'new'
|
|
196
|
+
? 'font-semibold text-stone-900'
|
|
197
|
+
: undefined
|
|
198
|
+
}
|
|
199
|
+
>
|
|
200
|
+
{step.label}
|
|
201
|
+
</span>
|
|
202
|
+
</>
|
|
203
|
+
) : (
|
|
204
|
+
<span
|
|
205
|
+
className={
|
|
206
|
+
itineraryStepChanged?.[i] && highlightVariant === 'new'
|
|
207
|
+
? 'font-semibold text-stone-900'
|
|
208
|
+
: undefined
|
|
209
|
+
}
|
|
210
|
+
>
|
|
211
|
+
{step.label}
|
|
212
|
+
</span>
|
|
213
|
+
)}
|
|
214
|
+
</li>
|
|
215
|
+
))}
|
|
216
|
+
</ul>
|
|
217
|
+
) : (
|
|
218
|
+
<p className={`${styles.value} mt-1 text-stone-500`}>—</p>
|
|
219
|
+
)}
|
|
220
|
+
</div>
|
|
221
|
+
|
|
222
|
+
<div className={styles.totalRow}>
|
|
223
|
+
<span className={styles.totalLabel}>Total:</span>
|
|
224
|
+
<span className={styles.totalAmount}>{totalFormatted}</span>
|
|
225
|
+
</div>
|
|
226
|
+
</div>
|
|
227
|
+
);
|
|
228
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Currency } from './CurrencySwitcher';
|
|
2
|
+
|
|
3
|
+
/** Payload passed to `onChangeBooking` when applying a dashboard-managed booking change. */
|
|
4
|
+
export type ProviderDashboardChangeBookingPayload = {
|
|
5
|
+
productId: string;
|
|
6
|
+
dateTime: string;
|
|
7
|
+
bookingItems: Array<{ category: string; count: number }>;
|
|
8
|
+
returnAvailabilityId?: string | null;
|
|
9
|
+
pickupLocationId?: string | null;
|
|
10
|
+
travelerHotel?: string | null;
|
|
11
|
+
startTime?: string | null;
|
|
12
|
+
passengerCount?: number | null;
|
|
13
|
+
childSafetySeatsCount?: number | null;
|
|
14
|
+
foodRestrictions?: string | null;
|
|
15
|
+
addOnSelections?: Array<{ addOnId: string; variantId?: string; quantity?: number }> | null;
|
|
16
|
+
cancellationPolicyId?: string | null;
|
|
17
|
+
promoCode?: string | null;
|
|
18
|
+
newTotalAmount?: number;
|
|
19
|
+
additionalHoursCount?: number | null;
|
|
20
|
+
pricingAdjustment?: {
|
|
21
|
+
mode: 'AUTO' | 'MANUAL_LINES';
|
|
22
|
+
lineOverrides?: Array<{ lineKey: string; amount: number; reason?: string }>;
|
|
23
|
+
additionalAdjustments?: Array<{ label: string; amount: number }>;
|
|
24
|
+
} | null;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/** Seeds the flow when opening provider change-booking (matches TicketBooth `BookingWidget` `initialBooking`). */
|
|
28
|
+
export type ProviderDashboardInitialBooking = {
|
|
29
|
+
bookingReference: string;
|
|
30
|
+
productId: string;
|
|
31
|
+
availabilityId?: string;
|
|
32
|
+
dateTime: string;
|
|
33
|
+
originalTotalAmount?: number;
|
|
34
|
+
originalCurrency?: Currency | string;
|
|
35
|
+
originalPromoAmount?: number;
|
|
36
|
+
originalPromoLabel?: string | null;
|
|
37
|
+
bookingItems: Array<{ category: string; count: number }>;
|
|
38
|
+
returnAvailabilityId?: string | null;
|
|
39
|
+
pickupLocationId?: string | null;
|
|
40
|
+
travelerHotel?: string | null;
|
|
41
|
+
startTime?: string | null;
|
|
42
|
+
privateShuttleDetails?: { passengerCount?: number };
|
|
43
|
+
cancellationPolicyId?: string | null;
|
|
44
|
+
promoCode?: string | null;
|
|
45
|
+
additionalHoursCount?: number | null;
|
|
46
|
+
addOnSelections?: Array<{ addOnId: string; variantId?: string; quantity?: number }> | null;
|
|
47
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -13,11 +13,30 @@ export {
|
|
|
13
13
|
|
|
14
14
|
/** Canonical Via Via booking UI — same modules as `@/components/booking/*` on the site. */
|
|
15
15
|
export { BookingFlow } from './components/booking/BookingFlow';
|
|
16
|
+
export type { ChangeFlowSelectionPreview } from './components/booking/BookingFlow';
|
|
16
17
|
export { PrivateShuttleBookingFlow } from './components/booking/PrivateShuttleBookingFlow';
|
|
17
18
|
export type { BookingFlowUiOptions } from './components/booking/booking-flow-ui';
|
|
19
|
+
export type {
|
|
20
|
+
ProviderDashboardChangeBookingPayload,
|
|
21
|
+
ProviderDashboardInitialBooking,
|
|
22
|
+
} from './components/booking/provider-dashboard-change-booking';
|
|
23
|
+
export {
|
|
24
|
+
BookingChangeSummaryColumn,
|
|
25
|
+
changeBookingCompareColumnsVisuallyMatch,
|
|
26
|
+
computeItineraryStepChanged,
|
|
27
|
+
formatBookingDateForChangeCompare,
|
|
28
|
+
formatBookingItemsForCompare,
|
|
29
|
+
itinerariesEqualForChangeCompare,
|
|
30
|
+
} from './components/booking/change-booking-compare';
|
|
31
|
+
export type {
|
|
32
|
+
ItineraryStepLine,
|
|
33
|
+
ChangeHighlightVariant,
|
|
34
|
+
} from './components/booking/change-booking-compare';
|
|
35
|
+
export { getItineraryStepLabel } from './lib/booking/itinerary-display';
|
|
18
36
|
export { PARTNER_EMBEDDED_BOOKING_FLOW_UI_BASE } from './components/booking/booking-flow-ui';
|
|
19
37
|
export { default as BookingDialog } from './components/booking/BookingDialog';
|
|
20
38
|
export { default as BookingProductGrid } from './components/booking/BookingProductGrid';
|
|
39
|
+
export { DefaultTermsContent as TermsContent } from './components/booking/DefaultTermsContent';
|
|
21
40
|
|
|
22
41
|
export type {
|
|
23
42
|
BookingAppMode,
|
package/src/runtime/types.ts
CHANGED
|
@@ -51,7 +51,8 @@ export interface BookingRuntimeSlots {
|
|
|
51
51
|
ProductTag: BookingSlotComponent;
|
|
52
52
|
PillVariant: unknown;
|
|
53
53
|
ImageModal: BookingSlotComponent;
|
|
54
|
-
|
|
54
|
+
/** Optional override; package supplies a full default terms component when omitted. */
|
|
55
|
+
TermsContent?: BookingSlotComponent;
|
|
55
56
|
PlusIcon: ComponentType<SVGProps<SVGSVGElement>>;
|
|
56
57
|
MinusIcon: ComponentType<SVGProps<SVGSVGElement>>;
|
|
57
58
|
/** Via Via `ButtonHoverColor` enum from `@/components/button`. */
|