@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.
@@ -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,
@@ -51,7 +51,8 @@ export interface BookingRuntimeSlots {
51
51
  ProductTag: BookingSlotComponent;
52
52
  PillVariant: unknown;
53
53
  ImageModal: BookingSlotComponent;
54
- TermsContent: BookingSlotComponent;
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`. */