@ticketboothapp/booking 1.2.47 → 1.2.49
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/BookingDetails.ts +18 -0
- package/src/components/booking/AddOnsSection.tsx +2 -2
- package/src/components/booking/AdminPaymentChoiceModal.tsx +1 -1
- package/src/components/booking/BookingDialog.tsx +1 -1
- package/src/components/booking/BookingFlow.tsx +16 -16
- package/src/components/booking/BookingFlowCollage.tsx +3 -2
- package/src/components/booking/BookingFlowPlaceholder.tsx +1 -1
- package/src/components/booking/BookingFlowPreview.tsx +1 -1
- package/src/components/booking/BookingProductGrid.tsx +4 -3
- package/src/components/booking/Calendar.module.css +2 -1
- package/src/components/booking/Calendar.tsx +5 -5
- package/src/components/booking/CancellationPolicySelector.tsx +2 -2
- package/src/components/booking/ChangeBookingDialog.tsx +6 -5
- package/src/components/booking/CheckoutForm.tsx +1 -1
- package/src/components/booking/CheckoutModal.tsx +3 -3
- package/src/components/booking/DapTourDescription.tsx +3 -3
- package/src/components/booking/DependentAddOnBookingDialog.tsx +7 -7
- package/src/components/booking/DependentAddOnPaymentForm.tsx +1 -1
- 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 +8 -8
- package/src/components/booking/PickupTimeSelector.tsx +2 -2
- package/src/components/booking/PriceBreakdown.tsx +4 -4
- package/src/components/booking/PriceSummary.tsx +3 -3
- package/src/components/booking/PrivateShuttleBookingFlow.tsx +11 -11
- package/src/components/booking/PromoCodeInput.tsx +1 -1
- package/src/components/booking/ReturnTimeSelector.tsx +2 -2
- package/src/components/booking/TicketSelector.tsx +1 -1
- package/src/components/booking/TourDescription.tsx +1 -1
- package/src/constants/images.ts +556 -0
- package/src/constants/products.ts +33 -0
- package/src/contexts/AvailabilitiesCacheContext.tsx +125 -0
- package/src/contexts/CompanyContext.tsx +70 -0
- package/src/data/dap-descriptions/session-couples-families-friends.en.json +61 -0
- package/src/data/dap-descriptions/session-elopements.en.json +60 -0
- package/src/data/dap-descriptions/session-proposals.en.json +60 -0
- package/src/hooks/useBookingSourceMetadataFromLocation.ts +1 -1
- package/src/hooks/useIsBookingLaunchLive.ts +1 -1
- package/src/lib/booking/booking-source.ts +51 -0
- package/src/lib/booking/checkout-breakdown.ts +69 -0
- package/src/lib/booking/correlation-id.ts +46 -0
- package/src/lib/booking/i18n/config.ts +21 -0
- package/src/lib/booking/i18n/index.tsx +144 -0
- package/src/lib/booking/i18n/messages/en.json +236 -0
- package/src/lib/booking/i18n/messages/fr.json +236 -0
- package/src/lib/booking/itinerary-display.ts +36 -0
- package/src/lib/booking/itinerary-labels.ts +70 -0
- package/src/lib/booking/location-calculations.ts +43 -0
- package/src/lib/booking/location-utils.ts +165 -0
- package/src/lib/booking/map-utils.ts +153 -0
- package/src/lib/booking/marker-icons.ts +113 -0
- package/src/lib/booking/normalize-booking-product-id.ts +21 -0
- package/src/lib/booking/pickup-location-types.ts +25 -0
- package/src/lib/booking/places-api.ts +154 -0
- package/src/lib/booking/pricing.ts +466 -0
- package/src/lib/booking/product-option-id.ts +35 -0
- package/src/lib/booking/source-metadata.ts +226 -0
- package/src/lib/booking/sunday-week.ts +14 -0
- package/src/lib/booking/trace-context.ts +62 -0
- package/src/lib/booking/utils.ts +9 -0
- package/src/lib/booking-api.ts +1793 -0
- package/src/lib/booking-constants.ts +23 -0
- package/src/lib/booking-ref.ts +13 -0
- package/src/lib/booking-types.ts +36 -0
- package/src/lib/currency.ts +81 -0
- package/src/lib/dap-descriptions.ts +50 -0
- package/src/lib/dap-itinerary-preview.ts +315 -0
- package/src/lib/dependent-add-on-api.ts +434 -0
- package/src/lib/env.ts +102 -0
- package/src/lib/manage-booking-post-checkout.ts +68 -0
- package/src/lib/photo-dap-config.ts +228 -0
- package/src/providers/booking-dialog-provider.tsx +3 -3
- package/src/providers/dependent-add-on-dialog-provider.tsx +105 -0
- package/src/strings/en.json +1774 -0
- package/src/strings/es.json +1573 -0
- package/src/strings/fr.json +1573 -0
- package/src/strings/index.js +23 -0
- package/src/types.d.ts +11 -0
- package/ticketboothapp-booking-1.2.48.tgz +0 -0
- package/tsconfig.json +3 -2
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maps photo-sessions packages to TicketBooth dependent add-on product (and optional option) IDs.
|
|
3
|
+
* Set NEXT_PUBLIC_DAP_* in .env.local to override seed defaults (e.g. other envs or 60/90 min options).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { IMAGES } from '../constants/images';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Default full-refund cancellation window (days before the scheduled photo session).
|
|
10
|
+
* Override per catalog entry in `getPhotoDapCatalog` when a DAP product differs.
|
|
11
|
+
*/
|
|
12
|
+
export const DEFAULT_PHOTO_DAP_CANCELLATION_DAYS_BEFORE_SESSION = 30;
|
|
13
|
+
|
|
14
|
+
/** Seeded test DAP: Friends, Family, & Couples (`dependent-add-on-products.json`) */
|
|
15
|
+
const COUPLES_SESSION_DEFAULT_PRODUCT_ID = 'dap_sM8pSzbFfU7P';
|
|
16
|
+
const ELOPEMENTS_SESSION_DEFAULT_PRODUCT_ID = 'dap_6tITweGzVyg8';
|
|
17
|
+
const PROPOSALS_SESSION_DEFAULT_PRODUCT_ID = 'dap_Xy3OCPIKQ7vr';
|
|
18
|
+
|
|
19
|
+
/** Session lengths for couples / families card — matches `dependent-add-on-products.json` options */
|
|
20
|
+
const COUPLES_SESSION_PRODUCT_OPTIONS = [
|
|
21
|
+
{
|
|
22
|
+
dependentAddOnProductOptionId: 'dapo_Ey0v4O7BjFaD',
|
|
23
|
+
label: '30 minutes',
|
|
24
|
+
photosLabel: '25 photos',
|
|
25
|
+
startingAtLabel: 'Starting at $499',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
dependentAddOnProductOptionId: 'dapo_caJh25HsTwKx',
|
|
29
|
+
label: '60 minutes',
|
|
30
|
+
photosLabel: '50 photos',
|
|
31
|
+
startingAtLabel: 'Starting at $799',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
dependentAddOnProductOptionId: 'dapo_6Km5njalQBp1',
|
|
35
|
+
label: '90 minutes',
|
|
36
|
+
photosLabel: '75 photos',
|
|
37
|
+
startingAtLabel: 'Starting at $799',
|
|
38
|
+
},
|
|
39
|
+
] as const;
|
|
40
|
+
|
|
41
|
+
const ELOPEMENTS_SESSION_PRODUCT_OPTIONS = [
|
|
42
|
+
{
|
|
43
|
+
dependentAddOnProductOptionId: 'dapo_XTeJt09NyNfX',
|
|
44
|
+
label: '30 minutes',
|
|
45
|
+
photosLabel: '25 photos',
|
|
46
|
+
startingAtLabel: 'Starting at $799',
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
dependentAddOnProductOptionId: 'dapo_Y8AxYt6Tjaam',
|
|
50
|
+
label: '60 minutes',
|
|
51
|
+
photosLabel: '50 photos',
|
|
52
|
+
startingAtLabel: 'Starting at $999',
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
dependentAddOnProductOptionId: 'dapo_YdmxIiQPxEJg',
|
|
56
|
+
label: '90 minutes',
|
|
57
|
+
photosLabel: '75 photos',
|
|
58
|
+
startingAtLabel: 'Starting at $1199',
|
|
59
|
+
},
|
|
60
|
+
] as const;
|
|
61
|
+
|
|
62
|
+
const PROPOSALS_SESSION_PRODUCT_OPTIONS = [
|
|
63
|
+
{
|
|
64
|
+
dependentAddOnProductOptionId: 'dapo_SMRxjfDpIvwU',
|
|
65
|
+
label: '30 minutes',
|
|
66
|
+
photosLabel: '25 photos',
|
|
67
|
+
startingAtLabel: 'Starting at $799',
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
dependentAddOnProductOptionId: 'dapo_FyMbFgrBU4L8',
|
|
71
|
+
label: '60 minutes',
|
|
72
|
+
photosLabel: '50 photos',
|
|
73
|
+
startingAtLabel: 'Starting at $999',
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
dependentAddOnProductOptionId: 'dapo_EOohfF0g2i1D',
|
|
77
|
+
label: '90 minutes',
|
|
78
|
+
photosLabel: '75 photos',
|
|
79
|
+
startingAtLabel: 'Starting at $1199',
|
|
80
|
+
},
|
|
81
|
+
] as const;
|
|
82
|
+
|
|
83
|
+
export const PHOTO_DAP_SLUGS = [
|
|
84
|
+
'session-couples-families-friends',
|
|
85
|
+
'session-elopements',
|
|
86
|
+
'session-proposals',
|
|
87
|
+
] as const;
|
|
88
|
+
|
|
89
|
+
export type PhotoDapSlug = (typeof PHOTO_DAP_SLUGS)[number];
|
|
90
|
+
|
|
91
|
+
export type PhotoDapProductOption = {
|
|
92
|
+
dependentAddOnProductOptionId: string;
|
|
93
|
+
/** First line on the tile (e.g. duration). */
|
|
94
|
+
label: string;
|
|
95
|
+
/** Second line (e.g. edited photo count). */
|
|
96
|
+
photosLabel?: string;
|
|
97
|
+
/** Third line (e.g. "Starting at $399"). */
|
|
98
|
+
startingAtLabel?: string;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export type PhotoDapCatalog = {
|
|
102
|
+
dependentAddOnProductId: string;
|
|
103
|
+
/** When set, that option is fixed (no picker). Env `NEXT_PUBLIC_DAP_PHOTO_SESSION_COUPLES_OPTION_ID` forces this for couples. */
|
|
104
|
+
dependentAddOnProductOptionId?: string;
|
|
105
|
+
/** When multiple entries and no fixed option id, the dialog shows a session-length control */
|
|
106
|
+
productOptions?: PhotoDapProductOption[];
|
|
107
|
+
/** Bunny CDN IDs for DapFlowCollage (hero + four-tile grid) */
|
|
108
|
+
collageImageIds: string[];
|
|
109
|
+
/**
|
|
110
|
+
* Days before the scheduled photo session that a full refund still applies for this DAP product.
|
|
111
|
+
* Should match TicketBooth dependent add-on product config; availability API may override when present.
|
|
112
|
+
*/
|
|
113
|
+
cancellationDaysBeforeSession: number;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/** Image sets for the dependent add-on dialog collage (photos only; no video). */
|
|
117
|
+
export function photoDapCollageImageIds(slug: PhotoDapSlug): string[] {
|
|
118
|
+
switch (slug) {
|
|
119
|
+
case 'session-couples-families-friends':
|
|
120
|
+
return [
|
|
121
|
+
IMAGES.FAMILY_MORIANE_LAKE_ROCKPILE.id,
|
|
122
|
+
IMAGES.PRIVATE_TOUR_FAMILY_MORAINE_LAKE.id,
|
|
123
|
+
IMAGES.GIRLS_DAY_RAINY_LAKE_LOUISE.id,
|
|
124
|
+
IMAGES.MORAINE_LAKE_COUPLE_AT_WATER.id,
|
|
125
|
+
IMAGES.MORAINE_LAKE_SUNRISE_GIRL_FRIENDS.id,
|
|
126
|
+
];
|
|
127
|
+
case 'session-elopements':
|
|
128
|
+
return [
|
|
129
|
+
IMAGES.MORAINE_LAKE_SUNRISE_ELOPEMENT.id,
|
|
130
|
+
IMAGES.SENTINEL_PASS_ELOPEMENT_SNOW.id,
|
|
131
|
+
IMAGES.MORAINE_LAKE_ELOPEMENT.id,
|
|
132
|
+
IMAGES.SENTINEL_PASS_ELOPEMENT_SNOW_HORIZONTAL.id,
|
|
133
|
+
IMAGES.MORAINE_LAKE_ELOPEMENT_LAKESHORE.id,
|
|
134
|
+
];
|
|
135
|
+
case 'session-proposals':
|
|
136
|
+
return [
|
|
137
|
+
IMAGES.LAKE_LOUISE_KISS.id,
|
|
138
|
+
IMAGES.MORAINE_LAKE_PROPOSE_CRY.id,
|
|
139
|
+
IMAGES.MORAINE_LAKE_OVERCAST_PROPOSE.id,
|
|
140
|
+
IMAGES.MORAINE_LAKE_PROPOSAL.id,
|
|
141
|
+
IMAGES.MORAINE_LAKE_PROPOSE.id,
|
|
142
|
+
];
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function trimEnv(value: string | undefined): string {
|
|
147
|
+
return (value ?? '').trim();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Returns catalog when product id is known (env or built-in default for couples session).
|
|
152
|
+
* Otherwise null (card falls back to #book-now).
|
|
153
|
+
*/
|
|
154
|
+
export function getPhotoDapCatalog(slug: PhotoDapSlug): PhotoDapCatalog | null {
|
|
155
|
+
if (slug === 'session-couples-families-friends') {
|
|
156
|
+
const productId =
|
|
157
|
+
trimEnv(process.env.NEXT_PUBLIC_DAP_PHOTO_SESSION_COUPLES_PRODUCT_ID) ||
|
|
158
|
+
COUPLES_SESSION_DEFAULT_PRODUCT_ID;
|
|
159
|
+
const forcedOptionId = trimEnv(
|
|
160
|
+
process.env.NEXT_PUBLIC_DAP_PHOTO_SESSION_COUPLES_OPTION_ID
|
|
161
|
+
);
|
|
162
|
+
const collageImageIds = photoDapCollageImageIds('session-couples-families-friends');
|
|
163
|
+
if (forcedOptionId) {
|
|
164
|
+
return {
|
|
165
|
+
dependentAddOnProductId: productId,
|
|
166
|
+
dependentAddOnProductOptionId: forcedOptionId,
|
|
167
|
+
collageImageIds,
|
|
168
|
+
cancellationDaysBeforeSession: DEFAULT_PHOTO_DAP_CANCELLATION_DAYS_BEFORE_SESSION,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
dependentAddOnProductId: productId,
|
|
173
|
+
productOptions: [...COUPLES_SESSION_PRODUCT_OPTIONS],
|
|
174
|
+
collageImageIds,
|
|
175
|
+
cancellationDaysBeforeSession: DEFAULT_PHOTO_DAP_CANCELLATION_DAYS_BEFORE_SESSION,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (slug === 'session-elopements') {
|
|
180
|
+
const productId =
|
|
181
|
+
trimEnv(process.env.NEXT_PUBLIC_DAP_PHOTO_SESSION_ELOPEMENTS_PRODUCT_ID) ||
|
|
182
|
+
ELOPEMENTS_SESSION_DEFAULT_PRODUCT_ID;
|
|
183
|
+
const forcedOptionId = trimEnv(
|
|
184
|
+
process.env.NEXT_PUBLIC_DAP_PHOTO_SESSION_ELOPEMENTS_OPTION_ID
|
|
185
|
+
);
|
|
186
|
+
const collageImageIds = photoDapCollageImageIds('session-elopements');
|
|
187
|
+
if (forcedOptionId) {
|
|
188
|
+
return {
|
|
189
|
+
dependentAddOnProductId: productId,
|
|
190
|
+
dependentAddOnProductOptionId: forcedOptionId,
|
|
191
|
+
collageImageIds,
|
|
192
|
+
cancellationDaysBeforeSession: DEFAULT_PHOTO_DAP_CANCELLATION_DAYS_BEFORE_SESSION,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
dependentAddOnProductId: productId,
|
|
197
|
+
productOptions: [...ELOPEMENTS_SESSION_PRODUCT_OPTIONS],
|
|
198
|
+
collageImageIds,
|
|
199
|
+
cancellationDaysBeforeSession: DEFAULT_PHOTO_DAP_CANCELLATION_DAYS_BEFORE_SESSION,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (slug === 'session-proposals') {
|
|
204
|
+
const productId =
|
|
205
|
+
trimEnv(process.env.NEXT_PUBLIC_DAP_PHOTO_SESSION_PROPOSALS_PRODUCT_ID) ||
|
|
206
|
+
PROPOSALS_SESSION_DEFAULT_PRODUCT_ID;
|
|
207
|
+
const forcedOptionId = trimEnv(
|
|
208
|
+
process.env.NEXT_PUBLIC_DAP_PHOTO_SESSION_PROPOSALS_OPTION_ID
|
|
209
|
+
);
|
|
210
|
+
const collageImageIds = photoDapCollageImageIds('session-proposals');
|
|
211
|
+
if (forcedOptionId) {
|
|
212
|
+
return {
|
|
213
|
+
dependentAddOnProductId: productId,
|
|
214
|
+
dependentAddOnProductOptionId: forcedOptionId,
|
|
215
|
+
collageImageIds,
|
|
216
|
+
cancellationDaysBeforeSession: DEFAULT_PHOTO_DAP_CANCELLATION_DAYS_BEFORE_SESSION,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
return {
|
|
220
|
+
dependentAddOnProductId: productId,
|
|
221
|
+
productOptions: [...PROPOSALS_SESSION_PRODUCT_OPTIONS],
|
|
222
|
+
collageImageIds,
|
|
223
|
+
cancellationDaysBeforeSession: DEFAULT_PHOTO_DAP_CANCELLATION_DAYS_BEFORE_SESSION,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
@@ -10,12 +10,12 @@ import {
|
|
|
10
10
|
useRef,
|
|
11
11
|
type ReactNode,
|
|
12
12
|
} from 'react';
|
|
13
|
-
import { getOrCreateBookingCorrelationId } from '
|
|
14
|
-
import { withBookingOutboundHeaders } from '
|
|
13
|
+
import { getOrCreateBookingCorrelationId } from '../lib/booking/correlation-id';
|
|
14
|
+
import { withBookingOutboundHeaders } from '../lib/booking/trace-context';
|
|
15
15
|
import {
|
|
16
16
|
isSuspiciousBookingProductId,
|
|
17
17
|
normalizeBookingProductId,
|
|
18
|
-
} from '
|
|
18
|
+
} from '../lib/booking/normalize-booking-product-id';
|
|
19
19
|
import { useBookingHostOptional } from '../runtime/BookingHostContext';
|
|
20
20
|
|
|
21
21
|
/** Filter IDs for the product grid. Must match BookingProductGrid FILTER_IDS. */
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
createContext,
|
|
5
|
+
useCallback,
|
|
6
|
+
useContext,
|
|
7
|
+
useMemo,
|
|
8
|
+
useRef,
|
|
9
|
+
useState,
|
|
10
|
+
type ReactNode,
|
|
11
|
+
} from 'react';
|
|
12
|
+
import type { PhotoDapSlug } from '../lib/photo-dap-config';
|
|
13
|
+
|
|
14
|
+
export type DependentAddOnProductOptionChoice = {
|
|
15
|
+
dependentAddOnProductOptionId: string;
|
|
16
|
+
label: string;
|
|
17
|
+
photosLabel?: string;
|
|
18
|
+
startingAtLabel?: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type DependentAddOnDialogOpenPayload = {
|
|
22
|
+
/** Card title shown in dialog header */
|
|
23
|
+
productDisplayTitle: string;
|
|
24
|
+
dependentAddOnProductId: string;
|
|
25
|
+
/** Fixed catalog option (no picker) */
|
|
26
|
+
dependentAddOnProductOptionId?: string;
|
|
27
|
+
/** When provided without a fixed option id, user picks one (e.g. 30 / 60 / 90 min) */
|
|
28
|
+
productOptions?: DependentAddOnProductOptionChoice[];
|
|
29
|
+
/**
|
|
30
|
+
* Default session-length id when multiple `productOptions` exist (e.g. manage-booking upsell probed 30 min).
|
|
31
|
+
* Unlike `dependentAddOnProductOptionId`, this does not hide the picker — the user can switch length.
|
|
32
|
+
*/
|
|
33
|
+
initialSelectedProductOptionId?: string;
|
|
34
|
+
/** Hero + grid images (DapFlowCollage); Bunny CDN IDs */
|
|
35
|
+
collageImageIds?: string[];
|
|
36
|
+
/** Loads expandable copy from dap-descriptions */
|
|
37
|
+
dapDescriptionSlug?: PhotoDapSlug;
|
|
38
|
+
/**
|
|
39
|
+
* From DAP catalog / TicketBooth product — days before the photo session for full-refund cancellation.
|
|
40
|
+
* Availability API may override when it returns the same field.
|
|
41
|
+
*/
|
|
42
|
+
cancellationDaysBeforeSession: number;
|
|
43
|
+
/**
|
|
44
|
+
* Pre-fill primary booking reference (e.g. manage-booking upsell after shuttle checkout).
|
|
45
|
+
* Accepts short or bookRef_ form; dialog normalizes for display.
|
|
46
|
+
*/
|
|
47
|
+
initialPrimaryBookingReference?: string;
|
|
48
|
+
/** Optional pre-fill for booking-owner verification on DAP availability checks. */
|
|
49
|
+
initialPrimaryBookingLastName?: string;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
interface DependentAddOnDialogContextValue {
|
|
53
|
+
isOpen: boolean;
|
|
54
|
+
payload: DependentAddOnDialogOpenPayload | null;
|
|
55
|
+
open: (p: DependentAddOnDialogOpenPayload) => void;
|
|
56
|
+
close: () => void;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const DependentAddOnDialogContext =
|
|
60
|
+
createContext<DependentAddOnDialogContextValue | null>(null);
|
|
61
|
+
|
|
62
|
+
export function useDependentAddOnDialog() {
|
|
63
|
+
const ctx = useContext(DependentAddOnDialogContext);
|
|
64
|
+
if (!ctx) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
'useDependentAddOnDialog must be used within DependentAddOnDialogProvider'
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
return ctx;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function DependentAddOnDialogProvider({ children }: { children: ReactNode }) {
|
|
73
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
74
|
+
const [payload, setPayload] = useState<DependentAddOnDialogOpenPayload | null>(null);
|
|
75
|
+
const previouslyFocusedRef = useRef<HTMLElement | null>(null);
|
|
76
|
+
|
|
77
|
+
const open = useCallback((p: DependentAddOnDialogOpenPayload) => {
|
|
78
|
+
previouslyFocusedRef.current =
|
|
79
|
+
document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
80
|
+
setPayload(p);
|
|
81
|
+
setIsOpen(true);
|
|
82
|
+
}, []);
|
|
83
|
+
|
|
84
|
+
const close = useCallback(() => {
|
|
85
|
+
setIsOpen(false);
|
|
86
|
+
setPayload(null);
|
|
87
|
+
const prev = previouslyFocusedRef.current;
|
|
88
|
+
requestAnimationFrame(() => {
|
|
89
|
+
if (prev && typeof prev.focus === 'function') {
|
|
90
|
+
prev.focus();
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}, []);
|
|
94
|
+
|
|
95
|
+
const value = useMemo(
|
|
96
|
+
() => ({ isOpen, payload, open, close }),
|
|
97
|
+
[isOpen, payload, open, close]
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
return (
|
|
101
|
+
<DependentAddOnDialogContext.Provider value={value}>
|
|
102
|
+
{children}
|
|
103
|
+
</DependentAddOnDialogContext.Provider>
|
|
104
|
+
);
|
|
105
|
+
}
|