@ticketboothapp/booking 1.2.25 → 1.2.27
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 +11 -29
- package/src/components/booking/AddOnsSection.tsx +2 -2
- package/src/components/booking/AdminPaymentChoiceModal.tsx +1 -1
- package/src/components/booking/BookingDialog.tsx +31 -13
- package/src/components/booking/BookingFlow.tsx +32 -27
- package/src/components/booking/BookingFlowCollage.tsx +10 -6
- package/src/components/booking/BookingFlowPlaceholder.tsx +1 -1
- package/src/components/booking/BookingFlowPreview.tsx +18 -9
- package/src/components/booking/BookingProductGrid.tsx +55 -19
- package/src/components/booking/Calendar.module.css +19 -4
- package/src/components/booking/Calendar.tsx +13 -8
- package/src/components/booking/CancellationPolicySelector.tsx +2 -2
- package/src/components/booking/ChangeBookingDialog.tsx +22 -12
- package/src/components/booking/CheckoutForm.module.css +10 -0
- package/src/components/booking/CheckoutForm.tsx +10 -2
- package/src/components/booking/CheckoutModal.tsx +16 -14
- package/src/components/booking/DapFlowCollage.tsx +5 -2
- package/src/components/booking/DapTourDescription.tsx +4 -4
- package/src/components/booking/DependentAddOnBookingDialog.tsx +23 -16
- package/src/components/booking/DependentAddOnPaymentForm.tsx +10 -7
- package/src/components/booking/ItineraryBox.tsx +6 -6
- package/src/components/booking/ItineraryBuilder.tsx +1 -1
- package/src/components/booking/MealDrinkAddOnSelector.tsx +3 -3
- package/src/components/booking/PickupLocationSelector.tsx +20 -18
- package/src/components/booking/PickupTimeSelector.tsx +3 -3
- package/src/components/booking/PriceBreakdown.tsx +5 -5
- package/src/components/booking/PriceSummary.module.css +7 -0
- package/src/components/booking/PriceSummary.tsx +8 -7
- package/src/components/booking/PrivateShuttleBookingFlow.tsx +28 -19
- package/src/components/booking/PromoCodeInput.module.css +31 -25
- package/src/components/booking/PromoCodeInput.tsx +36 -24
- package/src/components/booking/ReturnTimeSelector.tsx +3 -3
- package/src/components/booking/TermsAcceptance.tsx +7 -2
- package/src/components/booking/TicketSelector.tsx +1 -1
- package/src/components/booking/TourDescription.tsx +11 -6
- package/src/components/booking/booking-flow.css +65 -4
- package/src/hooks/useBookingSourceMetadataFromLocation.ts +1 -1
- package/src/hooks/useIsBookingLaunchLive.ts +1 -1
- package/src/index.ts +26 -64
- package/src/providers/booking-dialog-provider.tsx +62 -53
- package/src/runtime/BookingHostContext.tsx +39 -0
- package/src/runtime/index.ts +13 -0
- package/src/runtime/types.ts +86 -0
- package/tsconfig.json +3 -5
- package/src/assets/icons/minus.svg +0 -7
- package/src/assets/icons/partner-logos/getyourguide.svg +0 -8
- package/src/assets/icons/plus.svg +0 -3
- package/src/colours.css +0 -23
- package/src/components/BookingDetails.module.css +0 -1591
- package/src/components/BookingDetails.tsx +0 -2264
- package/src/components/BookingWidget.tsx +0 -305
- package/src/components/ManageBookingView.tsx +0 -437
- package/src/components/PhoneInputWithCountry.module.css +0 -131
- package/src/components/PhoneInputWithCountry.tsx +0 -44
- package/src/components/PickupLocationDialog.module.css +0 -360
- package/src/components/PickupLocationDialog.tsx +0 -357
- package/src/components/PostBookingDependentAddOnUpsell.module.css +0 -174
- package/src/components/PostBookingDependentAddOnUpsell.tsx +0 -407
- package/src/components/button.css +0 -245
- package/src/components/button.tsx +0 -152
- package/src/components/colorable-svg.tsx +0 -29
- package/src/components/image.css +0 -29
- package/src/components/image.tsx +0 -113
- package/src/components/partner/PartnerBookingPage.module.css +0 -130
- package/src/components/partner/PartnerBookingPage.tsx +0 -390
- package/src/components/partner/PartnerBookingPageWithBrowserMetadata.tsx +0 -45
- package/src/components/product-tag.module.css +0 -30
- package/src/components/product-tag.tsx +0 -34
- package/src/components/product-theme-pages/image-modal.tsx +0 -248
- package/src/components/product-theme-pages/photo-gallery.module.css +0 -200
- package/src/components/terms/TermsContent.tsx +0 -178
- package/src/components/value-pill.module.css +0 -59
- package/src/components/value-pill.tsx +0 -46
- package/src/constants/images.ts +0 -556
- package/src/constants/pill-values.ts +0 -210
- package/src/constants/products.ts +0 -155
- package/src/contexts/AvailabilitiesCacheContext.tsx +0 -125
- package/src/contexts/CompanyContext.tsx +0 -70
- package/src/data/dap-descriptions/session-couples-families-friends.en.json +0 -61
- package/src/data/dap-descriptions/session-elopements.en.json +0 -60
- package/src/data/dap-descriptions/session-proposals.en.json +0 -60
- package/src/data/product-descriptions/afternoon-delight.en.json +0 -35
- package/src/data/product-descriptions/emerald-lake-escape.en.json +0 -68
- package/src/data/product-descriptions/lake-louise-adventure.en.json +0 -74
- package/src/data/product-descriptions/moraine-lake-adventure.en.json +0 -78
- package/src/data/product-descriptions/moraine-lake-sunrise-lake-louise-golden-hour.en.json +0 -65
- package/src/data/product-descriptions/moraine-lake-sunrise.en.json +0 -64
- package/src/data/product-descriptions/private-tour.en.json +0 -80
- package/src/data/product-descriptions/two-lakes-combo.en.json +0 -65
- package/src/data/products-config.json +0 -101
- package/src/lib/analytics.ts +0 -197
- package/src/lib/booking/booking-source.ts +0 -51
- package/src/lib/booking/checkout-breakdown.ts +0 -69
- package/src/lib/booking/correlation-id.ts +0 -46
- package/src/lib/booking/i18n/config.ts +0 -21
- package/src/lib/booking/i18n/index.tsx +0 -144
- package/src/lib/booking/i18n/messages/en.json +0 -236
- package/src/lib/booking/i18n/messages/fr.json +0 -236
- package/src/lib/booking/itinerary-display.ts +0 -36
- package/src/lib/booking/itinerary-labels.ts +0 -70
- package/src/lib/booking/location-calculations.ts +0 -43
- package/src/lib/booking/location-utils.ts +0 -165
- package/src/lib/booking/map-utils.ts +0 -153
- package/src/lib/booking/marker-icons.ts +0 -113
- package/src/lib/booking/normalize-booking-product-id.ts +0 -21
- package/src/lib/booking/pickup-location-types.ts +0 -25
- package/src/lib/booking/places-api.ts +0 -154
- package/src/lib/booking/pricing.ts +0 -466
- package/src/lib/booking/product-option-id.ts +0 -35
- package/src/lib/booking/source-metadata.ts +0 -226
- package/src/lib/booking/sunday-week.ts +0 -14
- package/src/lib/booking/theme.ts +0 -83
- package/src/lib/booking/trace-context.ts +0 -62
- package/src/lib/booking/utils.ts +0 -9
- package/src/lib/booking-api.ts +0 -1793
- package/src/lib/booking-constants.ts +0 -23
- package/src/lib/booking-ref.ts +0 -13
- package/src/lib/booking-types.ts +0 -36
- package/src/lib/currency.ts +0 -81
- package/src/lib/dap-descriptions.ts +0 -50
- package/src/lib/dap-itinerary-preview.ts +0 -315
- package/src/lib/dependent-add-on-api.ts +0 -434
- package/src/lib/env.ts +0 -96
- package/src/lib/firebase.ts +0 -20
- package/src/lib/job-application-api.ts +0 -83
- package/src/lib/manage-booking-embed-print.ts +0 -16
- package/src/lib/manage-booking-post-checkout.ts +0 -68
- package/src/lib/photo-dap-config.ts +0 -228
- package/src/lib/photo-packages.ts +0 -75
- package/src/lib/pickup/map-utils.ts +0 -56
- package/src/lib/pickup/marker-icons.ts +0 -19
- package/src/lib/product-descriptions.ts +0 -66
- package/src/lib/products-config.ts +0 -73
- package/src/providers/dependent-add-on-dialog-provider.tsx +0 -105
- package/src/radius.css +0 -5
- package/src/spacing.css +0 -7
- package/src/strings/en.json +0 -1774
- package/src/strings/es.json +0 -1573
- package/src/strings/fr.json +0 -1573
- package/src/strings/index.js +0 -23
- package/src/text-style.css +0 -56
- package/src/utils/currency-converter.ts +0 -101
package/package.json
CHANGED
|
@@ -1,44 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ticketboothapp/booking",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.27",
|
|
4
4
|
"private": false,
|
|
5
|
-
"sideEffects":
|
|
5
|
+
"sideEffects": [
|
|
6
|
+
"**/*.css",
|
|
7
|
+
"**/*.module.css"
|
|
8
|
+
],
|
|
6
9
|
"publishConfig": {
|
|
7
10
|
"access": "public"
|
|
8
11
|
},
|
|
9
|
-
"scripts": {
|
|
10
|
-
"lint": "eslint src --ext .ts,.tsx",
|
|
11
|
-
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
12
|
-
},
|
|
13
12
|
"exports": {
|
|
14
13
|
".": "./src/index.ts",
|
|
15
|
-
"./
|
|
16
|
-
"./
|
|
17
|
-
"./
|
|
18
|
-
"./
|
|
19
|
-
"./
|
|
20
|
-
|
|
21
|
-
"dependencies": {
|
|
22
|
-
"@radix-ui/react-select": "^2.2.6",
|
|
23
|
-
"@radix-ui/react-slot": "^1.2.4",
|
|
24
|
-
"@react-google-maps/api": "^2.20.7",
|
|
25
|
-
"@stripe/react-stripe-js": "^3.9.0",
|
|
26
|
-
"@stripe/stripe-js": "^7.9.0",
|
|
27
|
-
"class-variance-authority": "^0.7.1",
|
|
28
|
-
"clsx": "^2.1.1",
|
|
29
|
-
"date-fns": "^4.1.0",
|
|
30
|
-
"date-fns-tz": "^3.2.0",
|
|
31
|
-
"framer-motion": "^12.0.0",
|
|
32
|
-
"lucide-react": "^0.577.0",
|
|
33
|
-
"react-international-phone": "^4.6.0",
|
|
34
|
-
"tailwind-merge": "^3.5.0"
|
|
14
|
+
"./booking-flow.css": "./src/components/booking/booking-flow.css",
|
|
15
|
+
"./runtime": "./src/runtime/index.ts",
|
|
16
|
+
"./contexts/booking-app-context": "./src/contexts/BookingAppContext.tsx",
|
|
17
|
+
"./providers/booking-dialog-provider": "./src/providers/booking-dialog-provider.tsx",
|
|
18
|
+
"./hooks/useBookingSourceMetadataFromLocation": "./src/hooks/useBookingSourceMetadataFromLocation.ts",
|
|
19
|
+
"./hooks/useIsBookingLaunchLive": "./src/hooks/useIsBookingLaunchLive.ts"
|
|
35
20
|
},
|
|
36
21
|
"peerDependencies": {
|
|
37
22
|
"next": "^15.0.0",
|
|
38
23
|
"react": "^18.0.0 || ^19.0.0",
|
|
39
24
|
"react-dom": "^18.0.0 || ^19.0.0"
|
|
40
|
-
},
|
|
41
|
-
"devDependencies": {
|
|
42
|
-
"@types/google.maps": "^3.58.1"
|
|
43
25
|
}
|
|
44
26
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { formatCurrencyAmount } from '
|
|
4
|
-
import type { AddOn } from '
|
|
3
|
+
import { formatCurrencyAmount } from '../../../../../src/lib/currency';
|
|
4
|
+
import type { AddOn } from '../../../../../src/lib/booking-api';
|
|
5
5
|
import { MealDrinkAddOnSelector, canUseMealDrinkSelector } from './MealDrinkAddOnSelector';
|
|
6
6
|
import type { Currency } from './CurrencySwitcher';
|
|
7
7
|
import styles from './AddOnsSection.module.css';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { createPortal } from 'react-dom';
|
|
4
|
-
import { formatCurrencyAmount } from '
|
|
4
|
+
import { formatCurrencyAmount } from '../../../../../src/lib/currency';
|
|
5
5
|
import type { Currency } from './CurrencySwitcher';
|
|
6
6
|
|
|
7
7
|
interface AdminPaymentChoiceModalProps {
|
|
@@ -1,20 +1,26 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useEffect, useRef, useState } from 'react';
|
|
4
|
-
import { useBookingSourceMetadataFromLocation } from '
|
|
5
|
-
import { useBookingDialog } from '
|
|
6
|
-
import {
|
|
7
|
-
import { getProduct, type Product } from '
|
|
8
|
-
import { ENV } from '@/lib/env';
|
|
9
|
-
import defaultStrings from '@/strings';
|
|
4
|
+
import { useBookingSourceMetadataFromLocation } from '../../hooks/useBookingSourceMetadataFromLocation';
|
|
5
|
+
import { useBookingDialog } from '../../providers/booking-dialog-provider';
|
|
6
|
+
import { useBookingHost } from '../../runtime';
|
|
7
|
+
import { getProduct, type Product } from '../../../../../src/lib/booking-api';
|
|
10
8
|
import './booking-flow.css';
|
|
11
9
|
import BookingProductGrid from './BookingProductGrid';
|
|
12
10
|
import { BookingFlow } from './BookingFlow';
|
|
13
11
|
import { PrivateShuttleBookingFlow } from './PrivateShuttleBookingFlow';
|
|
14
12
|
import { BookingFlowPreview } from './BookingFlowPreview';
|
|
15
|
-
import { useIsBookingLaunchLive } from '
|
|
13
|
+
import { useIsBookingLaunchLive } from '../../hooks/useIsBookingLaunchLive';
|
|
16
14
|
import styles from './BookingDialog.module.css';
|
|
17
15
|
|
|
16
|
+
/** Strings shape Via Via passes via `ViaViaBookingHost` (subset used by this dialog). */
|
|
17
|
+
type HostedBookingStrings = {
|
|
18
|
+
common: {
|
|
19
|
+
chooseYourExperience: string;
|
|
20
|
+
book: string;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
|
|
18
24
|
function BookFlowScreen({
|
|
19
25
|
productId,
|
|
20
26
|
onBack,
|
|
@@ -30,23 +36,29 @@ function BookFlowScreen({
|
|
|
30
36
|
contentRef?: React.RefObject<HTMLDivElement | null>;
|
|
31
37
|
isPartialLaunch: boolean;
|
|
32
38
|
}) {
|
|
39
|
+
const { env, strings, catalog } = useBookingHost();
|
|
40
|
+
const defaultStrings = strings as HostedBookingStrings;
|
|
33
41
|
const bookingSourceAttribution = useBookingSourceMetadataFromLocation();
|
|
34
42
|
const [product, setProduct] = useState<Product | null>(null);
|
|
35
43
|
const [error, setError] = useState<string | null>(null);
|
|
36
44
|
|
|
37
|
-
const config = getProductByIdOrSlug(productId)
|
|
45
|
+
const config = catalog.getProductByIdOrSlug(productId) as {
|
|
46
|
+
productId?: string;
|
|
47
|
+
display?: { shortName?: string; slug?: string };
|
|
48
|
+
} | null;
|
|
38
49
|
const apiProductId = config?.productId ?? productId;
|
|
39
50
|
|
|
40
51
|
// Build minimal product from config for immediate availability fetch (no /products wait)
|
|
41
|
-
const minimalProduct =
|
|
42
|
-
|
|
43
|
-
|
|
52
|
+
const minimalProduct =
|
|
53
|
+
config && catalog.buildMinimalProductFromConfig
|
|
54
|
+
? (catalog.buildMinimalProductFromConfig(config, env.COMPANY_ID) as Product)
|
|
55
|
+
: null;
|
|
44
56
|
|
|
45
57
|
useEffect(() => {
|
|
46
58
|
if (isPartialLaunch) return; // No API needed for partial launch
|
|
47
59
|
let cancelled = false;
|
|
48
60
|
setError(null);
|
|
49
|
-
getProduct(apiProductId,
|
|
61
|
+
getProduct(apiProductId, env.COMPANY_ID)
|
|
50
62
|
.then((p) => {
|
|
51
63
|
if (!cancelled && p) setProduct(p);
|
|
52
64
|
else if (!cancelled && !p && !config) setError('Product not found');
|
|
@@ -155,6 +167,8 @@ function getFocusableElements(container: HTMLElement): HTMLElement[] {
|
|
|
155
167
|
}
|
|
156
168
|
|
|
157
169
|
export default function BookingDialog() {
|
|
170
|
+
const { catalog, strings } = useBookingHost();
|
|
171
|
+
const defaultStrings = strings as HostedBookingStrings;
|
|
158
172
|
const {
|
|
159
173
|
isOpen,
|
|
160
174
|
close,
|
|
@@ -173,7 +187,11 @@ export default function BookingDialog() {
|
|
|
173
187
|
// Use products-config for instant header title (no API wait)
|
|
174
188
|
const displayNameFromConfig =
|
|
175
189
|
currentScreen?.type === 'book-flow'
|
|
176
|
-
?
|
|
190
|
+
? (
|
|
191
|
+
catalog.getProductByIdOrSlug(currentScreen.productId) as {
|
|
192
|
+
display?: { shortName?: string };
|
|
193
|
+
} | null
|
|
194
|
+
)?.display?.shortName ?? null
|
|
177
195
|
: null;
|
|
178
196
|
const contentRef = useRef<HTMLDivElement>(null);
|
|
179
197
|
|
|
@@ -25,13 +25,13 @@ import {
|
|
|
25
25
|
isInsufficientCapacityReserveError,
|
|
26
26
|
describeStandardTourCapacityConflictMessage,
|
|
27
27
|
reportReserveCapacityConflictClientContext,
|
|
28
|
-
} from '
|
|
28
|
+
} from '../../../../../src/lib/booking-api';
|
|
29
29
|
import {
|
|
30
30
|
EARLIEST_AVAILABILITY_DATE,
|
|
31
31
|
LATEST_AVAILABILITY_DATE,
|
|
32
32
|
INITIAL_FETCH_WEEKS,
|
|
33
|
-
} from '
|
|
34
|
-
import { getSundayOfWeek } from '
|
|
33
|
+
} from '../../../../../src/lib/booking-constants';
|
|
34
|
+
import { getSundayOfWeek } from '../../../../../src/lib/booking/sunday-week';
|
|
35
35
|
import { Calendar } from './Calendar';
|
|
36
36
|
import { AdminPaymentChoiceModal } from './AdminPaymentChoiceModal';
|
|
37
37
|
import { ItineraryBox } from './ItineraryBox';
|
|
@@ -43,44 +43,40 @@ import { TicketSelector } from './TicketSelector';
|
|
|
43
43
|
import { AddOnsSection } from './AddOnsSection';
|
|
44
44
|
import { CheckoutForm } from './CheckoutForm';
|
|
45
45
|
import { PromoCodeInput } from './PromoCodeInput';
|
|
46
|
-
import { useTranslations, useLocale } from '
|
|
46
|
+
import { useTranslations, useLocale } from '../../../../../src/lib/booking/i18n';
|
|
47
47
|
import { type Currency } from './CurrencySwitcher';
|
|
48
|
-
import { formatBookingRefForDisplay } from '
|
|
48
|
+
import { formatBookingRefForDisplay } from '../../../../../src/lib/booking-ref';
|
|
49
49
|
import {
|
|
50
50
|
formatCurrencyAmount,
|
|
51
51
|
reconcileChangeBookingProposedTotal,
|
|
52
|
-
} from '
|
|
53
|
-
import { buildCheckoutBreakdown } from '
|
|
54
|
-
import type { PricingConfig, PrecomputedPricesByCategory, ItineraryDisplayStep } from '
|
|
55
|
-
import { ItineraryStepType as StepType } from '
|
|
52
|
+
} from '../../../../../src/lib/currency';
|
|
53
|
+
import { buildCheckoutBreakdown } from '../../../../../src/lib/booking/checkout-breakdown';
|
|
54
|
+
import type { PricingConfig, PrecomputedPricesByCategory, ItineraryDisplayStep } from '../../../../../src/lib/booking-api';
|
|
55
|
+
import { ItineraryStepType as StepType } from '../../../../../src/lib/booking-api';
|
|
56
56
|
import {
|
|
57
57
|
getDisplayPriceFromBaseInDisplayCurrency,
|
|
58
58
|
computePriceBreakdown,
|
|
59
59
|
computeOrderSummary,
|
|
60
60
|
type PriceBreakdown as PriceBreakdownData,
|
|
61
61
|
type OrderSummary,
|
|
62
|
-
} from '
|
|
63
|
-
import { useCompanyTimezone } from '
|
|
64
|
-
import { useBookingApp } from '
|
|
65
|
-
import { useAvailabilitiesCache, buildAvailabilitiesCacheKey } from '
|
|
62
|
+
} from '../../../../../src/lib/booking/pricing';
|
|
63
|
+
import { useCompanyTimezone } from '../../../../../src/contexts/CompanyContext';
|
|
64
|
+
import { useBookingApp } from '../../contexts/BookingAppContext';
|
|
65
|
+
import { useAvailabilitiesCache, buildAvailabilitiesCacheKey } from '../../../../../src/contexts/AvailabilitiesCacheContext';
|
|
66
66
|
import { type PriceSummaryLine } from './PriceSummary';
|
|
67
67
|
import { CheckoutModal, type CheckoutModalLineItem } from './CheckoutModal';
|
|
68
68
|
import { BookingFlowCollage } from './BookingFlowCollage';
|
|
69
69
|
import { TourDescription } from './TourDescription';
|
|
70
|
-
import { getProductByIdOrSlug } from '@/lib/products-config';
|
|
71
|
-
import { getProducts } from '@/constants/products';
|
|
72
|
-
import defaultStrings from '@/strings';
|
|
73
|
-
import { trackViewItem } from '@/lib/analytics';
|
|
74
70
|
import {
|
|
75
71
|
buildBookingSourceContext,
|
|
76
72
|
inferClientBookingSourceFromProductIds,
|
|
77
73
|
type BookingSourceMetadata,
|
|
78
|
-
} from '
|
|
79
|
-
import { getItineraryStepLabel } from '
|
|
80
|
-
import { MANAGE_BOOKING_FROM_CHANGE_PAYMENT, MANAGE_BOOKING_QUERY_FROM } from '
|
|
81
|
-
import {
|
|
74
|
+
} from '../../../../../src/lib/booking/source-metadata';
|
|
75
|
+
import { getItineraryStepLabel } from '../../../../../src/lib/booking/itinerary-display';
|
|
76
|
+
import { MANAGE_BOOKING_FROM_CHANGE_PAYMENT, MANAGE_BOOKING_QUERY_FROM } from '../../../../../src/lib/manage-booking-post-checkout';
|
|
77
|
+
import { useBookingHost } from '../../runtime';
|
|
82
78
|
import type { BookingFlowUiOptions } from './booking-flow-ui';
|
|
83
|
-
import { BOOKING_FLOW_ABANDON_EVENT } from '
|
|
79
|
+
import { BOOKING_FLOW_ABANDON_EVENT } from '../../providers/booking-dialog-provider';
|
|
84
80
|
|
|
85
81
|
/** Live selection snapshot for change-booking compare UI (parent dialog). */
|
|
86
82
|
export interface ChangeFlowSelectionPreview {
|
|
@@ -607,6 +603,7 @@ export function BookingFlow({
|
|
|
607
603
|
availabilityPricingProfileId,
|
|
608
604
|
availabilityCancellationPolicyProfileId,
|
|
609
605
|
}: BookingFlowProps) {
|
|
606
|
+
const { env, strings: defaultStrings, analytics, catalog } = useBookingHost();
|
|
610
607
|
const { t } = useTranslations();
|
|
611
608
|
const { locale } = useLocale();
|
|
612
609
|
const companyTimezone = useCompanyTimezone(); // Get timezone from context
|
|
@@ -836,7 +833,7 @@ export function BookingFlow({
|
|
|
836
833
|
hasFiredViewItem.current = true;
|
|
837
834
|
const id = productId || product.productId;
|
|
838
835
|
const price = product.minPriceByCurrency?.[currency] ?? 0;
|
|
839
|
-
trackViewItem(id, product.name, price, currency);
|
|
836
|
+
analytics.trackViewItem(id, product.name, price, currency);
|
|
840
837
|
}
|
|
841
838
|
}, [product, productId, currency]);
|
|
842
839
|
|
|
@@ -2383,7 +2380,7 @@ export function BookingFlow({
|
|
|
2383
2380
|
|
|
2384
2381
|
const promoDiscountFetchKey = useMemo(() => {
|
|
2385
2382
|
if (!appliedPromoCode || !selectedAvailability || totalQuantity === 0) return '';
|
|
2386
|
-
const companyId = product.companyId ??
|
|
2383
|
+
const companyId = product.companyId ?? env.COMPANY_ID;
|
|
2387
2384
|
if (!companyId) return '';
|
|
2388
2385
|
const optionId = selectedAvailability.productOptionId;
|
|
2389
2386
|
if (!optionId || !quantitiesSignature) return '';
|
|
@@ -2437,7 +2434,7 @@ export function BookingFlow({
|
|
|
2437
2434
|
appliedPromoCode: code,
|
|
2438
2435
|
} = promoDiscountParamsRef.current;
|
|
2439
2436
|
if (!code || !sel) return;
|
|
2440
|
-
const companyId = product.companyId ??
|
|
2437
|
+
const companyId = product.companyId ?? env.COMPANY_ID;
|
|
2441
2438
|
const optionId = sel.productOptionId;
|
|
2442
2439
|
if (!companyId || !optionId) return;
|
|
2443
2440
|
const items = lines.map((l) => ({ category: l.category, qty: l.qty }));
|
|
@@ -4031,8 +4028,16 @@ export function BookingFlow({
|
|
|
4031
4028
|
)}
|
|
4032
4029
|
{/* Image/video collage - vertical video on left, image grid on right */}
|
|
4033
4030
|
{productId && !isChangeFlow && flowUi?.showCollage !== false && (() => {
|
|
4034
|
-
const config = getProductByIdOrSlug(productId)
|
|
4035
|
-
|
|
4031
|
+
const config = catalog.getProductByIdOrSlug(productId) as {
|
|
4032
|
+
display?: {
|
|
4033
|
+
collageImageIds?: string[];
|
|
4034
|
+
imageIds?: string[];
|
|
4035
|
+
};
|
|
4036
|
+
} | null;
|
|
4037
|
+
const displayProducts = catalog.getProducts(defaultStrings) as Record<
|
|
4038
|
+
string,
|
|
4039
|
+
{ id: string; videoUrl?: import('../../../../../src/constants/products').VideoSources }
|
|
4040
|
+
>;
|
|
4036
4041
|
const displayProduct = Object.values(displayProducts).find((p) => p.id === productId);
|
|
4037
4042
|
const collageImageIds = config?.display?.collageImageIds ?? config?.display?.imageIds ?? [];
|
|
4038
4043
|
const hasVideo = !!displayProduct?.videoUrl;
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useState, useRef, useEffect, useCallback } from 'react';
|
|
4
|
-
import ViaViaImage from '@/components/image';
|
|
5
|
-
import { getImageUrl } from '@/constants/images';
|
|
6
4
|
import BackgroundPlayer from 'next-video/background-player';
|
|
7
|
-
import type { VideoSources } from '
|
|
8
|
-
import
|
|
9
|
-
import { useTranslations } from '
|
|
5
|
+
import type { VideoSources } from '../../../../../src/constants/products';
|
|
6
|
+
import { useBookingHost } from '../../runtime';
|
|
7
|
+
import { useTranslations } from '../../../../../src/lib/booking/i18n';
|
|
10
8
|
import styles from './BookingFlowCollage.module.css';
|
|
11
9
|
|
|
12
10
|
export interface BookingFlowCollageProps {
|
|
@@ -31,12 +29,18 @@ export function BookingFlowCollage({
|
|
|
31
29
|
imageIds,
|
|
32
30
|
altPrefix = 'Tour',
|
|
33
31
|
}: BookingFlowCollageProps) {
|
|
32
|
+
const { catalog, slots } = useBookingHost();
|
|
33
|
+
const ViaViaImage = slots.Image;
|
|
34
|
+
const ImageModal = slots.ImageModal;
|
|
34
35
|
const videoSrc = video ?? DEFAULT_VIDEO;
|
|
35
36
|
// Use long version in BookingFlow when available; fall back to short
|
|
36
37
|
const videoForCollage = (videoSrc.longSrc && videoSrc.longWebm)
|
|
37
38
|
? { src: videoSrc.longSrc, webm: videoSrc.longWebm }
|
|
38
39
|
: { src: videoSrc.src, webm: videoSrc.webm };
|
|
39
|
-
const posterUrl =
|
|
40
|
+
const posterUrl =
|
|
41
|
+
videoPosterImageId && catalog.getImageUrl
|
|
42
|
+
? String(catalog.getImageUrl(videoPosterImageId))
|
|
43
|
+
: undefined;
|
|
40
44
|
const gridImages = imageIds.slice(0, 4);
|
|
41
45
|
// Pad with first image if fewer than 4
|
|
42
46
|
while (gridImages.length < 4 && gridImages.length > 0) {
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useEffect, useRef } from 'react';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import defaultStrings from '@/strings';
|
|
7
|
-
import { trackViewItem } from '@/lib/analytics';
|
|
4
|
+
import { useBookingHost } from '../../runtime';
|
|
5
|
+
import type { VideoSources } from '../../../../../src/constants/products';
|
|
8
6
|
import { BookingFlowCollage } from './BookingFlowCollage';
|
|
9
7
|
import { TourDescription } from './TourDescription';
|
|
10
8
|
|
|
@@ -13,20 +11,31 @@ import { TourDescription } from './TourDescription';
|
|
|
13
11
|
* No API call required - shows immediately.
|
|
14
12
|
*/
|
|
15
13
|
export function BookingFlowPreview({ productId, defaultExpanded = true }: { productId: string; defaultExpanded?: boolean }) {
|
|
16
|
-
const
|
|
14
|
+
const { catalog, strings: defaultStrings, analytics } = useBookingHost();
|
|
15
|
+
const config = catalog.getProductByIdOrSlug(productId) as {
|
|
16
|
+
display?: { shortName?: string; collageImageIds?: string[]; imageIds?: string[] };
|
|
17
|
+
productId?: string;
|
|
18
|
+
} | null;
|
|
17
19
|
const hasFiredViewItem = useRef(false);
|
|
18
20
|
|
|
19
21
|
useEffect(() => {
|
|
20
22
|
if (!hasFiredViewItem.current && productId && config) {
|
|
21
23
|
hasFiredViewItem.current = true;
|
|
22
|
-
const displayProducts = getProducts(defaultStrings)
|
|
24
|
+
const displayProducts = catalog.getProducts(defaultStrings) as Record<
|
|
25
|
+
string,
|
|
26
|
+
{ id: string; name: string; avgPrice: number }
|
|
27
|
+
>;
|
|
23
28
|
const displayProduct = Object.values(displayProducts).find((p) => p.id === productId);
|
|
24
29
|
const productName = displayProduct?.name ?? config.display?.shortName ?? productId;
|
|
25
30
|
const price = displayProduct?.avgPrice ?? 0;
|
|
26
|
-
trackViewItem(productId, productName, price, 'CAD');
|
|
31
|
+
analytics.trackViewItem(productId, productName, price, 'CAD');
|
|
27
32
|
}
|
|
28
|
-
}, [productId, config]);
|
|
29
|
-
|
|
33
|
+
}, [productId, config, catalog, defaultStrings, analytics]);
|
|
34
|
+
|
|
35
|
+
const displayProducts = catalog.getProducts(defaultStrings) as Record<
|
|
36
|
+
string,
|
|
37
|
+
{ id: string; videoUrl?: VideoSources }
|
|
38
|
+
>;
|
|
30
39
|
const displayProduct = Object.values(displayProducts).find((p) => p.id === productId);
|
|
31
40
|
const collageImageIds = config?.display?.collageImageIds ?? config?.display?.imageIds ?? [];
|
|
32
41
|
const hasVideo = !!displayProduct?.videoUrl;
|
|
@@ -5,21 +5,20 @@ import { motion, AnimatePresence } from 'framer-motion';
|
|
|
5
5
|
import {
|
|
6
6
|
useBookingDialog,
|
|
7
7
|
type ProductGridRestoreState,
|
|
8
|
-
} from '
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import { getProductDescription } from '@/lib/product-descriptions';
|
|
13
|
-
import ViaViaImage from '@/components/image';
|
|
14
|
-
import ValuePill from '@/components/value-pill';
|
|
15
|
-
import ProductTag from '@/components/product-tag';
|
|
16
|
-
import { PillVariant } from '@/components/value-pill';
|
|
17
|
-
import type { Product } from '@/constants/products';
|
|
18
|
-
import defaultStrings from '@/strings';
|
|
19
|
-
import { getImageUrl } from '@/constants/images';
|
|
8
|
+
} from '../../providers/booking-dialog-provider';
|
|
9
|
+
import { useBookingHost } from '../../runtime';
|
|
10
|
+
import { useLocale, useTranslations } from '../../../../../src/lib/booking/i18n';
|
|
11
|
+
import type { Product } from '../../../../../src/constants/products';
|
|
20
12
|
import BackgroundPlayer from 'next-video/background-player';
|
|
21
13
|
import styles from './BookingProductGrid.module.css';
|
|
22
|
-
|
|
14
|
+
|
|
15
|
+
/** Locale strings needed by the product grid tiles (Via Via host). */
|
|
16
|
+
type HostedProductGridStrings = {
|
|
17
|
+
common: {
|
|
18
|
+
moreInfo: string;
|
|
19
|
+
bookNow: string;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
23
22
|
|
|
24
23
|
const DEFAULT_VIDEO = {
|
|
25
24
|
src: '/videos/via-via-moraine-lake-tour-video.mp4',
|
|
@@ -63,6 +62,15 @@ function BookingProductTileCollapsed({
|
|
|
63
62
|
useLayoutId: boolean;
|
|
64
63
|
suppressLayoutAnimation?: boolean;
|
|
65
64
|
}) {
|
|
65
|
+
const { slots } = useBookingHost();
|
|
66
|
+
const ViaViaImage = slots.Image;
|
|
67
|
+
const ProductTag = slots.ProductTag;
|
|
68
|
+
const ValuePill = slots.ValuePill;
|
|
69
|
+
const PillVariant = slots.PillVariant as {
|
|
70
|
+
overlay: string;
|
|
71
|
+
solid: string;
|
|
72
|
+
};
|
|
73
|
+
|
|
66
74
|
return (
|
|
67
75
|
<motion.div
|
|
68
76
|
layout={!suppressLayoutAnimation}
|
|
@@ -127,7 +135,25 @@ function BookingProductTileExpanded({
|
|
|
127
135
|
}) {
|
|
128
136
|
const [isClosing, setIsClosing] = useState(false);
|
|
129
137
|
const { locale } = useLocale();
|
|
130
|
-
const
|
|
138
|
+
const { slots, catalog, strings } = useBookingHost();
|
|
139
|
+
const defaultStrings = strings as HostedProductGridStrings;
|
|
140
|
+
const ViaViaImage = slots.Image;
|
|
141
|
+
const ProductTag = slots.ProductTag;
|
|
142
|
+
const ValuePill = slots.ValuePill;
|
|
143
|
+
const Button = slots.Button;
|
|
144
|
+
const ButtonHoverColor = slots.ButtonHoverColor as {
|
|
145
|
+
Turquoise: string;
|
|
146
|
+
White: string;
|
|
147
|
+
Orange: string;
|
|
148
|
+
};
|
|
149
|
+
const PillVariant = slots.PillVariant as {
|
|
150
|
+
overlay: string;
|
|
151
|
+
solid: string;
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const productDesc = catalog.getProductDescription(product.id, locale) as {
|
|
155
|
+
shortDescription?: string;
|
|
156
|
+
} | null;
|
|
131
157
|
const displayDescription = productDesc?.shortDescription ?? product.description;
|
|
132
158
|
|
|
133
159
|
const handleCollapseClick = () => {
|
|
@@ -185,7 +211,11 @@ function BookingProductTileExpanded({
|
|
|
185
211
|
muted
|
|
186
212
|
loop
|
|
187
213
|
playsInline
|
|
188
|
-
poster={
|
|
214
|
+
poster={
|
|
215
|
+
catalog.getImageUrl
|
|
216
|
+
? String(catalog.getImageUrl(product.images[0].id))
|
|
217
|
+
: ''
|
|
218
|
+
}
|
|
189
219
|
className={styles.expandedVideo}
|
|
190
220
|
/>
|
|
191
221
|
{product.tags && product.tags.length > 0 && (
|
|
@@ -258,6 +288,7 @@ export default function BookingProductGrid({
|
|
|
258
288
|
onBookProduct,
|
|
259
289
|
bookOnTileClick = false,
|
|
260
290
|
}: BookingProductGridProps = {}) {
|
|
291
|
+
const { catalog, strings: defaultStrings } = useBookingHost();
|
|
261
292
|
const { push } = useBookingDialog();
|
|
262
293
|
const { t } = useTranslations();
|
|
263
294
|
const hasAppliedRestore = useRef(false);
|
|
@@ -289,8 +320,11 @@ export default function BookingProductGrid({
|
|
|
289
320
|
}, [isRestoring, restoreState, contentRef, onRestoreApplied]);
|
|
290
321
|
|
|
291
322
|
const allProducts = useMemo(
|
|
292
|
-
() =>
|
|
293
|
-
|
|
323
|
+
() =>
|
|
324
|
+
Object.values(
|
|
325
|
+
catalog.getProducts(defaultStrings) as Record<string, Product>
|
|
326
|
+
),
|
|
327
|
+
[catalog, defaultStrings]
|
|
294
328
|
);
|
|
295
329
|
|
|
296
330
|
const products = useMemo(
|
|
@@ -397,8 +431,10 @@ export default function BookingProductGrid({
|
|
|
397
431
|
);
|
|
398
432
|
|
|
399
433
|
const handleBook = (product: Product) => {
|
|
400
|
-
const config = getProductByIdOrSlug(product.id)
|
|
401
|
-
|
|
434
|
+
const config = catalog.getProductByIdOrSlug(product.id) as {
|
|
435
|
+
display?: { slug?: string };
|
|
436
|
+
} | null;
|
|
437
|
+
const productSlugOrId = config?.display?.slug ?? product.id;
|
|
402
438
|
|
|
403
439
|
if (onBookProduct) {
|
|
404
440
|
onBookProduct(productSlugOrId);
|
|
@@ -361,13 +361,20 @@
|
|
|
361
361
|
.calendarDaysGrid {
|
|
362
362
|
display: grid;
|
|
363
363
|
grid-template-columns: repeat(7, 1fr);
|
|
364
|
-
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/* Desktop: don't stretch every cell to the tallest day in the row (was huge empty space). */
|
|
367
|
+
@media (min-width: 640px) {
|
|
368
|
+
.calendarDaysGrid {
|
|
369
|
+
align-items: start;
|
|
370
|
+
}
|
|
365
371
|
}
|
|
366
372
|
|
|
367
373
|
/* Mobile: rows size to content to eliminate bottom gap */
|
|
368
374
|
@media (max-width: 639px) {
|
|
369
375
|
.calendarDaysGrid {
|
|
370
376
|
grid-auto-rows: minmax(min-content, max-content);
|
|
377
|
+
align-items: stretch;
|
|
371
378
|
}
|
|
372
379
|
}
|
|
373
380
|
|
|
@@ -381,17 +388,17 @@
|
|
|
381
388
|
flex-direction: column;
|
|
382
389
|
align-items: center;
|
|
383
390
|
justify-content: center;
|
|
384
|
-
min-height:
|
|
391
|
+
min-height: 4.75rem; /* Desktop: room for date row + time pills without oversized cells */
|
|
385
392
|
cursor: pointer;
|
|
386
393
|
}
|
|
387
394
|
|
|
388
395
|
/* Admin (showCapacity): extra line under each time pill — taller cells on sm+ only */
|
|
389
396
|
@media (min-width: 640px) {
|
|
390
397
|
.calendar .calendarDayCell.calendarDayCellWithAdminCapacity {
|
|
391
|
-
min-height:
|
|
398
|
+
min-height: 6.5rem;
|
|
392
399
|
}
|
|
393
400
|
.calendar .calendarDayCell.calendarDayCellWithAdminCapacity.calendarDayCellWithAdminCapacityTall {
|
|
394
|
-
min-height:
|
|
401
|
+
min-height: 7.5rem;
|
|
395
402
|
}
|
|
396
403
|
}
|
|
397
404
|
|
|
@@ -404,6 +411,14 @@
|
|
|
404
411
|
justify-content: center;
|
|
405
412
|
width: 100%;
|
|
406
413
|
}
|
|
414
|
+
|
|
415
|
+
/* Desktop: pin time pills to bottom of cell; centering left a tall empty band under the date. */
|
|
416
|
+
@media (min-width: 640px) {
|
|
417
|
+
.calendarDayCellInner:not(.calendarDayCellInnerMobile) {
|
|
418
|
+
justify-content: flex-end;
|
|
419
|
+
padding-bottom: 0.125rem;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
407
422
|
.calendar .calendarDayCell.calendarDayCellMobile .calendarDayCellInnerMobile,
|
|
408
423
|
.calendar .calendarDayCell.calendarDayCellMobileTall .calendarDayCellInnerMobile {
|
|
409
424
|
justify-content: flex-start !important;
|
|
@@ -5,15 +5,15 @@ import { createPortal } from 'react-dom';
|
|
|
5
5
|
import { startOfWeek, addDays, addWeeks, subWeeks, isSameDay, parseISO, startOfMonth, endOfMonth, eachDayOfInterval, addMonths, subMonths } from 'date-fns';
|
|
6
6
|
import { formatInTimeZone, fromZonedTime } from 'date-fns-tz';
|
|
7
7
|
import { enUS, fr } from 'date-fns/locale';
|
|
8
|
-
import type { Availability } from '
|
|
9
|
-
import { useTranslations, useLocale } from '
|
|
8
|
+
import type { Availability } from '../../../../../src/lib/booking-api';
|
|
9
|
+
import { useTranslations, useLocale } from '../../../../../src/lib/booking/i18n';
|
|
10
10
|
import {
|
|
11
11
|
MINI_CALENDAR_START_MONTH,
|
|
12
12
|
MINI_CALENDAR_END_MONTH,
|
|
13
13
|
VISIBLE_RANGE_BUFFER_WEEKS,
|
|
14
|
-
} from '
|
|
15
|
-
import { getSundayOfWeek } from '
|
|
16
|
-
import { cn } from '
|
|
14
|
+
} from '../../../../../src/lib/booking-constants';
|
|
15
|
+
import { getSundayOfWeek } from '../../../../../src/lib/booking/sunday-week';
|
|
16
|
+
import { cn } from '../../../../../src/lib/booking/utils';
|
|
17
17
|
import styles from './Calendar.module.css';
|
|
18
18
|
|
|
19
19
|
// ============ Types ============
|
|
@@ -207,8 +207,8 @@ const DateCell = memo(function DateCell({
|
|
|
207
207
|
? cn('min-w-0', styles.calendarDayCellWithAdminCapacity, styles.calendarDayCellWithAdminCapacityTall)
|
|
208
208
|
: styles.calendarDayCellWithAdminCapacity
|
|
209
209
|
: needsTallerCell
|
|
210
|
-
? 'min-w-0 min-h-[
|
|
211
|
-
: 'min-h-[
|
|
210
|
+
? 'min-w-0 min-h-[5.75rem]'
|
|
211
|
+
: 'min-h-[5rem]'
|
|
212
212
|
),
|
|
213
213
|
isDisabled
|
|
214
214
|
? styles.calendarDayCellDisabled
|
|
@@ -250,7 +250,12 @@ const DateCell = memo(function DateCell({
|
|
|
250
250
|
|
|
251
251
|
{/* Availability Indicators - Centered */}
|
|
252
252
|
{availability && (
|
|
253
|
-
<div
|
|
253
|
+
<div
|
|
254
|
+
className={cn(
|
|
255
|
+
'flex flex-col items-center space-y-0 w-full px-0.5',
|
|
256
|
+
isMobile ? 'justify-start' : 'justify-end'
|
|
257
|
+
)}
|
|
258
|
+
>
|
|
254
259
|
{/* Sold Out Badge - Centered */}
|
|
255
260
|
{availability.isSoldOut ? (
|
|
256
261
|
<div className={styles.calendarSoldOutBadge}>
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { Check } from 'lucide-react';
|
|
4
|
-
import { formatCurrencyAmount } from '
|
|
5
|
-
import type { CancellationPolicyOption, RefundTierOption } from '
|
|
4
|
+
import { formatCurrencyAmount } from '../../../../../src/lib/currency';
|
|
5
|
+
import type { CancellationPolicyOption, RefundTierOption } from '../../../../../src/lib/booking-api';
|
|
6
6
|
import type { Currency } from './CurrencySwitcher';
|
|
7
7
|
import styles from './CancellationPolicySelector.module.css';
|
|
8
8
|
|