@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
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
const busIconPath = '/pill-value-icons/bus-icon.svg';
|
|
2
|
-
const clockIconPath = '/pill-value-icons/clock-icon.svg';
|
|
3
|
-
const sunriseIconPath = '/pill-value-icons/sun-icon.svg';
|
|
4
|
-
const cameraIconPath = '/pill-value-icons/camera-icon.svg';
|
|
5
|
-
const moneyIconPath = '/pill-value-icons/money-icon.svg';
|
|
6
|
-
const doubleCheckIconPath = '/pill-value-icons/double-check-icon.svg';
|
|
7
|
-
const hikerIconPath = '/pill-value-icons/hiker-icon.svg';
|
|
8
|
-
const waterIconPath = '/pill-value-icons/water-icon.svg';
|
|
9
|
-
const lunchIconPath = '/pill-value-icons/lunch-icon.svg';
|
|
10
|
-
const croissantIconPath = '/pill-value-icons/croissant-icon.svg';
|
|
11
|
-
const locationPinIconPath = '/pill-value-icons/location-pin-icon.svg';
|
|
12
|
-
const addTimeIconPath = '/pill-value-icons/add-time-icon.svg';
|
|
13
|
-
const coffeeIconPath = '/pill-value-icons/coffee-icon.svg';
|
|
14
|
-
const blanketIconPath = '/pill-value-icons/blanket-icon.svg';
|
|
15
|
-
import defaultStrings from '@/strings';
|
|
16
|
-
import { ProductKey } from './products';
|
|
17
|
-
|
|
18
|
-
export type PillValue = {
|
|
19
|
-
icon: string;
|
|
20
|
-
label: string;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function createDeparturePillValue(productKey: ProductKey, strings = defaultStrings): PillValue {
|
|
24
|
-
return {
|
|
25
|
-
icon: busIconPath,
|
|
26
|
-
label: getDepartureLabel(productKey, strings)
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function getDepartureLabel(productKey: ProductKey, strings: any): string {
|
|
31
|
-
switch (productKey) {
|
|
32
|
-
case 'two-lakes-combo':
|
|
33
|
-
return strings.pillValues.departurePillValues.twoLakesCombo;
|
|
34
|
-
case 'moraine-lake-adventure':
|
|
35
|
-
return strings.pillValues.departurePillValues.moraineLakeAdventure;
|
|
36
|
-
case 'lake-louise-adventure':
|
|
37
|
-
return strings.pillValues.departurePillValues.lakeLouiseAdventure;
|
|
38
|
-
case 'emerald-lake-escape':
|
|
39
|
-
return strings.pillValues.departurePillValues.emeraldLakeEscapeTour;
|
|
40
|
-
case 'private-tour':
|
|
41
|
-
return strings.pillValues.departurePillValues.privateTour;
|
|
42
|
-
case 'afternoon-delight':
|
|
43
|
-
return strings.pillValues.departurePillValues.afternoonDelight;
|
|
44
|
-
default:
|
|
45
|
-
return '';
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function createDurationPillValue(productKey: ProductKey, strings = defaultStrings): PillValue {
|
|
50
|
-
return {
|
|
51
|
-
icon: clockIconPath,
|
|
52
|
-
label: getDurationLabel(productKey, strings)
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function getDurationLabel(productKey: ProductKey, strings: any): string {
|
|
57
|
-
switch (productKey) {
|
|
58
|
-
case 'moraine-lake-sunrise':
|
|
59
|
-
return strings.pillValues.durationPillValues.moraineLakeSunrise;
|
|
60
|
-
case 'moraine-lake-sunrise-lake-louise-golden-hour-1':
|
|
61
|
-
return strings.pillValues.durationPillValues.moraineLakeSunriseLakeLouiseGoldenHour1;
|
|
62
|
-
case 'moraine-lake-sunrise-lake-louise-golden-hour-2':
|
|
63
|
-
return strings.pillValues.durationPillValues.moraineLakeSunriseLakeLouiseGoldenHour2;
|
|
64
|
-
case 'two-lakes-combo-1':
|
|
65
|
-
return strings.pillValues.durationPillValues.twoLakesCombo1;
|
|
66
|
-
case 'two-lakes-combo-2':
|
|
67
|
-
return strings.pillValues.durationPillValues.twoLakesCombo2;
|
|
68
|
-
case 'moraine-lake-adventure':
|
|
69
|
-
return strings.pillValues.durationPillValues.moraineLakeAdventure;
|
|
70
|
-
case 'lake-louise-adventure':
|
|
71
|
-
return strings.pillValues.durationPillValues.lakeLouiseAdventure;
|
|
72
|
-
case 'emerald-lake-escape':
|
|
73
|
-
return strings.pillValues.durationPillValues.emeraldLakeEscapeTour;
|
|
74
|
-
case 'private-tour':
|
|
75
|
-
return strings.pillValues.durationPillValues.privateTour;
|
|
76
|
-
case 'afternoon-delight1':
|
|
77
|
-
return strings.pillValues.durationPillValues.afternoonDelight1;
|
|
78
|
-
case 'afternoon-delight2':
|
|
79
|
-
return strings.pillValues.durationPillValues.afternoonDelight2;
|
|
80
|
-
default:
|
|
81
|
-
return '';
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export function createAddTimePillValue(productKey: ProductKey, strings = defaultStrings): PillValue {
|
|
86
|
-
return {
|
|
87
|
-
icon: addTimeIconPath,
|
|
88
|
-
label: getAddTimeLabel(productKey, strings)
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function getAddTimeLabel(productKey: ProductKey, strings: any): string {
|
|
93
|
-
switch (productKey) {
|
|
94
|
-
case 'moraine-lake-sunrise':
|
|
95
|
-
return strings.pillValues.addTimePillValues.moraineLakeSunrise;
|
|
96
|
-
case 'moraine-lake-sunrise-lake-louise-golden-hour':
|
|
97
|
-
return strings.pillValues.addTimePillValues.moraineLakeSunriseLakeLouiseGoldenHour;
|
|
98
|
-
case 'two-lakes-combo':
|
|
99
|
-
return strings.pillValues.addTimePillValues.twoLakesCombo;
|
|
100
|
-
case 'moraine-lake-adventure':
|
|
101
|
-
return strings.pillValues.addTimePillValues.moraineLakeAdventure;
|
|
102
|
-
case 'lake-louise-adventure':
|
|
103
|
-
return strings.pillValues.addTimePillValues.lakeLouiseAdventure;
|
|
104
|
-
case 'emerald-lake-escape':
|
|
105
|
-
return strings.pillValues.addTimePillValues.emeraldLakeEscapeTour;
|
|
106
|
-
case 'private-tour':
|
|
107
|
-
return strings.pillValues.addTimePillValues.privateTour;
|
|
108
|
-
default:
|
|
109
|
-
return '';
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export function createMoneyPillValue(productKey: ProductKey, strings = defaultStrings): PillValue {
|
|
114
|
-
return {
|
|
115
|
-
icon: moneyIconPath,
|
|
116
|
-
label: getMoneyLabel(productKey, strings)
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function getMoneyLabel(productKey: ProductKey, strings: any): string {
|
|
121
|
-
switch (productKey) {
|
|
122
|
-
case 'moraine-lake-sunrise':
|
|
123
|
-
return strings.pillValues.moneyPillValues.moraineLakeSunrise;
|
|
124
|
-
case 'moraine-lake-sunrise-lake-louise-golden-hour':
|
|
125
|
-
return strings.pillValues.moneyPillValues.moraineLakeSunriseLakeLouiseGoldenHour;
|
|
126
|
-
case 'two-lakes-combo':
|
|
127
|
-
return strings.pillValues.moneyPillValues.twoLakesCombo;
|
|
128
|
-
case 'moraine-lake-adventure':
|
|
129
|
-
return strings.pillValues.moneyPillValues.moraineLakeAdventure;
|
|
130
|
-
case 'lake-louise-adventure':
|
|
131
|
-
return strings.pillValues.moneyPillValues.lakeLouiseAdventure;
|
|
132
|
-
case 'emerald-lake-escape':
|
|
133
|
-
return strings.pillValues.moneyPillValues.emeraldLakeEscapeTour;
|
|
134
|
-
case 'private-tour':
|
|
135
|
-
return strings.pillValues.moneyPillValues.privateTour;
|
|
136
|
-
case 'afternoon-delight':
|
|
137
|
-
return strings.pillValues.moneyPillValues.afternoonDelight;
|
|
138
|
-
default:
|
|
139
|
-
return '';
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
export function createemeraldLakeEscapeTourLocationsPillValues(strings = defaultStrings): PillValue[] {
|
|
144
|
-
return Object.values(strings.pillValues.emeraldLakeEscapeTourLocations).map(location => ({
|
|
145
|
-
icon: locationPinIconPath,
|
|
146
|
-
label: location as string
|
|
147
|
-
}));
|
|
148
|
-
}
|
|
149
|
-
export const createSunrisePillValue = (strings = defaultStrings): PillValue => {
|
|
150
|
-
return {
|
|
151
|
-
icon: sunriseIconPath,
|
|
152
|
-
label: strings.pillValues.sunrise
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
export const createTwoLakesInOnePillValue = (strings = defaultStrings): PillValue => {
|
|
157
|
-
return {
|
|
158
|
-
icon: doubleCheckIconPath,
|
|
159
|
-
label: strings.pillValues.twoLakesInOne
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
export const createPhotographyPillValue = (strings = defaultStrings): PillValue => {
|
|
164
|
-
return {
|
|
165
|
-
icon: cameraIconPath,
|
|
166
|
-
label: strings.pillValues.photography
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
export const createHikePillValue = (strings = defaultStrings): PillValue => {
|
|
171
|
-
return {
|
|
172
|
-
icon: hikerIconPath,
|
|
173
|
-
label: strings.pillValues.hike
|
|
174
|
-
};
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
export const createCanoePillValue = (strings = defaultStrings): PillValue => {
|
|
178
|
-
return {
|
|
179
|
-
icon: waterIconPath,
|
|
180
|
-
label: strings.pillValues.canoe
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
export const createLunchPillValue = (strings = defaultStrings): PillValue => {
|
|
185
|
-
return {
|
|
186
|
-
icon: lunchIconPath,
|
|
187
|
-
label: strings.pillValues.lunch
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
export const createCroissantPillValue = (strings = defaultStrings): PillValue => {
|
|
192
|
-
return {
|
|
193
|
-
icon: croissantIconPath,
|
|
194
|
-
label: strings.pillValues.croissant
|
|
195
|
-
};
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
export const createHotDrinksPillValue = (strings = defaultStrings): PillValue => {
|
|
199
|
-
return {
|
|
200
|
-
icon: coffeeIconPath,
|
|
201
|
-
label: strings.pillValues.hotDrinks
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
export const createCozyBlanketsPillValue = (strings = defaultStrings): PillValue => {
|
|
206
|
-
return {
|
|
207
|
-
icon: blanketIconPath,
|
|
208
|
-
label: strings.pillValues.blankets
|
|
209
|
-
};
|
|
210
|
-
}
|
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
import { ImageData, IMAGES } from './images';
|
|
2
|
-
import { PillValue, createDeparturePillValue, createDurationPillValue, createSunrisePillValue, createTwoLakesInOnePillValue, createHikePillValue, createCanoePillValue, createMoneyPillValue, createAddTimePillValue, createHotDrinksPillValue, createLunchPillValue, createCroissantPillValue, createemeraldLakeEscapeTourLocationsPillValues, createCozyBlanketsPillValue } from './pill-values';
|
|
3
|
-
import { ProductTagStyle } from '@/components/product-tag';
|
|
4
|
-
|
|
5
|
-
export interface ProductTag {
|
|
6
|
-
text: string;
|
|
7
|
-
style: ProductTagStyle;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export interface VideoSources {
|
|
11
|
-
src: string;
|
|
12
|
-
webm: string;
|
|
13
|
-
/** Long version for BookingFlow collage. Falls back to short (src/webm) if omitted. */
|
|
14
|
-
longSrc?: string;
|
|
15
|
-
longWebm?: string;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface Product {
|
|
19
|
-
id: string;
|
|
20
|
-
name: string;
|
|
21
|
-
shortName: string;
|
|
22
|
-
images: readonly ImageData[];
|
|
23
|
-
bookingLink: string;
|
|
24
|
-
currentStartTime: string;
|
|
25
|
-
description: string;
|
|
26
|
-
path: string;
|
|
27
|
-
avgPrice: number;
|
|
28
|
-
pillValues: PillValue[];
|
|
29
|
-
tags?: ProductTag[];
|
|
30
|
-
/** Video for expanded view in booking dialog. Place mp4 and webm in public/videos/ named {productId}.mp4 and {productId}.webm */
|
|
31
|
-
videoUrl?: VideoSources;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Type for accessing product keys
|
|
35
|
-
export type ProductKey = keyof ReturnType<typeof getProducts>;
|
|
36
|
-
|
|
37
|
-
export const getProducts = (strings: any): Record<string, Product> => ({
|
|
38
|
-
MORAINE_LAKE_SUNRISE_LAKE_LOUISE_GOLDEN_HOUR: {
|
|
39
|
-
id: 'moraine-lake-sunrise-lake-louise-golden-hour',
|
|
40
|
-
videoUrl: { src: '/videos/moraine-lake-sunrise-lake-louise-golden-hour.mp4', webm: '/videos/moraine-lake-sunrise-lake-louise-golden-hour.webm', longSrc: '/videos/moraine-lake-sunrise-lake-louise-golden-hour-long.mp4', longWebm: '/videos/moraine-lake-sunrise-lake-louise-golden-hour-long.webm' },
|
|
41
|
-
name: strings.products.productNames.moraineLakeSunriseLakeLouiseGoldenHour,
|
|
42
|
-
shortName: strings.products.productShortNames.moraineLakeSunriseLakeLouiseGoldenHourShort,
|
|
43
|
-
images: [
|
|
44
|
-
IMAGES.MORAINE_LAKE_CANOES_SUNRISE,
|
|
45
|
-
],
|
|
46
|
-
bookingLink: '#book-now/moraine-lake-sunrise-lake-louise-golden-hour',
|
|
47
|
-
currentStartTime: strings.products.startTimes.moraineLakeSunriseLakeLouiseGoldenHour,
|
|
48
|
-
description: strings.productThemePages.sunriseTours.description,
|
|
49
|
-
path: '/moraine-lake-sunrise-shuttle',
|
|
50
|
-
avgPrice: 159,
|
|
51
|
-
pillValues: [createSunrisePillValue(strings), createDurationPillValue('moraine-lake-sunrise-lake-louise-golden-hour-1', strings), createDurationPillValue('moraine-lake-sunrise-lake-louise-golden-hour-2', strings), createAddTimePillValue('moraine-lake-sunrise-lake-louise-golden-hour', strings), createHotDrinksPillValue(strings), createCozyBlanketsPillValue(strings), createTwoLakesInOnePillValue(strings), createMoneyPillValue('moraine-lake-sunrise-lake-louise-golden-hour', strings)],
|
|
52
|
-
tags: [{
|
|
53
|
-
text: strings.products.productTags.mostPopular,
|
|
54
|
-
style: ProductTagStyle.MOST_POPULAR
|
|
55
|
-
}]
|
|
56
|
-
},
|
|
57
|
-
MORAINE_LAKE_SUNRISE: {
|
|
58
|
-
id: 'moraine-lake-sunrise',
|
|
59
|
-
videoUrl: { src: '/videos/moraine-lake-sunrise.mp4', webm: '/videos/moraine-lake-sunrise.webm', longSrc: '/videos/moraine-lake-sunrise-long.mp4', longWebm: '/videos/moraine-lake-sunrise-long.webm' },
|
|
60
|
-
name: strings.products.productNames.moraineLakeSunrise,
|
|
61
|
-
shortName: strings.products.productShortNames.moraineLakeSunriseShort,
|
|
62
|
-
images: [
|
|
63
|
-
IMAGES.MORAINE_LAKE_SUNRISE_CHEERS,
|
|
64
|
-
],
|
|
65
|
-
bookingLink: '#book-now/moraine-lake-sunrise',
|
|
66
|
-
currentStartTime: strings.products.startTimes.moraineLakeSunrise,
|
|
67
|
-
description: strings.productThemePages.sunriseTours.description,
|
|
68
|
-
path: '/moraine-lake-sunrise-shuttle',
|
|
69
|
-
avgPrice: 129,
|
|
70
|
-
pillValues: [createSunrisePillValue(strings), createDurationPillValue('moraine-lake-sunrise', strings), createAddTimePillValue('moraine-lake-sunrise', strings), createHotDrinksPillValue(strings), createCozyBlanketsPillValue(strings), createMoneyPillValue('moraine-lake-sunrise', strings)]
|
|
71
|
-
},
|
|
72
|
-
TWO_LAKES_COMBO: {
|
|
73
|
-
id: 'two-lakes-combo',
|
|
74
|
-
videoUrl: { src: '/videos/two-lakes-combo.mp4', webm: '/videos/two-lakes-combo.webm', longSrc: '/videos/two-lakes-combo-long.mp4', longWebm: '/videos/two-lakes-combo-long.webm' },
|
|
75
|
-
name: strings.products.productNames.twoLakesCombo,
|
|
76
|
-
shortName: strings.products.productShortNames.twoLakesComboShort,
|
|
77
|
-
images: [
|
|
78
|
-
IMAGES.MORAINE_LAKE_YELLOW_BIKINI,
|
|
79
|
-
],
|
|
80
|
-
bookingLink: '#book-now/two-lakes-combo',
|
|
81
|
-
currentStartTime: strings.products.startTimes.moraineLakeAdventure,
|
|
82
|
-
description: strings.products.productDescriptions.twoLakesCombo,
|
|
83
|
-
path: '/moraine-lake-shuttle',
|
|
84
|
-
avgPrice: 149,
|
|
85
|
-
pillValues: [createDeparturePillValue('two-lakes-combo', strings), createDurationPillValue('two-lakes-combo-1', strings), createDurationPillValue('two-lakes-combo-2', strings), createAddTimePillValue('two-lakes-combo', strings), createTwoLakesInOnePillValue(strings), createMoneyPillValue('two-lakes-combo', strings)],
|
|
86
|
-
tags: [{
|
|
87
|
-
text: strings.products.productTags.mostPopular,
|
|
88
|
-
style: ProductTagStyle.MOST_POPULAR
|
|
89
|
-
}]
|
|
90
|
-
},
|
|
91
|
-
MORAINE_LAKE_ADVENTURE: {
|
|
92
|
-
id: 'moraine-lake-adventure',
|
|
93
|
-
videoUrl: { src: '/videos/moraine-lake-adventure.mp4', webm: '/videos/moraine-lake-adventure.webm', longSrc: '/videos/moraine-lake-adventure-long.mp4', longWebm: '/videos/moraine-lake-adventure-long.webm' },
|
|
94
|
-
name: strings.products.productNames.moraineLakeAdventure,
|
|
95
|
-
shortName: strings.products.productShortNames.moraineLakeAdventureShort,
|
|
96
|
-
images: [
|
|
97
|
-
IMAGES.MORAINE_LAKE_CANOE,
|
|
98
|
-
],
|
|
99
|
-
bookingLink: '#book-now/moraine-lake-adventure',
|
|
100
|
-
currentStartTime: strings.products.startTimes.moraineLakeAdventure,
|
|
101
|
-
description: strings.productThemePages.moraineLakeShuttle.description,
|
|
102
|
-
path: '/moraine-lake-shuttle',
|
|
103
|
-
avgPrice: 95,
|
|
104
|
-
pillValues: [createDeparturePillValue('moraine-lake-adventure', strings), createDurationPillValue('moraine-lake-adventure', strings), createAddTimePillValue('moraine-lake-adventure', strings), createHikePillValue(strings), createCanoePillValue(strings), createMoneyPillValue('moraine-lake-adventure', strings)]
|
|
105
|
-
},
|
|
106
|
-
LAKE_LOUISE_ADVENTURE: {
|
|
107
|
-
id: 'lake-louise-adventure',
|
|
108
|
-
videoUrl: { src: '/videos/lake-louise-adventure.mp4', webm: '/videos/lake-louise-adventure.webm', longSrc: '/videos/lake-louise-adventure-long.mp4', longWebm: '/videos/lake-louise-adventure-long.webm' },
|
|
109
|
-
name: strings.products.productNames.lakeLouiseAdventure,
|
|
110
|
-
shortName: strings.products.productShortNames.lakeLouiseAdventureShort,
|
|
111
|
-
images: [
|
|
112
|
-
IMAGES.LAKE_LOUISE_FLORA,
|
|
113
|
-
],
|
|
114
|
-
bookingLink: '#book-now/lake-louise-adventure',
|
|
115
|
-
currentStartTime: strings.products.startTimes.lakeLouiseAdventure,
|
|
116
|
-
description: strings.productThemePages.lakeLouiseShuttle.description,
|
|
117
|
-
path: '/lake-louise-shuttle',
|
|
118
|
-
avgPrice: 69,
|
|
119
|
-
pillValues: [createDeparturePillValue('lake-louise-adventure', strings), createDurationPillValue('lake-louise-adventure', strings), createAddTimePillValue('lake-louise-adventure', strings), createHikePillValue(strings), createCanoePillValue(strings), createMoneyPillValue('lake-louise-adventure', strings)]
|
|
120
|
-
},
|
|
121
|
-
EMERALD_LAKE_ESCAPE: {
|
|
122
|
-
id: 'emerald-lake-escape',
|
|
123
|
-
videoUrl: { src: '/videos/emerald-lake-escape-tour.mp4', webm: '/videos/emerald-lake-escape-tour.webm', longSrc: '/videos/emerald-lake-escape-tour-long.mp4', longWebm: '/videos/emerald-lake-escape-tour-long.webm' },
|
|
124
|
-
name: strings.products.productNames.emeraldLakeEscapeTour,
|
|
125
|
-
shortName: strings.products.productShortNames.emeraldLakeEscapeTourShort,
|
|
126
|
-
images: [
|
|
127
|
-
IMAGES.EMERALD_LAKE_LODGE_VIEWPOINT,
|
|
128
|
-
],
|
|
129
|
-
bookingLink: '#book-now/emerald-lake-escape',
|
|
130
|
-
currentStartTime: strings.products.startTimes.emeraldLakeEscape,
|
|
131
|
-
description: strings.productThemePages.emeraldLakeShuttle.description,
|
|
132
|
-
path: '/emerald-lake-shuttle',
|
|
133
|
-
avgPrice: 175,
|
|
134
|
-
pillValues: [createDeparturePillValue('emerald-lake-escape', strings), createDurationPillValue('emerald-lake-escape', strings), ...createemeraldLakeEscapeTourLocationsPillValues(strings), createLunchPillValue(strings), createMoneyPillValue('emerald-lake-escape', strings)]
|
|
135
|
-
},
|
|
136
|
-
PRIVATE_TOUR: {
|
|
137
|
-
id: 'private-tour',
|
|
138
|
-
videoUrl: { src: '/videos/private-tour.mp4', webm: '/videos/private-tour.webm', longSrc: '/videos/private-tour-long.mp4', longWebm: '/videos/private-tour-long.webm' },
|
|
139
|
-
name: strings.products.productNames.privateTour,
|
|
140
|
-
shortName: strings.products.productShortNames.privateTourShort,
|
|
141
|
-
images: [
|
|
142
|
-
IMAGES.PRIVATE_TOUR_FAMILY_MORAINE_LAKE,
|
|
143
|
-
],
|
|
144
|
-
bookingLink: '#book-now/private-tour',
|
|
145
|
-
currentStartTime: strings.products.startTimes.privateTour,
|
|
146
|
-
description: strings.productThemePages.privateTours.description,
|
|
147
|
-
path: '/private-shuttle',
|
|
148
|
-
avgPrice: 1699,
|
|
149
|
-
pillValues: [createDeparturePillValue('private-tour', strings), createDurationPillValue('private-tour', strings), createCroissantPillValue(strings), createHotDrinksPillValue(strings), createMoneyPillValue('private-tour', strings)]
|
|
150
|
-
}
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
// Default export for backward compatibility (uses English strings)
|
|
154
|
-
import defaultStrings from '@/strings';
|
|
155
|
-
export const PRODUCTS = getProducts(defaultStrings);
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
createContext,
|
|
5
|
-
useContext,
|
|
6
|
-
useRef,
|
|
7
|
-
useCallback,
|
|
8
|
-
type ReactNode,
|
|
9
|
-
} from 'react';
|
|
10
|
-
import type { Availability, PricingConfig, PrecomputedPricesByCategory } from '@/lib/booking-api';
|
|
11
|
-
|
|
12
|
-
/** Cache TTL in milliseconds (5 minutes). Entries older than this are considered stale. */
|
|
13
|
-
export const AVAILABILITIES_CACHE_TTL_MS = 5 * 60 * 1000;
|
|
14
|
-
|
|
15
|
-
export interface CachedAvailabilitiesData {
|
|
16
|
-
fetchedRanges: Array<{ start: Date; end: Date }>;
|
|
17
|
-
availabilities: Availability[];
|
|
18
|
-
pricingConfig: PricingConfig | null;
|
|
19
|
-
precomputedPricesByOption: Record<string, PrecomputedPricesByCategory> | null;
|
|
20
|
-
/** Private Shuttle: shared precomputed prices (category -> currency -> price). */
|
|
21
|
-
precomputedPrices?: PrecomputedPricesByCategory | null;
|
|
22
|
-
/** Private Shuttle: resource price by currency. */
|
|
23
|
-
resourcePriceByCurrency?: Record<string, number> | null;
|
|
24
|
-
/** Private Shuttle: resource price by option (optionId -> currency -> price). */
|
|
25
|
-
resourcePriceByOption?: Record<string, Record<string, number>> | null;
|
|
26
|
-
/** Timestamp when this entry was cached (for TTL / stale-while-revalidate). */
|
|
27
|
-
cachedAt: number;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
interface AvailabilitiesCacheContextValue {
|
|
31
|
-
/** Get cached data for a product+options+promo. Returns undefined if not cached. */
|
|
32
|
-
get: (cacheKey: string) => CachedAvailabilitiesData | undefined;
|
|
33
|
-
/** Check if cached entry is stale (older than TTL). Use for stale-while-revalidate. */
|
|
34
|
-
isStale: (cached: CachedAvailabilitiesData) => boolean;
|
|
35
|
-
/** Replace or set cache entry. */
|
|
36
|
-
set: (cacheKey: string, data: CachedAvailabilitiesData) => void;
|
|
37
|
-
/** Merge new availabilities into existing cache (or create new entry). */
|
|
38
|
-
merge: (
|
|
39
|
-
cacheKey: string,
|
|
40
|
-
update: {
|
|
41
|
-
fetchedRanges?: Array<{ start: Date; end: Date }>;
|
|
42
|
-
availabilities?: Availability[];
|
|
43
|
-
pricingConfig?: PricingConfig | null;
|
|
44
|
-
precomputedPricesByOption?: Record<string, PrecomputedPricesByCategory> | null;
|
|
45
|
-
precomputedPrices?: PrecomputedPricesByCategory | null;
|
|
46
|
-
resourcePriceByCurrency?: Record<string, number> | null;
|
|
47
|
-
resourcePriceByOption?: Record<string, Record<string, number>> | null;
|
|
48
|
-
}
|
|
49
|
-
) => void;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const AvailabilitiesCacheContext = createContext<AvailabilitiesCacheContextValue | null>(null);
|
|
53
|
-
|
|
54
|
-
export function useAvailabilitiesCache() {
|
|
55
|
-
const ctx = useContext(AvailabilitiesCacheContext);
|
|
56
|
-
return ctx; // May be null if not wrapped
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/** Build cache key from product + options + promo + optional partner pricing profile. */
|
|
60
|
-
export function buildAvailabilitiesCacheKey(
|
|
61
|
-
productId: string,
|
|
62
|
-
optionIdsKey: string,
|
|
63
|
-
promoCode: string | null,
|
|
64
|
-
pricingProfileId?: string | null
|
|
65
|
-
): string {
|
|
66
|
-
const pp = (pricingProfileId ?? '').trim();
|
|
67
|
-
return `${productId}|${optionIdsKey}|${promoCode ?? ''}|pp:${pp}`;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export function AvailabilitiesCacheProvider({ children }: { children: ReactNode }) {
|
|
71
|
-
const cacheRef = useRef<Map<string, CachedAvailabilitiesData>>(new Map());
|
|
72
|
-
|
|
73
|
-
const get = useCallback((cacheKey: string) => {
|
|
74
|
-
return cacheRef.current.get(cacheKey);
|
|
75
|
-
}, []);
|
|
76
|
-
|
|
77
|
-
const isStale = useCallback((cached: CachedAvailabilitiesData) => {
|
|
78
|
-
const age = Date.now() - (cached.cachedAt ?? 0);
|
|
79
|
-
return age > AVAILABILITIES_CACHE_TTL_MS;
|
|
80
|
-
}, []);
|
|
81
|
-
|
|
82
|
-
const set = useCallback((cacheKey: string, data: CachedAvailabilitiesData) => {
|
|
83
|
-
cacheRef.current.set(cacheKey, data);
|
|
84
|
-
}, []);
|
|
85
|
-
|
|
86
|
-
const merge = useCallback(
|
|
87
|
-
(
|
|
88
|
-
cacheKey: string,
|
|
89
|
-
update: {
|
|
90
|
-
fetchedRanges?: Array<{ start: Date; end: Date }>;
|
|
91
|
-
availabilities?: Availability[];
|
|
92
|
-
pricingConfig?: PricingConfig | null;
|
|
93
|
-
precomputedPricesByOption?: Record<string, PrecomputedPricesByCategory> | null;
|
|
94
|
-
precomputedPrices?: PrecomputedPricesByCategory | null;
|
|
95
|
-
resourcePriceByCurrency?: Record<string, number> | null;
|
|
96
|
-
resourcePriceByOption?: Record<string, Record<string, number>> | null;
|
|
97
|
-
}
|
|
98
|
-
) => {
|
|
99
|
-
const existing = cacheRef.current.get(cacheKey);
|
|
100
|
-
const next: CachedAvailabilitiesData = {
|
|
101
|
-
fetchedRanges: update.fetchedRanges ?? existing?.fetchedRanges ?? [],
|
|
102
|
-
availabilities: update.availabilities ?? existing?.availabilities ?? [],
|
|
103
|
-
pricingConfig: update.pricingConfig !== undefined ? update.pricingConfig : existing?.pricingConfig ?? null,
|
|
104
|
-
precomputedPricesByOption:
|
|
105
|
-
update.precomputedPricesByOption !== undefined
|
|
106
|
-
? update.precomputedPricesByOption
|
|
107
|
-
: existing?.precomputedPricesByOption ?? null,
|
|
108
|
-
precomputedPrices: update.precomputedPrices !== undefined ? update.precomputedPrices : existing?.precomputedPrices ?? null,
|
|
109
|
-
resourcePriceByCurrency: update.resourcePriceByCurrency !== undefined ? update.resourcePriceByCurrency : existing?.resourcePriceByCurrency ?? null,
|
|
110
|
-
resourcePriceByOption: update.resourcePriceByOption !== undefined ? update.resourcePriceByOption : existing?.resourcePriceByOption ?? null,
|
|
111
|
-
cachedAt: Date.now(),
|
|
112
|
-
};
|
|
113
|
-
cacheRef.current.set(cacheKey, next);
|
|
114
|
-
},
|
|
115
|
-
[]
|
|
116
|
-
);
|
|
117
|
-
|
|
118
|
-
const value: AvailabilitiesCacheContextValue = { get, set, merge, isStale };
|
|
119
|
-
|
|
120
|
-
return (
|
|
121
|
-
<AvailabilitiesCacheContext.Provider value={value}>
|
|
122
|
-
{children}
|
|
123
|
-
</AvailabilitiesCacheContext.Provider>
|
|
124
|
-
);
|
|
125
|
-
}
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { createContext, useContext, useState, useEffect, ReactNode } from 'react';
|
|
4
|
-
import { getCompany, type Company } from '@/lib/booking-api';
|
|
5
|
-
import { ENV } from '@/lib/env';
|
|
6
|
-
|
|
7
|
-
interface CompanyContextType {
|
|
8
|
-
company: Company | null;
|
|
9
|
-
loading: boolean;
|
|
10
|
-
error: string | null;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const CompanyContext = createContext<CompanyContextType | undefined>(undefined);
|
|
14
|
-
|
|
15
|
-
interface CompanyProviderProps {
|
|
16
|
-
children: ReactNode;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Provider component that fetches company data once and makes it available via context
|
|
21
|
-
*/
|
|
22
|
-
export function CompanyProvider({ children }: CompanyProviderProps) {
|
|
23
|
-
const [company, setCompany] = useState<Company | null>(null);
|
|
24
|
-
const [loading, setLoading] = useState(true);
|
|
25
|
-
const [error, setError] = useState<string | null>(null);
|
|
26
|
-
|
|
27
|
-
useEffect(() => {
|
|
28
|
-
async function fetchCompany() {
|
|
29
|
-
try {
|
|
30
|
-
const companyData = await getCompany(ENV.COMPANY_ID);
|
|
31
|
-
setCompany(companyData);
|
|
32
|
-
setError(null);
|
|
33
|
-
} catch (err) {
|
|
34
|
-
console.warn('Failed to fetch company data, using defaults:', err);
|
|
35
|
-
setError(err instanceof Error ? err.message : 'Failed to fetch company');
|
|
36
|
-
// Don't set company to null - let components use defaults
|
|
37
|
-
} finally {
|
|
38
|
-
setLoading(false);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
fetchCompany();
|
|
43
|
-
}, []);
|
|
44
|
-
|
|
45
|
-
return (
|
|
46
|
-
<CompanyContext.Provider value={{ company, loading, error }}>
|
|
47
|
-
{children}
|
|
48
|
-
</CompanyContext.Provider>
|
|
49
|
-
);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Hook to access company context
|
|
54
|
-
* Returns company data, loading state, and error
|
|
55
|
-
*/
|
|
56
|
-
export function useCompany() {
|
|
57
|
-
const context = useContext(CompanyContext);
|
|
58
|
-
if (context === undefined) {
|
|
59
|
-
throw new Error('useCompany must be used within a CompanyProvider');
|
|
60
|
-
}
|
|
61
|
-
return context;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Hook to get company timezone with fallback
|
|
66
|
-
*/
|
|
67
|
-
export function useCompanyTimezone(): string {
|
|
68
|
-
const { company } = useCompany();
|
|
69
|
-
return company?.settings?.timezone || 'America/Edmonton';
|
|
70
|
-
}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"paragraphs": [
|
|
3
|
-
"Looking to create memories with your loved ones in one of the most breathtaking places on earth? Let us help you turn it into an unforgettable experience.",
|
|
4
|
-
"Whether it's a joyful family gathering, a meaningful moment with friends, or a romantic couples shoot, we know how to blend into the moment while capturing it beautifully - candid, natural, and full of connection."
|
|
5
|
-
],
|
|
6
|
-
"sections": [
|
|
7
|
-
{
|
|
8
|
-
"title": "What's included",
|
|
9
|
-
"content": [
|
|
10
|
-
{
|
|
11
|
-
"title": "30-minute session",
|
|
12
|
-
"items": [
|
|
13
|
-
"πΈ 20 to 25 minutes of photography time",
|
|
14
|
-
"π 2 Locations within walking distance",
|
|
15
|
-
"πΌοΈ 25 professionally edited photos delivered in a personalized online gallery"
|
|
16
|
-
]
|
|
17
|
-
},
|
|
18
|
-
{
|
|
19
|
-
"title": "60-minute session",
|
|
20
|
-
"items": [
|
|
21
|
-
"πΈ 50 to 55 minutes of photography time",
|
|
22
|
-
"π 2-3 Locations within walking distance",
|
|
23
|
-
"πΌοΈ 50 professionally edited photos delivered in a personalized online gallery"
|
|
24
|
-
]
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
"title": "90-minute session",
|
|
28
|
-
"items": [
|
|
29
|
-
"πΈ 80-90 mins of Photography Time",
|
|
30
|
-
"π 5 Locations within walking distance",
|
|
31
|
-
"πΌοΈ 75 professionally edited photos delivered in a personalized online gallery"
|
|
32
|
-
]
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
"title": "Other details",
|
|
36
|
-
"items": [
|
|
37
|
-
"π₯οΈ Editing: Basic exposure and lighting adjustments + colour corrections",
|
|
38
|
-
"ππ½ 10 People Max per session"
|
|
39
|
-
]
|
|
40
|
-
}
|
|
41
|
-
]
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
"title": "Available add ons",
|
|
45
|
-
"content": [
|
|
46
|
-
"πββοΈ Express Service - 7 Day turnaround on images - $300",
|
|
47
|
-
"πββοΈπ¨ Super Express Service - 48-hour turnaround on images - $900",
|
|
48
|
-
"π¬ Additional Editing: Object Removal / Skin Retouching - $199/item",
|
|
49
|
-
"β Additional Images: 15 Additional Images - $99",
|
|
50
|
-
"βΎοΈ The Works - Get ALL additional images taken on the day - $199"
|
|
51
|
-
]
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
"title": "Exclusions",
|
|
55
|
-
"content": [
|
|
56
|
-
"π΅ Tips or Gratuities",
|
|
57
|
-
"π
ΏοΈ Parking Fees"
|
|
58
|
-
]
|
|
59
|
-
}
|
|
60
|
-
]
|
|
61
|
-
}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"paragraphs": [
|
|
3
|
-
"Planning to elope in one of the most breathtaking places on earth? Let us help you make it truly unforgettable. We know how to blend into the moment while capturing the joy of an intimate celebration beautifully."
|
|
4
|
-
],
|
|
5
|
-
"sections": [
|
|
6
|
-
{
|
|
7
|
-
"title": "What's included",
|
|
8
|
-
"content": [
|
|
9
|
-
{
|
|
10
|
-
"title": "30-minute session",
|
|
11
|
-
"items": [
|
|
12
|
-
"πΈ 20 to 25 minutes of photography time",
|
|
13
|
-
"π 2 Locations within walking distance",
|
|
14
|
-
"πΌοΈ 25 professionally edited photos delivered in a personalized online gallery"
|
|
15
|
-
]
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
"title": "60-minute session",
|
|
19
|
-
"items": [
|
|
20
|
-
"πΈ 50 to 55 minutes of photography time",
|
|
21
|
-
"π 2-3 Locations within walking distance",
|
|
22
|
-
"πΌοΈ 50 professionally edited photos delivered in a personalized online gallery"
|
|
23
|
-
]
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
"title": "90-minute session",
|
|
27
|
-
"items": [
|
|
28
|
-
"πΈ 80-90 mins of Photography Time",
|
|
29
|
-
"π 5 Locations within walking distance",
|
|
30
|
-
"πΌοΈ 75 professionally edited photos delivered in a personalized online gallery"
|
|
31
|
-
]
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
"title": "Other details",
|
|
35
|
-
"items": [
|
|
36
|
-
"π₯οΈ Editing: Basic exposure and lighting adjustments + colour corrections",
|
|
37
|
-
"ππ½ 2 People Max per session"
|
|
38
|
-
]
|
|
39
|
-
}
|
|
40
|
-
]
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
"title": "Available add ons",
|
|
44
|
-
"content": [
|
|
45
|
-
"πββοΈ Express Service - 7 Day turnaround on images - $300",
|
|
46
|
-
"πββοΈπ¨ Super Express Service - 48-hour turnaround on images - $900",
|
|
47
|
-
"π¬ Additional Editing: Object Removal / Skin Retouching - $199/item",
|
|
48
|
-
"β Additional Images: 15 Additional Images - $99",
|
|
49
|
-
"βΎοΈ The Works - Get ALL additional images taken on the day - $199"
|
|
50
|
-
]
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
"title": "Exclusions",
|
|
54
|
-
"content": [
|
|
55
|
-
"π΅ Tips or Gratuities",
|
|
56
|
-
"π
ΏοΈ Parking Fees"
|
|
57
|
-
]
|
|
58
|
-
}
|
|
59
|
-
]
|
|
60
|
-
}
|