@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
|
@@ -195,11 +195,16 @@
|
|
|
195
195
|
border: 1px solid var(--cal-dropdown-border);
|
|
196
196
|
border-radius: var(--cal-dropdown-radius);
|
|
197
197
|
box-shadow: var(--cal-dropdown-shadow);
|
|
198
|
-
z-index:
|
|
198
|
+
z-index: 10060;
|
|
199
199
|
padding: var(--cal-dropdown-padding);
|
|
200
200
|
min-width: var(--cal-dropdown-min-width);
|
|
201
201
|
max-height: var(--cal-dropdown-max-height);
|
|
202
202
|
overflow-y: auto;
|
|
203
|
+
pointer-events: auto;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.calendarDropdown * {
|
|
207
|
+
pointer-events: auto;
|
|
203
208
|
}
|
|
204
209
|
|
|
205
210
|
.calendarDropdownGridWrapper {
|
|
@@ -13,6 +13,14 @@ import './booking-flow.css';
|
|
|
13
13
|
import type { BookingData } from '../../components/BookingDetails';
|
|
14
14
|
import { getItineraryStepLabel } from '../../lib/booking/itinerary-display';
|
|
15
15
|
import { effectiveProductOptionIdForChangeFlow } from '../../lib/booking/product-option-id';
|
|
16
|
+
import {
|
|
17
|
+
BookingChangeSummaryColumn,
|
|
18
|
+
changeBookingCompareColumnsVisuallyMatch,
|
|
19
|
+
computeItineraryStepChanged,
|
|
20
|
+
formatBookingDateForChangeCompare,
|
|
21
|
+
formatBookingItemsForCompare,
|
|
22
|
+
type ItineraryStepLine,
|
|
23
|
+
} from './change-booking-compare';
|
|
16
24
|
|
|
17
25
|
interface ChangeBookingDialogProps {
|
|
18
26
|
isOpen: boolean;
|
|
@@ -21,42 +29,6 @@ interface ChangeBookingDialogProps {
|
|
|
21
29
|
onChangeCompleted?: (preview: ChangeFlowSelectionPreview | null) => void;
|
|
22
30
|
}
|
|
23
31
|
|
|
24
|
-
function formatBookingDate(dateTime: string | null | undefined): string {
|
|
25
|
-
if (!dateTime) return '—';
|
|
26
|
-
try {
|
|
27
|
-
const date = new Date(dateTime);
|
|
28
|
-
return date.toLocaleDateString('en-US', {
|
|
29
|
-
weekday: 'short',
|
|
30
|
-
year: 'numeric',
|
|
31
|
-
month: 'short',
|
|
32
|
-
day: 'numeric',
|
|
33
|
-
timeZone: 'America/Denver',
|
|
34
|
-
});
|
|
35
|
-
} catch {
|
|
36
|
-
return dateTime;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function formatBookingItems(
|
|
41
|
-
items: Array<{ category: string; count: number }> | null | undefined
|
|
42
|
-
): string {
|
|
43
|
-
if (!items?.length) return '—';
|
|
44
|
-
const labels: Record<string, string> = {
|
|
45
|
-
ADULT: 'adult',
|
|
46
|
-
CHILD: 'child',
|
|
47
|
-
INFANT: 'infant',
|
|
48
|
-
SENIOR: 'senior',
|
|
49
|
-
STUDENT: 'student',
|
|
50
|
-
};
|
|
51
|
-
const parts = items
|
|
52
|
-
.filter((item) => item.count > 0)
|
|
53
|
-
.map((item) => {
|
|
54
|
-
const label = labels[item.category] || item.category.toLowerCase();
|
|
55
|
-
return `${item.count} ${label}${item.count !== 1 ? 's' : ''}`;
|
|
56
|
-
});
|
|
57
|
-
return parts.length > 0 ? parts.join(', ') : '—';
|
|
58
|
-
}
|
|
59
|
-
|
|
60
32
|
function getOriginalPromoFromReceipt(receipt: BookingData['receipt']): {
|
|
61
33
|
amount: number;
|
|
62
34
|
label: string | null;
|
|
@@ -88,141 +60,6 @@ function getOriginalPromoFromReceipt(receipt: BookingData['receipt']): {
|
|
|
88
60
|
return { amount, label: line.label || receipt.promoCode || receipt.promoName || null, code };
|
|
89
61
|
}
|
|
90
62
|
|
|
91
|
-
type ItineraryStepLine = { time: string | null; label: string };
|
|
92
|
-
|
|
93
|
-
type ChangeHighlightVariant = 'current' | 'new';
|
|
94
|
-
|
|
95
|
-
function BookingChangeSummaryColumn({
|
|
96
|
-
kicker,
|
|
97
|
-
tourName,
|
|
98
|
-
dateStr,
|
|
99
|
-
ticketsStr,
|
|
100
|
-
itinerarySteps,
|
|
101
|
-
highlightVariant,
|
|
102
|
-
totalFormatted,
|
|
103
|
-
dateChanged = false,
|
|
104
|
-
ticketsChanged = false,
|
|
105
|
-
itineraryStepChanged,
|
|
106
|
-
}: {
|
|
107
|
-
kicker: string;
|
|
108
|
-
tourName: string;
|
|
109
|
-
dateStr: string;
|
|
110
|
-
ticketsStr: string;
|
|
111
|
-
itinerarySteps: ItineraryStepLine[] | null;
|
|
112
|
-
highlightVariant: ChangeHighlightVariant;
|
|
113
|
-
/** e.g. "C$123.45" */
|
|
114
|
-
totalFormatted: string;
|
|
115
|
-
dateChanged?: boolean;
|
|
116
|
-
ticketsChanged?: boolean;
|
|
117
|
-
itineraryStepChanged?: boolean[];
|
|
118
|
-
}) {
|
|
119
|
-
const dateClass =
|
|
120
|
-
highlightVariant === 'current' && dateChanged
|
|
121
|
-
? 'line-through text-stone-500'
|
|
122
|
-
: highlightVariant === 'new' && dateChanged
|
|
123
|
-
? 'font-semibold text-stone-900'
|
|
124
|
-
: '';
|
|
125
|
-
const ticketsClass =
|
|
126
|
-
highlightVariant === 'current' && ticketsChanged
|
|
127
|
-
? 'line-through text-stone-500'
|
|
128
|
-
: highlightVariant === 'new' && ticketsChanged
|
|
129
|
-
? 'font-semibold text-stone-900'
|
|
130
|
-
: '';
|
|
131
|
-
|
|
132
|
-
return (
|
|
133
|
-
<div
|
|
134
|
-
className="overflow-hidden rounded-lg border border-stone-200 px-4 py-3 text-sm text-stone-600"
|
|
135
|
-
style={{ background: 'var(--light-orange-background)' }}
|
|
136
|
-
>
|
|
137
|
-
<p className="text-xs font-semibold uppercase tracking-wide text-stone-500">{kicker}</p>
|
|
138
|
-
<p className="mt-1 text-base font-semibold text-stone-900">Tour: {tourName}</p>
|
|
139
|
-
<dl className="mt-3 space-y-2">
|
|
140
|
-
<div className="flex flex-wrap gap-x-2 gap-y-0">
|
|
141
|
-
<dt className="shrink-0 font-medium text-stone-500">Date:</dt>
|
|
142
|
-
<dd className="min-w-0 text-stone-800">
|
|
143
|
-
{dateClass ? <span className={dateClass}>{dateStr}</span> : dateStr}
|
|
144
|
-
</dd>
|
|
145
|
-
</div>
|
|
146
|
-
<div className="flex flex-wrap gap-x-2 gap-y-0">
|
|
147
|
-
<dt className="shrink-0 font-medium text-stone-500">Tickets:</dt>
|
|
148
|
-
<dd className="min-w-0 text-stone-800">
|
|
149
|
-
{ticketsClass ? <span className={ticketsClass}>{ticketsStr}</span> : ticketsStr}
|
|
150
|
-
</dd>
|
|
151
|
-
</div>
|
|
152
|
-
<div>
|
|
153
|
-
<dt className="font-medium text-stone-500">Itinerary:</dt>
|
|
154
|
-
<dd className="mt-1">
|
|
155
|
-
{itinerarySteps && itinerarySteps.length > 0 ? (
|
|
156
|
-
<ul className="list-none pl-0 text-sm leading-snug text-stone-700">
|
|
157
|
-
{itinerarySteps.map((step, i) => (
|
|
158
|
-
<li
|
|
159
|
-
key={i}
|
|
160
|
-
className={
|
|
161
|
-
itineraryStepChanged?.[i]
|
|
162
|
-
? highlightVariant === 'current'
|
|
163
|
-
? 'min-w-0 line-through text-stone-500'
|
|
164
|
-
: 'min-w-0 font-semibold text-stone-900'
|
|
165
|
-
: 'min-w-0'
|
|
166
|
-
}
|
|
167
|
-
>
|
|
168
|
-
{step.time ? (
|
|
169
|
-
<>
|
|
170
|
-
<span
|
|
171
|
-
className={
|
|
172
|
-
itineraryStepChanged?.[i]
|
|
173
|
-
? highlightVariant === 'current'
|
|
174
|
-
? 'font-medium text-stone-500'
|
|
175
|
-
: 'font-semibold text-stone-900'
|
|
176
|
-
: 'font-medium text-stone-800'
|
|
177
|
-
}
|
|
178
|
-
>
|
|
179
|
-
{step.time}
|
|
180
|
-
</span>
|
|
181
|
-
<span className={itineraryStepChanged?.[i] && highlightVariant === 'new' ? 'text-stone-400' : 'text-stone-500'}>
|
|
182
|
-
{' '}
|
|
183
|
-
·{' '}
|
|
184
|
-
</span>
|
|
185
|
-
<span className={itineraryStepChanged?.[i] && highlightVariant === 'new' ? 'font-semibold text-stone-900' : undefined}>
|
|
186
|
-
{step.label}
|
|
187
|
-
</span>
|
|
188
|
-
</>
|
|
189
|
-
) : (
|
|
190
|
-
<span
|
|
191
|
-
className={
|
|
192
|
-
itineraryStepChanged?.[i] && highlightVariant === 'new' ? 'font-semibold text-stone-900' : undefined
|
|
193
|
-
}
|
|
194
|
-
>
|
|
195
|
-
{step.label}
|
|
196
|
-
</span>
|
|
197
|
-
)}
|
|
198
|
-
</li>
|
|
199
|
-
))}
|
|
200
|
-
</ul>
|
|
201
|
-
) : (
|
|
202
|
-
<span className="text-sm text-stone-500">—</span>
|
|
203
|
-
)}
|
|
204
|
-
</dd>
|
|
205
|
-
</div>
|
|
206
|
-
</dl>
|
|
207
|
-
<div className="mt-3 flex flex-wrap items-baseline justify-between gap-x-2 border-t border-stone-200/80 pt-3">
|
|
208
|
-
<span className="font-medium text-stone-500">Total:</span>
|
|
209
|
-
<span className="font-semibold tabular-nums text-stone-900">{totalFormatted}</span>
|
|
210
|
-
</div>
|
|
211
|
-
</div>
|
|
212
|
-
);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
function computeItineraryStepChanged(
|
|
216
|
-
stepsA: ItineraryStepLine[] | null,
|
|
217
|
-
stepsB: ItineraryStepLine[] | null,
|
|
218
|
-
): boolean[] | undefined {
|
|
219
|
-
if (!stepsA) return undefined;
|
|
220
|
-
return stepsA.map((a, i) => {
|
|
221
|
-
const b = stepsB?.[i];
|
|
222
|
-
return !b || a.time !== b.time || a.label !== b.label;
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
|
|
226
63
|
function getFocusableElements(container: HTMLElement): HTMLElement[] {
|
|
227
64
|
const selector =
|
|
228
65
|
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
|
|
@@ -381,7 +218,7 @@ export default function ChangeBookingDialog({
|
|
|
381
218
|
const ticketsLine =
|
|
382
219
|
booking.productType === 'PRIVATE_SHUTTLE'
|
|
383
220
|
? `${booking.privateShuttleDetails?.requestedPassengerCount ?? booking.privateShuttleDetails?.passengerCount ?? 0} passengers`
|
|
384
|
-
:
|
|
221
|
+
: formatBookingItemsForCompare(booking.bookingItems);
|
|
385
222
|
|
|
386
223
|
const currentItinerarySteps: ItineraryStepLine[] | null =
|
|
387
224
|
booking.itineraryDisplay && booking.itineraryDisplay.length > 0
|
|
@@ -391,8 +228,9 @@ export default function ChangeBookingDialog({
|
|
|
391
228
|
}))
|
|
392
229
|
: null;
|
|
393
230
|
|
|
231
|
+
const currentDateStr = formatBookingDateForChangeCompare(booking.dateTime);
|
|
394
232
|
const newDateStr = newBookingPreview?.dateTime
|
|
395
|
-
?
|
|
233
|
+
? formatBookingDateForChangeCompare(newBookingPreview.dateTime)
|
|
396
234
|
: '—';
|
|
397
235
|
const newTicketsStr = newBookingPreview?.ticketsLine ?? '—';
|
|
398
236
|
const newItinerarySteps: ItineraryStepLine[] | null =
|
|
@@ -400,9 +238,6 @@ export default function ChangeBookingDialog({
|
|
|
400
238
|
? newBookingPreview.itinerarySteps
|
|
401
239
|
: null;
|
|
402
240
|
|
|
403
|
-
const showNewBookingColumn = Boolean(newBookingPreview?.hasChangesFromInitial);
|
|
404
|
-
const dateChanged = Boolean(newBookingPreview?.dateChanged);
|
|
405
|
-
const ticketsChanged = Boolean(newBookingPreview?.ticketsChanged);
|
|
406
241
|
const currentTotalFormatted = formatCurrencyAmount(
|
|
407
242
|
booking.receipt.totalAmount,
|
|
408
243
|
receiptCurrency,
|
|
@@ -416,6 +251,28 @@ export default function ChangeBookingDialog({
|
|
|
416
251
|
'en',
|
|
417
252
|
)
|
|
418
253
|
: '—';
|
|
254
|
+
|
|
255
|
+
const visuallyMatches =
|
|
256
|
+
Boolean(newBookingPreview) &&
|
|
257
|
+
changeBookingCompareColumnsVisuallyMatch({
|
|
258
|
+
dateCurrent: currentDateStr,
|
|
259
|
+
dateNew: newDateStr,
|
|
260
|
+
ticketsCurrent: ticketsLine,
|
|
261
|
+
ticketsNew: newTicketsStr,
|
|
262
|
+
itineraryCurrent: currentItinerarySteps,
|
|
263
|
+
itineraryNew: newItinerarySteps,
|
|
264
|
+
totalCurrent: currentTotalFormatted,
|
|
265
|
+
totalNew: newTotalFormatted,
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
const showNewBookingColumn = Boolean(
|
|
269
|
+
newBookingPreview?.hasChangesFromInitial && !visuallyMatches,
|
|
270
|
+
);
|
|
271
|
+
const dateChanged =
|
|
272
|
+
Boolean(newBookingPreview?.dateChanged) && currentDateStr !== newDateStr;
|
|
273
|
+
const ticketsChanged =
|
|
274
|
+
Boolean(newBookingPreview?.ticketsChanged) && ticketsLine !== newTicketsStr;
|
|
275
|
+
|
|
419
276
|
const itineraryStepChangedCurrent = showNewBookingColumn
|
|
420
277
|
? computeItineraryStepChanged(currentItinerarySteps, newItinerarySteps)
|
|
421
278
|
: undefined;
|
|
@@ -482,7 +339,7 @@ export default function ChangeBookingDialog({
|
|
|
482
339
|
{product && (
|
|
483
340
|
<div className={`${styles.screen} booking-flow-preflight`}>
|
|
484
341
|
<div
|
|
485
|
-
className="sticky top-0 z-10 mb-
|
|
342
|
+
className="sticky top-0 z-10 mb-3 flex w-full flex-col gap-0 rounded-2xl py-2 md:flex-row md:items-stretch"
|
|
486
343
|
style={{ background: 'var(--light-orange-background)' }}
|
|
487
344
|
>
|
|
488
345
|
<motion.div
|
|
@@ -498,7 +355,7 @@ export default function ChangeBookingDialog({
|
|
|
498
355
|
highlightVariant="current"
|
|
499
356
|
kicker="Current booking"
|
|
500
357
|
tourName={tourName}
|
|
501
|
-
dateStr={
|
|
358
|
+
dateStr={currentDateStr}
|
|
502
359
|
ticketsStr={ticketsLine}
|
|
503
360
|
itinerarySteps={currentItinerarySteps}
|
|
504
361
|
totalFormatted={currentTotalFormatted}
|
|
@@ -507,6 +364,12 @@ export default function ChangeBookingDialog({
|
|
|
507
364
|
itineraryStepChanged={itineraryStepChangedCurrent}
|
|
508
365
|
/>
|
|
509
366
|
</motion.div>
|
|
367
|
+
{showNewBookingColumn ? (
|
|
368
|
+
<div
|
|
369
|
+
aria-hidden
|
|
370
|
+
className="booking-change-compare-divider max-md:my-3 md:my-0"
|
|
371
|
+
/>
|
|
372
|
+
) : null}
|
|
510
373
|
<AnimatePresence initial={false} mode="popLayout">
|
|
511
374
|
{showNewBookingColumn && (
|
|
512
375
|
<motion.div
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
+
import type { ReactNode } from 'react';
|
|
3
4
|
import { PriceSummary, type PriceSummaryLine } from './PriceSummary';
|
|
4
5
|
import { TermsAcceptance } from './TermsAcceptance';
|
|
5
6
|
import { PickupLocationSelector } from './PickupLocationSelector';
|
|
@@ -24,6 +25,14 @@ interface CheckoutFormProps {
|
|
|
24
25
|
extraBetweenTaxAndTotal: React.ReactNode;
|
|
25
26
|
/** Overrides PriceSummary final row label (e.g. change booking) */
|
|
26
27
|
totalSummaryLabel?: string;
|
|
28
|
+
/** Passed through to PriceSummary (e.g. provider price delta + checkbox) */
|
|
29
|
+
extraAfterTotal?: ReactNode;
|
|
30
|
+
/** Optional map + handler for inline editable amount rows in PriceSummary. */
|
|
31
|
+
lineAmountInputs?: Record<string, string>;
|
|
32
|
+
onLineAmountInputChange?: (lineKey: string, value: string) => void;
|
|
33
|
+
onLineAmountInputBlur?: (lineKey: string) => void;
|
|
34
|
+
onLineAmountReset?: (lineKey: string) => void;
|
|
35
|
+
extraBeforeSubtotal?: ReactNode;
|
|
27
36
|
// Promo (passed as extraBetweenTaxAndTotal content - we'll keep it in parent for now)
|
|
28
37
|
// Contact info
|
|
29
38
|
firstName: string;
|
|
@@ -48,6 +57,7 @@ interface CheckoutFormProps {
|
|
|
48
57
|
onTermsChange: (checked: boolean) => void;
|
|
49
58
|
// Admin
|
|
50
59
|
isAdmin: boolean;
|
|
60
|
+
showCommunicationAdminSection?: boolean;
|
|
51
61
|
skipConfirmationCommunications: boolean;
|
|
52
62
|
disableAutoCommunications: boolean;
|
|
53
63
|
onSkipConfirmationChange: (checked: boolean) => void;
|
|
@@ -78,6 +88,12 @@ export function CheckoutForm({
|
|
|
78
88
|
t,
|
|
79
89
|
extraBetweenTaxAndTotal,
|
|
80
90
|
totalSummaryLabel,
|
|
91
|
+
extraAfterTotal,
|
|
92
|
+
lineAmountInputs,
|
|
93
|
+
onLineAmountInputChange,
|
|
94
|
+
onLineAmountInputBlur,
|
|
95
|
+
onLineAmountReset,
|
|
96
|
+
extraBeforeSubtotal,
|
|
81
97
|
firstName,
|
|
82
98
|
lastName,
|
|
83
99
|
email,
|
|
@@ -97,6 +113,7 @@ export function CheckoutForm({
|
|
|
97
113
|
termsAccepted,
|
|
98
114
|
onTermsChange,
|
|
99
115
|
isAdmin,
|
|
116
|
+
showCommunicationAdminSection = true,
|
|
100
117
|
skipConfirmationCommunications,
|
|
101
118
|
disableAutoCommunications,
|
|
102
119
|
onSkipConfirmationChange,
|
|
@@ -136,6 +153,12 @@ export function CheckoutForm({
|
|
|
136
153
|
t={t}
|
|
137
154
|
extraBetweenTaxAndTotal={extraBetweenTaxAndTotal}
|
|
138
155
|
totalLabel={totalSummaryLabel}
|
|
156
|
+
extraAfterTotal={extraAfterTotal}
|
|
157
|
+
lineAmountInputs={lineAmountInputs}
|
|
158
|
+
onLineAmountInputChange={onLineAmountInputChange}
|
|
159
|
+
onLineAmountInputBlur={onLineAmountInputBlur}
|
|
160
|
+
onLineAmountReset={onLineAmountReset}
|
|
161
|
+
extraBeforeSubtotal={extraBeforeSubtotal}
|
|
139
162
|
/>
|
|
140
163
|
</div>
|
|
141
164
|
|
|
@@ -292,7 +315,7 @@ export function CheckoutForm({
|
|
|
292
315
|
)}
|
|
293
316
|
|
|
294
317
|
{/* Admin: communication options */}
|
|
295
|
-
{isAdmin && (
|
|
318
|
+
{isAdmin && showCommunicationAdminSection && (
|
|
296
319
|
<div className={styles.adminSection}>
|
|
297
320
|
<p className={styles.adminTitle}>Communications (admin)</p>
|
|
298
321
|
<label className={styles.adminCheckbox}>
|
|
@@ -364,9 +387,11 @@ export function CheckoutForm({
|
|
|
364
387
|
</Button>
|
|
365
388
|
</div>
|
|
366
389
|
)}
|
|
367
|
-
|
|
368
|
-
{
|
|
369
|
-
|
|
390
|
+
{!hideSubmitButton && (
|
|
391
|
+
<p className={styles.secureNote}>
|
|
392
|
+
{t('booking.securePayment')}
|
|
393
|
+
</p>
|
|
394
|
+
)}
|
|
370
395
|
</div>
|
|
371
396
|
);
|
|
372
397
|
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shared terms content bundled in @ticketboothapp/booking so host apps get
|
|
5
|
+
* full Terms & Conditions text by default without custom slot wiring.
|
|
6
|
+
*/
|
|
7
|
+
export function DefaultTermsContent({ className = '' }: { className?: string }) {
|
|
8
|
+
return (
|
|
9
|
+
<div className={`text-sm text-stone-600 [&_p]:mb-3 [&_p:last-child]:mb-0 ${className}`}>
|
|
10
|
+
<h1 className="text-xl font-bold text-stone-900 mb-4">Via Via Moraine Lake Shuttle</h1>
|
|
11
|
+
|
|
12
|
+
<h2 className="text-base font-semibold text-stone-900 mt-6 mb-2">Terms & Conditions</h2>
|
|
13
|
+
<p>
|
|
14
|
+
Via Via Moraine Lake Shuttle is owned and operated by Via Via Shuttle Service Inc. By purchasing a ticket, you enter into an agreement with Via Via Shuttle Service Inc. and acknowledge that you have read, understood, and agreed to the terms and conditions outlined herein. If you make a booking on behalf of other passengers, you confirm that you have the legal authority to accept these terms on behalf of all individuals in your booking party.
|
|
15
|
+
</p>
|
|
16
|
+
<p>
|
|
17
|
+
These terms and conditions govern the agreement between you and Via Via Shuttle Service Inc., including important provisions regarding your legal rights and limitations of liability. Please review them carefully before booking your tickets.
|
|
18
|
+
</p>
|
|
19
|
+
|
|
20
|
+
<h2 className="text-base font-semibold text-stone-900 mt-6 mb-2">Prices</h2>
|
|
21
|
+
<p>
|
|
22
|
+
All of our prices are displayed in Canadian Dollars (CAD) and do not include applicable taxes and fees (including GST and a service charge), which are added during checkout, as well as a $15.99 contribution towards the per-trip Parks Canada Moraine Lake Road License of Occupation.
|
|
23
|
+
</p>
|
|
24
|
+
|
|
25
|
+
<h2 className="text-base font-semibold text-stone-900 mt-6 mb-2">Payments, fees, and changes</h2>
|
|
26
|
+
<p>
|
|
27
|
+
Payments must be made in Canadian Funds. Via Via Shuttle Service Inc. accepts Visa, MasterCard, and American Express. Payments are securely processed through our booking partner, FareHarbor. For more specific details regarding privacy policies, please visit their website here
|
|
28
|
+
</p>
|
|
29
|
+
|
|
30
|
+
<h2 className="text-base font-semibold text-stone-900 mt-6 mb-2">Cancellations & refunds</h2>
|
|
31
|
+
|
|
32
|
+
<h3 className="text-sm font-semibold text-stone-800 mt-6 mb-2">Weather conditions</h3>
|
|
33
|
+
<p>
|
|
34
|
+
When booking seats for regular round-trip tours, private shuttle experiences, or direct shuttle services with Via Via Shuttle Service Inc., you're reserving seats for specific dates and times. Our shuttles run rain, snow, smoke, or shine — no matter the weather or temperatures. As such, cancellations due to weather conditions are not accepted. While we can't control the weather, we can help you prepare — check out our Frequently Asked Questions page for tips on what to expect and how to make the most of your adventure in the Rockies.
|
|
35
|
+
</p>
|
|
36
|
+
|
|
37
|
+
<h3 className="text-sm font-semibold text-stone-800 mt-6 mb-2">Flexible Cancellation Policy</h3>
|
|
38
|
+
<p>
|
|
39
|
+
Via Via Shuttle Service Inc. offers a flexible cancellation policy for regular shuttle services (excluding private shuttles). Generally, cancellations made up to 7 days before departure are eligible for a full refund, OR the specifications of the cancellation policy you purchase at checkout will be honored. Refunds will be automatically processed to the original payment method.
|
|
40
|
+
</p>
|
|
41
|
+
<p>
|
|
42
|
+
Changes to the booking date or time can be made up to 72 hours before departure (subject to availability). If the booking is rescheduled to a date with a lower advertised price than the original booking, the original price will be maintained and no partial refund will be issued. If no suitable alternative date is available, a refund will not be possible.
|
|
43
|
+
</p>
|
|
44
|
+
<p>
|
|
45
|
+
Refunds will not be issued for cancellations received less than 7 days before the scheduled departure time. This applies to all shuttle tickets (excluding private shuttles).
|
|
46
|
+
</p>
|
|
47
|
+
<p>
|
|
48
|
+
Cancellations and rescheduling requests must be submitted via phone at +1 587 907 4560 or email at info@viaviamorainelake.com.
|
|
49
|
+
</p>
|
|
50
|
+
|
|
51
|
+
<h3 className="text-sm font-semibold text-stone-800 mt-6 mb-2">Private Shuttle Cancellation Policy</h3>
|
|
52
|
+
<p>
|
|
53
|
+
For private shuttle experiences, Via Via Shuttle Service Inc. applies a different cancellation policy. Once the booking is confirmed, the deposit is non-refundable.
|
|
54
|
+
</p>
|
|
55
|
+
<p>
|
|
56
|
+
Cancellations made up to 14 days before departure are eligible for a full refund, excluding the non-refundable deposit. Cancellations made between 14 and 7 days before departure will receive a 50% refund. Cancellations made less than 7 days before departure are non-refundable.
|
|
57
|
+
</p>
|
|
58
|
+
<p>
|
|
59
|
+
Changes to the booking date or time can be made up to 72 hours before departure (subject to availability). If no suitable alternative date is available, a refund will not be possible.
|
|
60
|
+
</p>
|
|
61
|
+
<p>
|
|
62
|
+
To cancel or reschedule your private shuttle experience, please contact us via phone at +1 587 907 4560 or email at info@viaviamorainelake.com.
|
|
63
|
+
</p>
|
|
64
|
+
|
|
65
|
+
<h3 className="text-sm font-semibold text-stone-800 mt-6 mb-2">Cancellation by operator</h3>
|
|
66
|
+
<p>
|
|
67
|
+
In the event of a cancellation of any service (private or regular) initiated by Via Via Moraine Lake Shuttle, operated by Via Via Shuttle Service Inc., passengers will be offered either a full refund or the option to select an alternative date. Cancellations made by us will be communicated to affected passengers prior to the scheduled departure.
|
|
68
|
+
</p>
|
|
69
|
+
<p>
|
|
70
|
+
If a trip is canceled during its course, affected passengers will be notified promptly. Such cancellations may occur due to vehicle malfunctions or accidents. However, cancellations caused by traffic delays or passenger-related issues do not qualify for refunds. In all cases, the safety of our passengers is our top priority, and our employees are entrusted to make informed decisions on whether to continue or pause the journey until conditions allow for safe travel.
|
|
71
|
+
</p>
|
|
72
|
+
|
|
73
|
+
<h3 className="text-sm font-semibold text-stone-800 mt-6 mb-2">Cancellation by customer</h3>
|
|
74
|
+
<p>
|
|
75
|
+
If you need to cancel your booking, please contact us as soon as possible. Cancellations made up to 7 days before departure are eligible for a full refund, minus a $5 cancellation fee per booking to cover non-recoverable transaction costs. Refunds will be automatically processed to the original payment method.
|
|
76
|
+
</p>
|
|
77
|
+
|
|
78
|
+
<h2 className="text-base font-semibold text-stone-900 mt-6 mb-2">Tour Confirmation Policy</h2>
|
|
79
|
+
<p>
|
|
80
|
+
For some of our tours and services, we require a minimum of 5 bookings to confirm the tour. This requirement will be clearly displayed at checkout. If the minimum number of bookings is not met, we will contact you directly to offer an alternative date for your tour. If no suitable alternative date can be found, we will issue a full refund for your booking.
|
|
81
|
+
</p>
|
|
82
|
+
<p>
|
|
83
|
+
If you see 8 or fewer seats available in the calendar, it means the tour has reached its required bookings and will run as scheduled. If more than 8 seats are available, the tour is still pending confirmation.
|
|
84
|
+
</p>
|
|
85
|
+
<p>
|
|
86
|
+
Please note that this policy applies only to certain tours and some times of the season and may not always apply. We are committed to providing the best experience possible and will work with you to find a suitable solution if the tour cannot be confirmed.
|
|
87
|
+
</p>
|
|
88
|
+
|
|
89
|
+
<h2 className="text-base font-semibold text-stone-900 mt-6 mb-2">Shuttle Departure & Return Policy</h2>
|
|
90
|
+
<p>
|
|
91
|
+
The departure time for your designated pick-up location is clearly stated in your email booking confirmation, as well as on our website and booking platform. Passengers must board at the location selected in their booking. If you wish to change your pick-up location, you must contact Via Via Shuttle Service Inc. at least 24 hours prior to departure and receive confirmation. Our drivers use a passenger list with exact pick-up locations, and the shuttle will not stop at locations without passengers booked to save time.
|
|
92
|
+
</p>
|
|
93
|
+
<p>
|
|
94
|
+
Pick-up times vary by location, so please refer to your email confirmation for the exact departure time. We recommend allowing up to 15 minutes for the shuttle's arrival in case of unforeseen delays or traffic conditions.
|
|
95
|
+
</p>
|
|
96
|
+
<p>
|
|
97
|
+
You are required to be ready to board at least 5 minutes before the scheduled departure time. If you are not ready, the shuttle will depart without you, and you will be marked as a no-show with no refunds or credits issued. The shuttle will not wait for any passengers under any circumstances, and contacting us will not change this.
|
|
98
|
+
</p>
|
|
99
|
+
<p>
|
|
100
|
+
To stay informed, we recommend opting in for text and/or email notifications when finalizing your booking. This will allow you to receive updates about your shuttle's status and confirm when it is on its way. Opting in will not result in any spam or promotional messages; the notifications are solely for keeping you informed about your shuttle departure.
|
|
101
|
+
</p>
|
|
102
|
+
<p>
|
|
103
|
+
The return time listed on your itinerary or booking confirmation is the time you must be at the shuttle for your return trip. Return times may differ from your original booking if communicated differently by your driver-guide upon arrival. Return times are not the same for all passengers, as there are options to pre-book different combinations of locations. Returning on a later shuttle is only possible if pre-booked and confirmed by Via Via Shuttle Service Inc. Changes to your shuttle or tour type cannot be made on the day of travel.
|
|
104
|
+
</p>
|
|
105
|
+
<p>
|
|
106
|
+
If you miss your return shuttle, it is your responsibility to arrange alternative transportation back to your accommodation. Via Via Shuttle Service Inc. will not cover any costs related to this, nor are we liable for any incidents or damages that may occur while you arrange your own transportation.
|
|
107
|
+
</p>
|
|
108
|
+
<p>
|
|
109
|
+
Shuttle times provided on your booking confirmation and our website are for reference only, as our service operates on an internal schedule that may vary slightly. If you experience a delay due to unforeseen circumstances beyond our control, it will not be considered a cancellation on our part. If you arrive after the scheduled departure time, please allow 15-20 minutes for our shuttle to arrive. If the shuttle does not arrive within 20 minutes, it means the shuttle has already departed.
|
|
110
|
+
</p>
|
|
111
|
+
<p>
|
|
112
|
+
It is your responsibility to return to the shuttle on time after your visit. If you miss the shuttle at locations like Moraine Lake or Lake Louise, you must arrange your own transportation back to your pick-up location at your own expense. Please note that there may be limited or no cell service at some locations in Banff National Park. Our drivers maintain passenger lists for each shuttle, and missed departures will not be accommodated on a later shuttle.
|
|
113
|
+
</p>
|
|
114
|
+
<p>
|
|
115
|
+
Via Via Shuttle Service Inc. is not responsible for missed departures or delayed returns. By booking a seat on our shuttles, you acknowledge your responsibility to adhere to the specified times, and failure to do so may result in financial and logistical challenges in securing alternative transportation.
|
|
116
|
+
</p>
|
|
117
|
+
|
|
118
|
+
<h2 className="text-base font-semibold text-stone-900 mt-6 mb-2">Travel Policies</h2>
|
|
119
|
+
<p>
|
|
120
|
+
Children under the age of 6 are required by law in Alberta to be seated in a child safety seat.
|
|
121
|
+
</p>
|
|
122
|
+
<p>
|
|
123
|
+
For children aged 2-6 years, a front-facing child safety seat is mandatory. We can provide a limited number of front-facing safety seats for children aged 2 years and up or weighing +40 lbs (18 kg). For children younger than 2 years old, or those weighing less than the indicated weight, a rear-facing safety seat is required, which must be provided by the customer(s). Communication with Via Via Shuttle Service Inc. at least 24 hours prior to departure is necessary to arrange the use of a front-facing safety seat. Tickets and seats must be booked for children of all ages, regardless of whether they are occupying a safety seat.
|
|
124
|
+
</p>
|
|
125
|
+
<p>
|
|
126
|
+
It is the responsibility of our customers to provide a child safety seat where required. Our drivers are authorized to decline boarding if a child safety seat is required but not provided. In such instances, passengers unable to board due to this requirement will be considered "missed," and no refund will be issued.
|
|
127
|
+
</p>
|
|
128
|
+
<p>
|
|
129
|
+
Under no circumstances are passengers permitted to hold children of any age on their lap or person. Guests under the age of 18 must be accompanied by an adult.
|
|
130
|
+
</p>
|
|
131
|
+
|
|
132
|
+
<h2 className="text-base font-semibold text-stone-900 mt-6 mb-2">Force Majeure</h2>
|
|
133
|
+
<p>
|
|
134
|
+
If Via Via Shuttle Service Inc. operating as Via Via Moraine Lake Shuttle are hindered or restricted, directly or indirectly, from fulfilling any or all of their obligations under these terms and conditions due to causes beyond the reasonable control of the affected party, including but not limited to war, civil commotion, riot, strikes, lockouts, fire, explosion, floods, and acts of God (collectively referred to as a "Force Majeure" event), Via Via Shuttle Service Inc. shall be excused from its obligations for the duration of the Force Majeure event.
|
|
135
|
+
</p>
|
|
136
|
+
<p>
|
|
137
|
+
In such instances, Via Via Shuttle Service Inc. shall not be held liable for any delays or failure in the performance of its obligations, nor for any loss or damages suffered by the other party as a result of such delay or failure. However, written notice of the inability to perform due to the Force Majeure event must be provided by Via Via Shuttle Service Inc. within 48 hours of the event's commencement.
|
|
138
|
+
</p>
|
|
139
|
+
|
|
140
|
+
<h2 className="text-base font-semibold text-stone-900 mt-6 mb-2">Applicable Law</h2>
|
|
141
|
+
<p>
|
|
142
|
+
This contract, governed by these terms and conditions, is subject to the laws of Alberta, Canada. By agreeing to these terms, you consent to the exclusive jurisdiction of the courts located in Calgary, Alberta, Canada, for the resolution of any disputes arising under these terms or related to any shuttle service, product, or service provided.
|
|
143
|
+
</p>
|
|
144
|
+
|
|
145
|
+
<h2 className="text-base font-semibold text-stone-900 mt-6 mb-2">Liability Release</h2>
|
|
146
|
+
<p>
|
|
147
|
+
By booking a ticket for the shuttle through Via Via Shuttle Service Inc. operating as Via Via Moraine Lake Shuttle, you and all participants in the shuttle service waive any claims and agree to release and indemnify Via Via Shuttle Service Inc. operating as Via Via Moraine Lake Shuttle, its employees, and agents from any and all liability or damages arising from the use of our services.
|
|
148
|
+
</p>
|
|
149
|
+
<p>
|
|
150
|
+
You acknowledge that traveling and participating in the shuttle service involves inherent risks, which may result in death, personal injury, loss or damage to property, delays, inconvenience, additional costs, or other losses.
|
|
151
|
+
</p>
|
|
152
|
+
<p>
|
|
153
|
+
Via Via Shuttle Service Inc. operating as Via Via Moraine Lake Shuttle, along with its employees and agents, shall not be responsible for any misrepresentation or omission regarding the suitability of any shuttle service, health or insurance matters, or any other related issues.
|
|
154
|
+
</p>
|
|
155
|
+
<p>
|
|
156
|
+
Additionally, Via Via Shuttle Service Inc. operating as Via Via Moraine Lake Shuttle, its employees and agents, shall not be liable for any loss or damage, including injury, death, loss of personal effects or luggage, loss of revenue or profit, costs, expenses, delays, inconvenience, loss of enjoyment, or emotional distress, regardless of the cause. This includes situations arising from the actions, omissions, or negligence of Via Via Shuttle Service Inc. operating as Via Via Moraine Lake Shuttle, its employees, agents, or other guests.
|
|
157
|
+
</p>
|
|
158
|
+
<p>
|
|
159
|
+
If any part of this Liability Release is found unenforceable for any reason, the total liability of Via Via Shuttle Service Inc. operating as Via Via Moraine Lake Shuttle will be limited to the lesser of the total price paid for the service provided.
|
|
160
|
+
</p>
|
|
161
|
+
|
|
162
|
+
<h2 className="text-base font-semibold text-stone-900 mt-6 mb-2">Insurance coverage</h2>
|
|
163
|
+
<p>
|
|
164
|
+
All participants are strongly advised to purchase medical insurance coverage and travel cancellation insurance to ensure adequate protection during their trip.
|
|
165
|
+
</p>
|
|
166
|
+
|
|
167
|
+
<h2 className="text-base font-semibold text-stone-900 mt-6 mb-2">Media Policy</h2>
|
|
168
|
+
<p>
|
|
169
|
+
Any pictures, videos, or promotional footage captured by Via Via Shuttle Service Inc. operating as Via Via Moraine Lake Shuttle, its employees, or other parties collaborating with Via Via Shuttle Service Inc. (which may include passengers) during the shuttle service shall remain the property of Via Via Shuttle Service Inc. operating as Via Via Moraine Lake Shuttle. By using our services, passengers consent to the use and disclosure of such materials for future promotional purposes.
|
|
170
|
+
</p>
|
|
171
|
+
|
|
172
|
+
<h3 className="text-sm font-semibold text-stone-800 mt-6 mb-2">Terms and Conditions update notice</h3>
|
|
173
|
+
<p>
|
|
174
|
+
Updated on Monday, March 3rd 2025 by Via Via Shuttle Service Inc., operating as Via Via Moraine Lake Shuttle. If you have any questions or concerns regarding our terms and conditions, please contact us at info@viaviamorainelake.com.
|
|
175
|
+
</p>
|
|
176
|
+
</div>
|
|
177
|
+
);
|
|
178
|
+
}
|
|
@@ -252,7 +252,7 @@ export function ItineraryBox({
|
|
|
252
252
|
<button
|
|
253
253
|
type="button"
|
|
254
254
|
onClick={handlePickupLocationClick}
|
|
255
|
-
className={styles.placeLink}
|
|
255
|
+
className={`${styles.placeLink} text-emerald-600`}
|
|
256
256
|
>
|
|
257
257
|
{getDisplayLabel(locationOnlyDisplay)}
|
|
258
258
|
</button>
|
|
@@ -284,7 +284,7 @@ export function ItineraryBox({
|
|
|
284
284
|
<button
|
|
285
285
|
type="button"
|
|
286
286
|
onClick={handlePickupLocationClick}
|
|
287
|
-
className={styles.placeLink}
|
|
287
|
+
className={`${styles.placeLink} text-emerald-600`}
|
|
288
288
|
>
|
|
289
289
|
{getDisplayLabel(locationOnlyDisplay)}
|
|
290
290
|
</button>
|