@voyant-travel/catalog-react 0.117.2
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/LICENSE +201 -0
- package/README.md +36 -0
- package/dist/admin/catalog-vertical-host.d.ts +45 -0
- package/dist/admin/catalog-vertical-host.d.ts.map +1 -0
- package/dist/admin/catalog-vertical-host.js +230 -0
- package/dist/admin/cruise-detail-host.d.ts +11 -0
- package/dist/admin/cruise-detail-host.d.ts.map +1 -0
- package/dist/admin/cruise-detail-host.js +33 -0
- package/dist/admin/dynamic-catalog-host.d.ts +13 -0
- package/dist/admin/dynamic-catalog-host.d.ts.map +1 -0
- package/dist/admin/dynamic-catalog-host.js +17 -0
- package/dist/admin/index.d.ts +133 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +144 -0
- package/dist/admin/open-in-new-tab.d.ts +7 -0
- package/dist/admin/open-in-new-tab.d.ts.map +1 -0
- package/dist/admin/open-in-new-tab.js +10 -0
- package/dist/admin/pages/catalog-accommodations-detail-page.d.ts +4 -0
- package/dist/admin/pages/catalog-accommodations-detail-page.d.ts.map +1 -0
- package/dist/admin/pages/catalog-accommodations-detail-page.js +7 -0
- package/dist/admin/pages/catalog-accommodations-index-page.d.ts +8 -0
- package/dist/admin/pages/catalog-accommodations-index-page.d.ts.map +1 -0
- package/dist/admin/pages/catalog-accommodations-index-page.js +17 -0
- package/dist/admin/pages/catalog-cruises-detail-page.d.ts +4 -0
- package/dist/admin/pages/catalog-cruises-detail-page.d.ts.map +1 -0
- package/dist/admin/pages/catalog-cruises-detail-page.js +7 -0
- package/dist/admin/pages/catalog-cruises-index-page.d.ts +8 -0
- package/dist/admin/pages/catalog-cruises-index-page.d.ts.map +1 -0
- package/dist/admin/pages/catalog-cruises-index-page.js +19 -0
- package/dist/admin/pages/catalog-excursions-detail-page.d.ts +4 -0
- package/dist/admin/pages/catalog-excursions-detail-page.d.ts.map +1 -0
- package/dist/admin/pages/catalog-excursions-detail-page.js +7 -0
- package/dist/admin/pages/catalog-excursions-index-page.d.ts +8 -0
- package/dist/admin/pages/catalog-excursions-index-page.d.ts.map +1 -0
- package/dist/admin/pages/catalog-excursions-index-page.js +12 -0
- package/dist/admin/pages/catalog-products-detail-page.d.ts +8 -0
- package/dist/admin/pages/catalog-products-detail-page.d.ts.map +1 -0
- package/dist/admin/pages/catalog-products-detail-page.js +12 -0
- package/dist/admin/pages/catalog-products-index-page.d.ts +8 -0
- package/dist/admin/pages/catalog-products-index-page.d.ts.map +1 -0
- package/dist/admin/pages/catalog-products-index-page.js +12 -0
- package/dist/admin/pages/catalog-tours-detail-page.d.ts +4 -0
- package/dist/admin/pages/catalog-tours-detail-page.d.ts.map +1 -0
- package/dist/admin/pages/catalog-tours-detail-page.js +7 -0
- package/dist/admin/pages/catalog-tours-index-page.d.ts +8 -0
- package/dist/admin/pages/catalog-tours-index-page.d.ts.map +1 -0
- package/dist/admin/pages/catalog-tours-index-page.js +12 -0
- package/dist/admin/product-detail-host.d.ts +18 -0
- package/dist/admin/product-detail-host.d.ts.map +1 -0
- package/dist/admin/product-detail-host.js +40 -0
- package/dist/admin/scheduled-catalog-host.d.ts +15 -0
- package/dist/admin/scheduled-catalog-host.d.ts.map +1 -0
- package/dist/admin/scheduled-catalog-host.js +19 -0
- package/dist/admin/vertical-detail-host.d.ts +13 -0
- package/dist/admin/vertical-detail-host.d.ts.map +1 -0
- package/dist/admin/vertical-detail-host.js +62 -0
- package/dist/booking-engine/index.d.ts +26 -0
- package/dist/booking-engine/index.d.ts.map +1 -0
- package/dist/booking-engine/index.js +25 -0
- package/dist/booking-engine/use-booking-commit.d.ts +61 -0
- package/dist/booking-engine/use-booking-commit.d.ts.map +1 -0
- package/dist/booking-engine/use-booking-commit.js +47 -0
- package/dist/booking-engine/use-booking-draft-shape.d.ts +20 -0
- package/dist/booking-engine/use-booking-draft-shape.d.ts.map +1 -0
- package/dist/booking-engine/use-booking-draft-shape.js +9 -0
- package/dist/booking-engine/use-booking-draft.d.ts +102 -0
- package/dist/booking-engine/use-booking-draft.d.ts.map +1 -0
- package/dist/booking-engine/use-booking-draft.js +93 -0
- package/dist/booking-engine/use-booking-hold.d.ts +30 -0
- package/dist/booking-engine/use-booking-hold.d.ts.map +1 -0
- package/dist/booking-engine/use-booking-hold.js +44 -0
- package/dist/booking-engine/use-booking-journey-api.d.ts +23 -0
- package/dist/booking-engine/use-booking-journey-api.d.ts.map +1 -0
- package/dist/booking-engine/use-booking-journey-api.js +24 -0
- package/dist/booking-engine/use-booking-quote.d.ts +711 -0
- package/dist/booking-engine/use-booking-quote.d.ts.map +1 -0
- package/dist/booking-engine/use-booking-quote.js +122 -0
- package/dist/catalog-enrichment-mappers.d.ts +162 -0
- package/dist/catalog-enrichment-mappers.d.ts.map +1 -0
- package/dist/catalog-enrichment-mappers.js +190 -0
- package/dist/catalog-enrichment.d.ts +203 -0
- package/dist/catalog-enrichment.d.ts.map +1 -0
- package/dist/catalog-enrichment.js +130 -0
- package/dist/catalog-offers-client.d.ts +58 -0
- package/dist/catalog-offers-client.d.ts.map +1 -0
- package/dist/catalog-offers-client.js +61 -0
- package/dist/catalog-search-params.d.ts +45 -0
- package/dist/catalog-search-params.d.ts.map +1 -0
- package/dist/catalog-search-params.js +30 -0
- package/dist/catalog-surfaces.d.ts +17 -0
- package/dist/catalog-surfaces.d.ts.map +1 -0
- package/dist/catalog-surfaces.js +26 -0
- package/dist/client.d.ts +20 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +65 -0
- package/dist/components/availability-calendar.d.ts +33 -0
- package/dist/components/availability-calendar.d.ts.map +1 -0
- package/dist/components/availability-calendar.js +65 -0
- package/dist/components/catalog-browse-page.d.ts +41 -0
- package/dist/components/catalog-browse-page.d.ts.map +1 -0
- package/dist/components/catalog-browse-page.js +47 -0
- package/dist/components/catalog-card.d.ts +68 -0
- package/dist/components/catalog-card.d.ts.map +1 -0
- package/dist/components/catalog-card.js +52 -0
- package/dist/components/catalog-detail-cruise-cards.d.ts +16 -0
- package/dist/components/catalog-detail-cruise-cards.d.ts.map +1 -0
- package/dist/components/catalog-detail-cruise-cards.js +54 -0
- package/dist/components/catalog-detail-departures.d.ts +25 -0
- package/dist/components/catalog-detail-departures.d.ts.map +1 -0
- package/dist/components/catalog-detail-departures.js +240 -0
- package/dist/components/catalog-detail-parts.d.ts +70 -0
- package/dist/components/catalog-detail-parts.d.ts.map +1 -0
- package/dist/components/catalog-detail-parts.js +282 -0
- package/dist/components/catalog-detail-sheet.d.ts +93 -0
- package/dist/components/catalog-detail-sheet.d.ts.map +1 -0
- package/dist/components/catalog-detail-sheet.js +68 -0
- package/dist/components/catalog-detail-view.d.ts +39 -0
- package/dist/components/catalog-detail-view.d.ts.map +1 -0
- package/dist/components/catalog-detail-view.js +157 -0
- package/dist/components/catalog-enrichment-fetchers.d.ts +8 -0
- package/dist/components/catalog-enrichment-fetchers.d.ts.map +1 -0
- package/dist/components/catalog-enrichment-fetchers.js +7 -0
- package/dist/components/catalog-faceted-filter.d.ts +30 -0
- package/dist/components/catalog-faceted-filter.d.ts.map +1 -0
- package/dist/components/catalog-faceted-filter.js +24 -0
- package/dist/components/catalog-filter-rail.d.ts +25 -0
- package/dist/components/catalog-filter-rail.d.ts.map +1 -0
- package/dist/components/catalog-filter-rail.js +88 -0
- package/dist/components/catalog-gallery.d.ts +27 -0
- package/dist/components/catalog-gallery.d.ts.map +1 -0
- package/dist/components/catalog-gallery.js +66 -0
- package/dist/components/catalog-hit.d.ts +27 -0
- package/dist/components/catalog-hit.d.ts.map +1 -0
- package/dist/components/catalog-hit.js +57 -0
- package/dist/components/catalog-page-cards.d.ts +21 -0
- package/dist/components/catalog-page-cards.d.ts.map +1 -0
- package/dist/components/catalog-page-cards.js +174 -0
- package/dist/components/catalog-page-config.d.ts +17 -0
- package/dist/components/catalog-page-config.d.ts.map +1 -0
- package/dist/components/catalog-page-config.js +369 -0
- package/dist/components/catalog-page.d.ts +88 -0
- package/dist/components/catalog-page.d.ts.map +1 -0
- package/dist/components/catalog-page.js +148 -0
- package/dist/components/catalog-range-filter.d.ts +34 -0
- package/dist/components/catalog-range-filter.d.ts.map +1 -0
- package/dist/components/catalog-range-filter.js +72 -0
- package/dist/components/catalog-search-page.d.ts +239 -0
- package/dist/components/catalog-search-page.d.ts.map +1 -0
- package/dist/components/catalog-search-page.js +63 -0
- package/dist/components/catalog-search-tab-panel.d.ts +42 -0
- package/dist/components/catalog-search-tab-panel.d.ts.map +1 -0
- package/dist/components/catalog-search-tab-panel.js +199 -0
- package/dist/components/catalog-vertical-detail-page.d.ts +33 -0
- package/dist/components/catalog-vertical-detail-page.d.ts.map +1 -0
- package/dist/components/catalog-vertical-detail-page.js +100 -0
- package/dist/components/cruise-detail-page-parts.d.ts +72 -0
- package/dist/components/cruise-detail-page-parts.d.ts.map +1 -0
- package/dist/components/cruise-detail-page-parts.js +146 -0
- package/dist/components/cruise-detail-page.d.ts +21 -0
- package/dist/components/cruise-detail-page.d.ts.map +1 -0
- package/dist/components/cruise-detail-page.js +201 -0
- package/dist/components/dynamic-catalog-page-parts.d.ts +40 -0
- package/dist/components/dynamic-catalog-page-parts.d.ts.map +1 -0
- package/dist/components/dynamic-catalog-page-parts.js +43 -0
- package/dist/components/dynamic-catalog-page.d.ts +23 -0
- package/dist/components/dynamic-catalog-page.d.ts.map +1 -0
- package/dist/components/dynamic-catalog-page.js +270 -0
- package/dist/components/media-gallery.d.ts +13 -0
- package/dist/components/media-gallery.d.ts.map +1 -0
- package/dist/components/media-gallery.js +42 -0
- package/dist/components/product-detail-page-parts.d.ts +106 -0
- package/dist/components/product-detail-page-parts.d.ts.map +1 -0
- package/dist/components/product-detail-page-parts.js +130 -0
- package/dist/components/product-detail-page.d.ts +57 -0
- package/dist/components/product-detail-page.d.ts.map +1 -0
- package/dist/components/product-detail-page.js +175 -0
- package/dist/components/scheduled-catalog-page.d.ts +34 -0
- package/dist/components/scheduled-catalog-page.d.ts.map +1 -0
- package/dist/components/scheduled-catalog-page.js +6 -0
- package/dist/hooks/index.d.ts +3 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +2 -0
- package/dist/hooks/use-catalog-offers.d.ts +186 -0
- package/dist/hooks/use-catalog-offers.d.ts.map +1 -0
- package/dist/hooks/use-catalog-offers.js +105 -0
- package/dist/hooks/use-catalog-search.d.ts +109 -0
- package/dist/hooks/use-catalog-search.d.ts.map +1 -0
- package/dist/hooks/use-catalog-search.js +52 -0
- package/dist/i18n/en.d.ts +397 -0
- package/dist/i18n/en.d.ts.map +1 -0
- package/dist/i18n/en.js +396 -0
- package/dist/i18n/index.d.ts +5 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +3 -0
- package/dist/i18n/messages.d.ts +335 -0
- package/dist/i18n/messages.d.ts.map +1 -0
- package/dist/i18n/messages.js +1 -0
- package/dist/i18n/provider.d.ts +816 -0
- package/dist/i18n/provider.d.ts.map +1 -0
- package/dist/i18n/provider.js +44 -0
- package/dist/i18n/ro.d.ts +397 -0
- package/dist/i18n/ro.d.ts.map +1 -0
- package/dist/i18n/ro.js +396 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/provider.d.ts +2 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +1 -0
- package/dist/schemas-catalog-offers.d.ts +290 -0
- package/dist/schemas-catalog-offers.d.ts.map +1 -0
- package/dist/schemas-catalog-offers.js +155 -0
- package/dist/schemas.d.ts +143 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/schemas.js +76 -0
- package/dist/ui.d.ts +19 -0
- package/dist/ui.d.ts.map +1 -0
- package/dist/ui.js +18 -0
- package/package.json +150 -0
- package/src/styles.css +11 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Catalog detail enrichment — the normalized detail view-model
|
|
3
|
+
* (`CatalogDetailEnrichment`) + the client that fetches a single entity's rich
|
|
4
|
+
* content from the catalog content service and maps it into that shape.
|
|
5
|
+
*
|
|
6
|
+
* Provides a first-class factory for the
|
|
7
|
+
* `/v1/admin/{products,cruises,accommodations}/:id/content` URLs the detail
|
|
8
|
+
* sheet expects, so hosts no longer hand-roll `onLoadProductDetail` for the
|
|
9
|
+
* common case and can't accidentally point at a route that isn't mounted.
|
|
10
|
+
*
|
|
11
|
+
* The server side of this contract is `createProductContentRoutes` from
|
|
12
|
+
* `@voyant-travel/inventory`. If a host mounts `CatalogPage` with
|
|
13
|
+
* `enrichmentFetchers` but forgets the server mount, the first 404 triggers a
|
|
14
|
+
* one-time `console.warn` pointing at the docs — fast feedback on the foot-gun
|
|
15
|
+
* called out in issue #1023.
|
|
16
|
+
*/
|
|
17
|
+
import type { CatalogSearchHit } from "./schemas.js";
|
|
18
|
+
/**
|
|
19
|
+
* Rich detail enrichment loaded on-demand when the sheet opens.
|
|
20
|
+
* Fetched separately from the search index — the index keeps a lean
|
|
21
|
+
* facetable projection; the enrichment carries everything else via the
|
|
22
|
+
* catalog content service (description, itinerary, media, options,
|
|
23
|
+
* policies, supplier).
|
|
24
|
+
*/
|
|
25
|
+
export interface CatalogDetailEnrichment {
|
|
26
|
+
/** Entity display name from the content source (header on the full-page view). */
|
|
27
|
+
name?: string | null;
|
|
28
|
+
description?: string | null;
|
|
29
|
+
shortDescription?: string | null;
|
|
30
|
+
highlights?: ReadonlyArray<string>;
|
|
31
|
+
heroImageUrl?: string | null;
|
|
32
|
+
supplier?: string | null;
|
|
33
|
+
/** The vessel a cruise sails on (Ship tab). */
|
|
34
|
+
ship?: {
|
|
35
|
+
id?: string | null;
|
|
36
|
+
name: string;
|
|
37
|
+
shipType?: string | null;
|
|
38
|
+
description?: string | null;
|
|
39
|
+
deckPlanUrl?: string | null;
|
|
40
|
+
deckPlans?: Array<{
|
|
41
|
+
name: string;
|
|
42
|
+
level?: number | null;
|
|
43
|
+
imageUrl?: string | null;
|
|
44
|
+
}>;
|
|
45
|
+
capacity?: number | null;
|
|
46
|
+
decks?: number | null;
|
|
47
|
+
yearBuilt?: number | null;
|
|
48
|
+
images?: string[];
|
|
49
|
+
} | null;
|
|
50
|
+
itinerary?: ReadonlyArray<{
|
|
51
|
+
dayNumber: number;
|
|
52
|
+
title?: string | null;
|
|
53
|
+
description?: string | null;
|
|
54
|
+
location?: string | null;
|
|
55
|
+
date?: string | null;
|
|
56
|
+
arrivalTime?: string | null;
|
|
57
|
+
departureTime?: string | null;
|
|
58
|
+
isAtSea?: boolean | null;
|
|
59
|
+
/** Optional hero image rendered alongside the day card. */
|
|
60
|
+
heroImageUrl?: string | null;
|
|
61
|
+
}>;
|
|
62
|
+
media?: ReadonlyArray<{
|
|
63
|
+
url: string;
|
|
64
|
+
type?: string;
|
|
65
|
+
caption?: string | null;
|
|
66
|
+
}>;
|
|
67
|
+
options?: ReadonlyArray<{
|
|
68
|
+
id: string;
|
|
69
|
+
name: string;
|
|
70
|
+
description?: string | null;
|
|
71
|
+
code?: string | null;
|
|
72
|
+
type?: string | null;
|
|
73
|
+
images?: string[];
|
|
74
|
+
floorplanImages?: string[];
|
|
75
|
+
squareFeet?: string | null;
|
|
76
|
+
gradeCodes?: string[];
|
|
77
|
+
wheelchairAccessible?: boolean;
|
|
78
|
+
capacityMax?: number | null;
|
|
79
|
+
amenities?: string[];
|
|
80
|
+
}>;
|
|
81
|
+
policies?: ReadonlyArray<{
|
|
82
|
+
kind: string;
|
|
83
|
+
body: string;
|
|
84
|
+
}>;
|
|
85
|
+
departures?: ReadonlyArray<{
|
|
86
|
+
id: string;
|
|
87
|
+
sourceRef?: string | null;
|
|
88
|
+
startsAt: string;
|
|
89
|
+
endsAt?: string | null;
|
|
90
|
+
durationNights?: number | null;
|
|
91
|
+
status?: string | null;
|
|
92
|
+
embarkationPort?: string | null;
|
|
93
|
+
disembarkationPort?: string | null;
|
|
94
|
+
unlimited?: boolean | null;
|
|
95
|
+
capacity?: number | null;
|
|
96
|
+
remaining?: number | null;
|
|
97
|
+
lowestPriceCents?: number | null;
|
|
98
|
+
currency?: string | null;
|
|
99
|
+
note?: string | null;
|
|
100
|
+
itinerary?: ReadonlyArray<{
|
|
101
|
+
dayNumber: number;
|
|
102
|
+
title?: string | null;
|
|
103
|
+
description?: string | null;
|
|
104
|
+
location?: string | null;
|
|
105
|
+
date?: string | null;
|
|
106
|
+
arrivalTime?: string | null;
|
|
107
|
+
departureTime?: string | null;
|
|
108
|
+
isAtSea?: boolean | null;
|
|
109
|
+
}>;
|
|
110
|
+
}>;
|
|
111
|
+
/** Resolution metadata — drives the chips at the top. */
|
|
112
|
+
servedLocale?: string;
|
|
113
|
+
matchKind?: string;
|
|
114
|
+
source?: string;
|
|
115
|
+
servedStale?: boolean;
|
|
116
|
+
synthesized?: boolean;
|
|
117
|
+
machineTranslated?: boolean;
|
|
118
|
+
}
|
|
119
|
+
export interface CatalogEnrichmentFetchers {
|
|
120
|
+
/**
|
|
121
|
+
* Fetch the rich detail enrichment for a single hit. Returns `null`
|
|
122
|
+
* when the server has no content row (404 / 503) — the sheet falls
|
|
123
|
+
* back to the bare indexed projection.
|
|
124
|
+
*
|
|
125
|
+
* `vertical` selects the content route when `contentBasePathByVertical`
|
|
126
|
+
* is configured (e.g. cruises read `/v1/admin/cruises/:id/content`,
|
|
127
|
+
* not the products route). Returns `null` for verticals with no
|
|
128
|
+
* configured content route so the sheet renders the projection only.
|
|
129
|
+
*/
|
|
130
|
+
loadProductDetail: (hit: CatalogSearchHit, vertical?: string) => Promise<CatalogDetailEnrichment | null>;
|
|
131
|
+
/**
|
|
132
|
+
* Fetch live per-cabin pricing for one sailing (cruises). Called lazily when
|
|
133
|
+
* a departure row expands — pricing is volatile-live, so it's never cached in
|
|
134
|
+
* the detail enrichment. Returns `null` when unavailable (non-cruise vertical,
|
|
135
|
+
* no sailing ref, or the adapter can't price).
|
|
136
|
+
*/
|
|
137
|
+
loadDeparturePricing: (hit: CatalogSearchHit, sailingRef: string, vertical?: string) => Promise<CatalogDeparturePricingRow[] | null>;
|
|
138
|
+
}
|
|
139
|
+
/** One live per-cabin price row for a cruise sailing. */
|
|
140
|
+
export interface CatalogDeparturePricingRow {
|
|
141
|
+
cabinExternalId: string;
|
|
142
|
+
occupancy: number;
|
|
143
|
+
fareCode: string | null;
|
|
144
|
+
fareName: string | null;
|
|
145
|
+
currency: string;
|
|
146
|
+
/** Major-unit price string as the adapter returns it (e.g. "12959.00"). */
|
|
147
|
+
pricePerPerson: string;
|
|
148
|
+
availability: string;
|
|
149
|
+
}
|
|
150
|
+
export interface CatalogEnrichmentFetchersOptions {
|
|
151
|
+
/** Base URL the content route lives under, e.g. `/api` or `https://operator.example/api`. */
|
|
152
|
+
baseUrl: string;
|
|
153
|
+
/**
|
|
154
|
+
* Hono-mount path for `createProductContentRoutes`. Defaults to
|
|
155
|
+
* `/v1/admin/products` — the operator mount. Public surfaces typically
|
|
156
|
+
* pass `/v1/public/products`.
|
|
157
|
+
*/
|
|
158
|
+
contentBasePath?: string;
|
|
159
|
+
/**
|
|
160
|
+
* Per-vertical content mount paths, e.g.
|
|
161
|
+
* `{ products: "/v1/admin/products", cruises: "/v1/admin/cruises" }`.
|
|
162
|
+
* When set, the detail loader routes by the hit's vertical: a vertical
|
|
163
|
+
* present in the map fetches from its path; a vertical absent from the
|
|
164
|
+
* map skips the fetch (returns `null`) so the sheet shows the projection
|
|
165
|
+
* only — avoiding a spurious request to the products route. When unset,
|
|
166
|
+
* every fetch uses `contentBasePath` (legacy single-vertical behavior).
|
|
167
|
+
*/
|
|
168
|
+
contentBasePathByVertical?: Record<string, string>;
|
|
169
|
+
fetch?: typeof globalThis.fetch;
|
|
170
|
+
credentials?: RequestCredentials;
|
|
171
|
+
/**
|
|
172
|
+
* Resolves supplier ids to display names. The server returns the
|
|
173
|
+
* supplier as an id string; the sheet shows the resolved name. When
|
|
174
|
+
* omitted, the id is passed through unchanged.
|
|
175
|
+
*/
|
|
176
|
+
formatSupplier?: (id: string) => string;
|
|
177
|
+
/** Optional locale forwarded as `?locale=...` (BCP 47). */
|
|
178
|
+
locale?: string;
|
|
179
|
+
/** Optional market id forwarded as `?market=...`. */
|
|
180
|
+
market?: string;
|
|
181
|
+
/**
|
|
182
|
+
* Optional per-departure availability loader. When provided, the
|
|
183
|
+
* fetcher merges runtime availability (status / remaining / capacity)
|
|
184
|
+
* onto each departure in the result. Mirrors the operator's existing
|
|
185
|
+
* call to `/v1/admin/catalog/slots`.
|
|
186
|
+
*/
|
|
187
|
+
loadSlotAvailability?: (productId: string) => Promise<Map<string, CatalogSlotAvailability>>;
|
|
188
|
+
}
|
|
189
|
+
export interface CatalogSlotAvailability {
|
|
190
|
+
id: string;
|
|
191
|
+
startsAt?: string;
|
|
192
|
+
status?: string | null;
|
|
193
|
+
unlimited?: boolean | null;
|
|
194
|
+
remainingPax?: number | null;
|
|
195
|
+
initialPax?: number | null;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Build a `CatalogEnrichmentFetchers` for the configured `baseUrl`.
|
|
199
|
+
*/
|
|
200
|
+
export declare function createCatalogEnrichmentFetchers(options: CatalogEnrichmentFetchersOptions): CatalogEnrichmentFetchers;
|
|
201
|
+
/** @internal — exported for testing. Resets the one-time warn flag. */
|
|
202
|
+
export declare function __resetEnrichmentFetcherWarnings(): void;
|
|
203
|
+
//# sourceMappingURL=catalog-enrichment.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog-enrichment.d.ts","sourceRoot":"","sources":["../src/catalog-enrichment.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Catalog detail enrichment — the normalized detail view-model
|
|
3
|
+
* (`CatalogDetailEnrichment`) + the client that fetches a single entity's rich
|
|
4
|
+
* content from the catalog content service and maps it into that shape.
|
|
5
|
+
*
|
|
6
|
+
* Provides a first-class factory for the
|
|
7
|
+
* `/v1/admin/{products,cruises,accommodations}/:id/content` URLs the detail
|
|
8
|
+
* sheet expects, so hosts no longer hand-roll `onLoadProductDetail` for the
|
|
9
|
+
* common case and can't accidentally point at a route that isn't mounted.
|
|
10
|
+
*
|
|
11
|
+
* The server side of this contract is `createProductContentRoutes` from
|
|
12
|
+
* `@voyant-travel/inventory`. If a host mounts `CatalogPage` with
|
|
13
|
+
* `enrichmentFetchers` but forgets the server mount, the first 404 triggers a
|
|
14
|
+
* one-time `console.warn` pointing at the docs — fast feedback on the foot-gun
|
|
15
|
+
* called out in issue #1023.
|
|
16
|
+
*/
|
|
17
|
+
import { mapContentToEnrichment, } from "./catalog-enrichment-mappers.js";
|
|
18
|
+
let warnedAboutMissingMount = false;
|
|
19
|
+
/**
|
|
20
|
+
* Build a `CatalogEnrichmentFetchers` for the configured `baseUrl`.
|
|
21
|
+
*/
|
|
22
|
+
export function createCatalogEnrichmentFetchers(options) {
|
|
23
|
+
const { baseUrl, contentBasePath = "/v1/admin/products", contentBasePathByVertical, fetch: fetchImpl = globalThis.fetch, credentials = "include", formatSupplier, locale, market, loadSlotAvailability, } = options;
|
|
24
|
+
const root = trimTrailingSlash(baseUrl);
|
|
25
|
+
const defaultBasePath = ensureLeadingSlash(contentBasePath);
|
|
26
|
+
// Resolve the content mount for a vertical. Returns null when a per-vertical
|
|
27
|
+
// map is configured but this vertical has no entry (so callers skip the fetch
|
|
28
|
+
// rather than hitting the products route with a foreign id, which 404s).
|
|
29
|
+
const resolveBasePath = (vertical) => {
|
|
30
|
+
if (!contentBasePathByVertical)
|
|
31
|
+
return defaultBasePath;
|
|
32
|
+
if (!vertical)
|
|
33
|
+
return null;
|
|
34
|
+
const mapped = contentBasePathByVertical[vertical];
|
|
35
|
+
return mapped ? ensureLeadingSlash(mapped) : null;
|
|
36
|
+
};
|
|
37
|
+
const loadProductDetail = async (hit, vertical) => {
|
|
38
|
+
const basePath = resolveBasePath(vertical);
|
|
39
|
+
if (!basePath)
|
|
40
|
+
return null;
|
|
41
|
+
const url = buildContentUrl(root, basePath, hit.id, { locale, market });
|
|
42
|
+
let response;
|
|
43
|
+
try {
|
|
44
|
+
response = await fetchImpl(url, {
|
|
45
|
+
method: "GET",
|
|
46
|
+
headers: { Accept: "application/json" },
|
|
47
|
+
credentials,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
throw new Error(`catalog enrichment fetch failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
52
|
+
}
|
|
53
|
+
if (response.status === 404 || response.status === 503) {
|
|
54
|
+
// 404 ALSO means the server didn't mount `createProductContentRoutes`.
|
|
55
|
+
// Warn once so the foot-gun called out in #1023 surfaces in dev
|
|
56
|
+
// instead of silently rendering the bare projection.
|
|
57
|
+
maybeWarnMissingMount(response, url);
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
if (!response.ok) {
|
|
61
|
+
throw new Error(`catalog enrichment fetch failed: ${response.status} ${response.statusText}`);
|
|
62
|
+
}
|
|
63
|
+
const payload = (await response.json());
|
|
64
|
+
const availability = loadSlotAvailability
|
|
65
|
+
? // i18n-literal-ok: `=> new Map<…>` trips the JSX-text heuristic — no user-facing copy here.
|
|
66
|
+
await loadSlotAvailability(hit.id).catch(() => new Map())
|
|
67
|
+
: new Map();
|
|
68
|
+
return mapContentToEnrichment(payload, availability, formatSupplier);
|
|
69
|
+
};
|
|
70
|
+
const loadDeparturePricing = async (hit, sailingRef, vertical) => {
|
|
71
|
+
const basePath = resolveBasePath(vertical);
|
|
72
|
+
if (!basePath)
|
|
73
|
+
return null;
|
|
74
|
+
const url = `${root}${basePath}/${encodeURIComponent(hit.id)}/sailings/${encodeURIComponent(sailingRef)}/pricing`;
|
|
75
|
+
let response;
|
|
76
|
+
try {
|
|
77
|
+
response = await fetchImpl(url, {
|
|
78
|
+
method: "GET",
|
|
79
|
+
headers: { Accept: "application/json" },
|
|
80
|
+
credentials,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
if (!response.ok)
|
|
87
|
+
return null;
|
|
88
|
+
const payload = (await response.json());
|
|
89
|
+
return payload.data?.pricing ?? null;
|
|
90
|
+
};
|
|
91
|
+
return { loadProductDetail, loadDeparturePricing };
|
|
92
|
+
}
|
|
93
|
+
function maybeWarnMissingMount(response, url) {
|
|
94
|
+
if (warnedAboutMissingMount)
|
|
95
|
+
return;
|
|
96
|
+
if (response.status !== 404)
|
|
97
|
+
return;
|
|
98
|
+
// Heuristic: a 404 returned with no JSON body almost always means the
|
|
99
|
+
// route is unmounted on the server side. A real "product not found"
|
|
100
|
+
// response carries a JSON `{ error: "not_found", detail }`. We don't
|
|
101
|
+
// re-read the body here (the caller will not parse it for 404), so we
|
|
102
|
+
// log a generic hint — false positives are tolerable in dev.
|
|
103
|
+
warnedAboutMissingMount = true;
|
|
104
|
+
if (typeof console !== "undefined" && typeof console.warn === "function") {
|
|
105
|
+
console.warn(`[catalog-ui] enrichment fetch returned 404 for ${url}. ` +
|
|
106
|
+
`If the catalog detail sheet renders empty, make sure the host mounts ` +
|
|
107
|
+
`createProductContentRoutes from @voyant-travel/inventory ` +
|
|
108
|
+
`under the same prefix used here.`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function buildContentUrl(root, basePath, id, query) {
|
|
112
|
+
const params = new URLSearchParams();
|
|
113
|
+
if (query.locale)
|
|
114
|
+
params.set("locale", query.locale);
|
|
115
|
+
if (query.market)
|
|
116
|
+
params.set("market", query.market);
|
|
117
|
+
const qs = params.toString();
|
|
118
|
+
const suffix = qs ? `?${qs}` : "";
|
|
119
|
+
return `${root}${basePath}/${encodeURIComponent(id)}/content${suffix}`;
|
|
120
|
+
}
|
|
121
|
+
function trimTrailingSlash(s) {
|
|
122
|
+
return s.endsWith("/") ? s.slice(0, -1) : s;
|
|
123
|
+
}
|
|
124
|
+
function ensureLeadingSlash(s) {
|
|
125
|
+
return s.startsWith("/") ? s : `/${s}`;
|
|
126
|
+
}
|
|
127
|
+
/** @internal — exported for testing. Resets the one-time warn flag. */
|
|
128
|
+
export function __resetEnrichmentFetcherWarnings() {
|
|
129
|
+
warnedAboutMissingMount = false;
|
|
130
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client functions for the Booking.com-style catalog offer endpoints.
|
|
3
|
+
*
|
|
4
|
+
* These are the non-hook layer (mirrors `fetchWithValidation`'s "bypass the
|
|
5
|
+
* hook layer" intent) — imperative consumers (search-on-submit, lazy
|
|
6
|
+
* per-row pricing) call these directly with `{ baseUrl, fetcher }` from
|
|
7
|
+
* `useVoyantCatalogContext()`; the `hooks/` wrap them in react-query.
|
|
8
|
+
*
|
|
9
|
+
* `surface` switches `/v1/admin/...` (operator dashboards, default) vs
|
|
10
|
+
* `/v1/public/...` (storefront / partner / embedded surfaces).
|
|
11
|
+
*/
|
|
12
|
+
import { type VoyantFetcher } from "./client.js";
|
|
13
|
+
import { type CatalogSlotsResponse, type CruiseContentResponse, type CruisePriceResponse, type CruiseSailingPricingResponse, type DepartureAirportsResponse, type PackageDetailResponse, type PackageSearchResponse } from "./schemas-catalog-offers.js";
|
|
14
|
+
export type CatalogSurface = "admin" | "public";
|
|
15
|
+
export interface CatalogOffersClientContext {
|
|
16
|
+
baseUrl: string;
|
|
17
|
+
fetcher: VoyantFetcher;
|
|
18
|
+
/** Defaults to `"admin"`. */
|
|
19
|
+
surface?: CatalogSurface;
|
|
20
|
+
}
|
|
21
|
+
export interface NightsRange {
|
|
22
|
+
min: number;
|
|
23
|
+
max: number;
|
|
24
|
+
}
|
|
25
|
+
export declare function fetchDepartureAirports(ctx: CatalogOffersClientContext, args: {
|
|
26
|
+
countryCode: string;
|
|
27
|
+
}, signal?: AbortSignal): Promise<DepartureAirportsResponse>;
|
|
28
|
+
export declare function fetchPackageSearch(ctx: CatalogOffersClientContext, args: {
|
|
29
|
+
countryCode: string;
|
|
30
|
+
departureDateFrom: string;
|
|
31
|
+
departureDateTo: string;
|
|
32
|
+
adults: number;
|
|
33
|
+
nights: NightsRange;
|
|
34
|
+
}, signal?: AbortSignal): Promise<PackageSearchResponse>;
|
|
35
|
+
export declare function fetchPackageDetail(ctx: CatalogOffersClientContext, args: {
|
|
36
|
+
productId: string;
|
|
37
|
+
departureDateFrom: string;
|
|
38
|
+
departureDateTo: string;
|
|
39
|
+
adults: number;
|
|
40
|
+
nights: NightsRange;
|
|
41
|
+
locale?: string;
|
|
42
|
+
}, signal?: AbortSignal): Promise<PackageDetailResponse>;
|
|
43
|
+
export declare function fetchCruisePrice(ctx: CatalogOffersClientContext, args: {
|
|
44
|
+
cruiseId: string;
|
|
45
|
+
}, signal?: AbortSignal): Promise<CruisePriceResponse>;
|
|
46
|
+
export declare function fetchCruiseSailingPricing(ctx: CatalogOffersClientContext, args: {
|
|
47
|
+
cruiseId: string;
|
|
48
|
+
sailingRef: string;
|
|
49
|
+
}, signal?: AbortSignal): Promise<CruiseSailingPricingResponse>;
|
|
50
|
+
export declare function fetchCruiseContent(ctx: CatalogOffersClientContext, args: {
|
|
51
|
+
cruiseId: string;
|
|
52
|
+
locale?: string;
|
|
53
|
+
}, signal?: AbortSignal): Promise<CruiseContentResponse>;
|
|
54
|
+
export declare function fetchCatalogSlots(ctx: CatalogOffersClientContext, args: {
|
|
55
|
+
entityModule: string;
|
|
56
|
+
entityId: string;
|
|
57
|
+
}, signal?: AbortSignal): Promise<CatalogSlotsResponse>;
|
|
58
|
+
//# sourceMappingURL=catalog-offers-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog-offers-client.d.ts","sourceRoot":"","sources":["../src/catalog-offers-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAA;AACrE,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,4BAA4B,EAKjC,KAAK,yBAAyB,EAE9B,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAG3B,MAAM,6BAA6B,CAAA;AAEpC,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,QAAQ,CAAA;AAE/C,MAAM,WAAW,0BAA0B;IACzC,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,aAAa,CAAA;IACtB,6BAA6B;IAC7B,OAAO,CAAC,EAAE,cAAc,CAAA;CACzB;AAED,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;CACZ;AAUD,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,0BAA0B,EAC/B,IAAI,EAAE;IAAE,WAAW,EAAE,MAAM,CAAA;CAAE,EAC7B,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,yBAAyB,CAAC,CAOpC;AAED,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,0BAA0B,EAC/B,IAAI,EAAE;IACJ,WAAW,EAAE,MAAM,CAAA;IACnB,iBAAiB,EAAE,MAAM,CAAA;IACzB,eAAe,EAAE,MAAM,CAAA;IACvB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,WAAW,CAAA;CACpB,EACD,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,qBAAqB,CAAC,CAgBhC;AAED,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,0BAA0B,EAC/B,IAAI,EAAE;IACJ,SAAS,EAAE,MAAM,CAAA;IACjB,iBAAiB,EAAE,MAAM,CAAA;IACzB,eAAe,EAAE,MAAM,CAAA;IACvB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,WAAW,CAAA;IACnB,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB,EACD,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,qBAAqB,CAAC,CAiBhC;AAED,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,0BAA0B,EAC/B,IAAI,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,EAC1B,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,mBAAmB,CAAC,CAO9B;AAED,wBAAgB,yBAAyB,CACvC,GAAG,EAAE,0BAA0B,EAC/B,IAAI,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,EAC9C,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,4BAA4B,CAAC,CAOvC;AAED,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,0BAA0B,EAC/B,IAAI,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,EAC3C,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,qBAAqB,CAAC,CAYhC;AAED,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,0BAA0B,EAC/B,IAAI,EAAE;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,EAChD,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,oBAAoB,CAAC,CAW/B"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client functions for the Booking.com-style catalog offer endpoints.
|
|
3
|
+
*
|
|
4
|
+
* These are the non-hook layer (mirrors `fetchWithValidation`'s "bypass the
|
|
5
|
+
* hook layer" intent) — imperative consumers (search-on-submit, lazy
|
|
6
|
+
* per-row pricing) call these directly with `{ baseUrl, fetcher }` from
|
|
7
|
+
* `useVoyantCatalogContext()`; the `hooks/` wrap them in react-query.
|
|
8
|
+
*
|
|
9
|
+
* `surface` switches `/v1/admin/...` (operator dashboards, default) vs
|
|
10
|
+
* `/v1/public/...` (storefront / partner / embedded surfaces).
|
|
11
|
+
*/
|
|
12
|
+
import { fetchWithValidation } from "./client.js";
|
|
13
|
+
import { catalogSlotsResponseSchema, cruiseContentResponseSchema, cruisePriceResponseSchema, cruiseSailingPricingResponseSchema, departureAirportsResponseSchema, packageDetailResponseSchema, packageSearchResponseSchema, } from "./schemas-catalog-offers.js";
|
|
14
|
+
function catalogPath(surface, path) {
|
|
15
|
+
return `/v1/${surface ?? "admin"}/catalog/${path}`;
|
|
16
|
+
}
|
|
17
|
+
function post(body, signal) {
|
|
18
|
+
return { method: "POST", body: JSON.stringify(body), signal };
|
|
19
|
+
}
|
|
20
|
+
export function fetchDepartureAirports(ctx, args, signal) {
|
|
21
|
+
return fetchWithValidation(catalogPath(ctx.surface, "departure-airports"), departureAirportsResponseSchema, { baseUrl: ctx.baseUrl, fetcher: ctx.fetcher }, post({ destination: { countryCode: args.countryCode } }, signal));
|
|
22
|
+
}
|
|
23
|
+
export function fetchPackageSearch(ctx, args, signal) {
|
|
24
|
+
return fetchWithValidation(catalogPath(ctx.surface, "package-search"), packageSearchResponseSchema, { baseUrl: ctx.baseUrl, fetcher: ctx.fetcher }, post({
|
|
25
|
+
destination: { countryCode: args.countryCode },
|
|
26
|
+
departureDateFrom: args.departureDateFrom,
|
|
27
|
+
departureDateTo: args.departureDateTo,
|
|
28
|
+
adults: args.adults,
|
|
29
|
+
nights: args.nights,
|
|
30
|
+
}, signal));
|
|
31
|
+
}
|
|
32
|
+
export function fetchPackageDetail(ctx, args, signal) {
|
|
33
|
+
return fetchWithValidation(catalogPath(ctx.surface, "package-detail"), packageDetailResponseSchema, { baseUrl: ctx.baseUrl, fetcher: ctx.fetcher }, post({
|
|
34
|
+
productId: args.productId,
|
|
35
|
+
departureDateFrom: args.departureDateFrom,
|
|
36
|
+
departureDateTo: args.departureDateTo,
|
|
37
|
+
adults: args.adults,
|
|
38
|
+
nights: args.nights,
|
|
39
|
+
locale: args.locale,
|
|
40
|
+
}, signal));
|
|
41
|
+
}
|
|
42
|
+
export function fetchCruisePrice(ctx, args, signal) {
|
|
43
|
+
return fetchWithValidation(catalogPath(ctx.surface, "cruise-price"), cruisePriceResponseSchema, { baseUrl: ctx.baseUrl, fetcher: ctx.fetcher }, post({ cruiseId: args.cruiseId }, signal));
|
|
44
|
+
}
|
|
45
|
+
export function fetchCruiseSailingPricing(ctx, args, signal) {
|
|
46
|
+
return fetchWithValidation(catalogPath(ctx.surface, "cruise-sailing-pricing"), cruiseSailingPricingResponseSchema, { baseUrl: ctx.baseUrl, fetcher: ctx.fetcher }, post({ cruiseId: args.cruiseId, sailingRef: args.sailingRef }, signal));
|
|
47
|
+
}
|
|
48
|
+
export function fetchCruiseContent(ctx, args, signal) {
|
|
49
|
+
const params = new URLSearchParams();
|
|
50
|
+
if (args.locale)
|
|
51
|
+
params.set("locale", args.locale);
|
|
52
|
+
const qs = params.toString();
|
|
53
|
+
return fetchWithValidation(`/v1/${ctx.surface ?? "admin"}/cruises/${encodeURIComponent(args.cruiseId)}/content${qs ? `?${qs}` : ""}`, cruiseContentResponseSchema, { baseUrl: ctx.baseUrl, fetcher: ctx.fetcher }, { method: "GET", signal });
|
|
54
|
+
}
|
|
55
|
+
export function fetchCatalogSlots(ctx, args, signal) {
|
|
56
|
+
const params = new URLSearchParams({
|
|
57
|
+
entityModule: args.entityModule,
|
|
58
|
+
entityId: args.entityId,
|
|
59
|
+
});
|
|
60
|
+
return fetchWithValidation(catalogPath(ctx.surface, `slots?${params.toString()}`), catalogSlotsResponseSchema, { baseUrl: ctx.baseUrl, fetcher: ctx.fetcher }, { method: "GET", signal });
|
|
61
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Catalog browse search state — the URL/query shape shared by every catalog
|
|
4
|
+
* surface (grid/list + sort + facet/range filters). Lives in the data layer
|
|
5
|
+
* (no router imports) so route files and templates parse/validate the same
|
|
6
|
+
* contract; the operator binds it to TanStack Router's `validateSearch`.
|
|
7
|
+
*/
|
|
8
|
+
export declare const catalogViewModes: readonly ["grid", "list"];
|
|
9
|
+
export type CatalogViewMode = (typeof catalogViewModes)[number];
|
|
10
|
+
export declare const catalogSortOptions: readonly ["relevance", "price-asc", "price-desc", "departure-asc", "newest"];
|
|
11
|
+
export type CatalogSortOption = (typeof catalogSortOptions)[number];
|
|
12
|
+
export declare const catalogFiltersSchema: z.ZodObject<{
|
|
13
|
+
facets: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>>;
|
|
14
|
+
ranges: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
15
|
+
gte: z.ZodOptional<z.ZodNumber>;
|
|
16
|
+
lte: z.ZodOptional<z.ZodNumber>;
|
|
17
|
+
}, z.core.$strip>>>;
|
|
18
|
+
}, z.core.$strip>;
|
|
19
|
+
export type CatalogFilterSelections = z.infer<typeof catalogFiltersSchema>;
|
|
20
|
+
export declare const catalogSearchSchema: z.ZodObject<{
|
|
21
|
+
q: z.ZodOptional<z.ZodString>;
|
|
22
|
+
page: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
23
|
+
market: z.ZodOptional<z.ZodString>;
|
|
24
|
+
locale: z.ZodOptional<z.ZodString>;
|
|
25
|
+
view: z.ZodOptional<z.ZodEnum<{
|
|
26
|
+
grid: "grid";
|
|
27
|
+
list: "list";
|
|
28
|
+
}>>;
|
|
29
|
+
sort: z.ZodOptional<z.ZodEnum<{
|
|
30
|
+
relevance: "relevance";
|
|
31
|
+
"price-asc": "price-asc";
|
|
32
|
+
"price-desc": "price-desc";
|
|
33
|
+
"departure-asc": "departure-asc";
|
|
34
|
+
newest: "newest";
|
|
35
|
+
}>>;
|
|
36
|
+
filters: z.ZodOptional<z.ZodObject<{
|
|
37
|
+
facets: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>>;
|
|
38
|
+
ranges: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
39
|
+
gte: z.ZodOptional<z.ZodNumber>;
|
|
40
|
+
lte: z.ZodOptional<z.ZodNumber>;
|
|
41
|
+
}, z.core.$strip>>>;
|
|
42
|
+
}, z.core.$strip>>;
|
|
43
|
+
}, z.core.$strip>;
|
|
44
|
+
export type CatalogSearchParams = z.infer<typeof catalogSearchSchema>;
|
|
45
|
+
//# sourceMappingURL=catalog-search-params.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog-search-params.d.ts","sourceRoot":"","sources":["../src/catalog-search-params.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB;;;;;GAKG;AAEH,eAAO,MAAM,gBAAgB,2BAA4B,CAAA;AACzD,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAA;AAE/D,eAAO,MAAM,kBAAkB,8EAMrB,CAAA;AACV,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAA;AAEnE,eAAO,MAAM,oBAAoB;;;;;;iBAK/B,CAAA;AACF,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAA;AAE1E,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;iBAQ9B,CAAA;AAEF,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAA"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Catalog browse search state — the URL/query shape shared by every catalog
|
|
4
|
+
* surface (grid/list + sort + facet/range filters). Lives in the data layer
|
|
5
|
+
* (no router imports) so route files and templates parse/validate the same
|
|
6
|
+
* contract; the operator binds it to TanStack Router's `validateSearch`.
|
|
7
|
+
*/
|
|
8
|
+
export const catalogViewModes = ["grid", "list"];
|
|
9
|
+
export const catalogSortOptions = [
|
|
10
|
+
"relevance",
|
|
11
|
+
"price-asc",
|
|
12
|
+
"price-desc",
|
|
13
|
+
"departure-asc",
|
|
14
|
+
"newest",
|
|
15
|
+
];
|
|
16
|
+
export const catalogFiltersSchema = z.object({
|
|
17
|
+
facets: z.record(z.string(), z.array(z.union([z.string(), z.number()]))).optional(),
|
|
18
|
+
ranges: z
|
|
19
|
+
.record(z.string(), z.object({ gte: z.number().optional(), lte: z.number().optional() }))
|
|
20
|
+
.optional(),
|
|
21
|
+
});
|
|
22
|
+
export const catalogSearchSchema = z.object({
|
|
23
|
+
q: z.string().optional(),
|
|
24
|
+
page: z.coerce.number().int().min(1).default(1),
|
|
25
|
+
market: z.string().optional(),
|
|
26
|
+
locale: z.string().optional(),
|
|
27
|
+
view: z.enum(catalogViewModes).optional(),
|
|
28
|
+
sort: z.enum(catalogSortOptions).optional(),
|
|
29
|
+
filters: catalogFiltersSchema.optional(),
|
|
30
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Catalog "surfaces" that have a dedicated, full-page detail view. Excursions
|
|
3
|
+
* and tours are scheduled PRODUCTS (their own browse surface), but their detail
|
|
4
|
+
* content comes from the products content route — see `catalogSurfaceVertical`.
|
|
5
|
+
*/
|
|
6
|
+
export declare const catalogDetailSurfaces: readonly ["products", "cruises", "accommodations", "excursions", "tours"];
|
|
7
|
+
export type CatalogDetailSurface = (typeof catalogDetailSurfaces)[number];
|
|
8
|
+
/**
|
|
9
|
+
* Verticals the indexed browse grid (`CatalogVerticalHost`) can render — the
|
|
10
|
+
* surfaces with their own search-index collection. Excursions/tours browse the
|
|
11
|
+
* `products` vertical with locked supply-model/duration filters instead.
|
|
12
|
+
*/
|
|
13
|
+
export declare const catalogVerticalPageIds: readonly ["products", "cruises", "accommodations"];
|
|
14
|
+
export type CatalogVerticalPageId = (typeof catalogVerticalPageIds)[number];
|
|
15
|
+
/** Catalog/content vertical backing a surface (excursions/tours → products). */
|
|
16
|
+
export declare function catalogSurfaceVertical(surface: CatalogDetailSurface): "products" | "cruises" | "accommodations";
|
|
17
|
+
//# sourceMappingURL=catalog-surfaces.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog-surfaces.d.ts","sourceRoot":"","sources":["../src/catalog-surfaces.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,2EAMxB,CAAA;AACV,MAAM,MAAM,oBAAoB,GAAG,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,CAAC,CAAA;AAEzE;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,oDAAqD,CAAA;AACxF,MAAM,MAAM,qBAAqB,GAAG,CAAC,OAAO,sBAAsB,CAAC,CAAC,MAAM,CAAC,CAAA;AAE3E,gFAAgF;AAChF,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,oBAAoB,GAC5B,UAAU,GAAG,SAAS,GAAG,gBAAgB,CAI3C"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Catalog "surfaces" that have a dedicated, full-page detail view. Excursions
|
|
3
|
+
* and tours are scheduled PRODUCTS (their own browse surface), but their detail
|
|
4
|
+
* content comes from the products content route — see `catalogSurfaceVertical`.
|
|
5
|
+
*/
|
|
6
|
+
export const catalogDetailSurfaces = [
|
|
7
|
+
"products",
|
|
8
|
+
"cruises",
|
|
9
|
+
"accommodations",
|
|
10
|
+
"excursions",
|
|
11
|
+
"tours",
|
|
12
|
+
];
|
|
13
|
+
/**
|
|
14
|
+
* Verticals the indexed browse grid (`CatalogVerticalHost`) can render — the
|
|
15
|
+
* surfaces with their own search-index collection. Excursions/tours browse the
|
|
16
|
+
* `products` vertical with locked supply-model/duration filters instead.
|
|
17
|
+
*/
|
|
18
|
+
export const catalogVerticalPageIds = ["products", "cruises", "accommodations"];
|
|
19
|
+
/** Catalog/content vertical backing a surface (excursions/tours → products). */
|
|
20
|
+
export function catalogSurfaceVertical(surface) {
|
|
21
|
+
if (surface === "cruises")
|
|
22
|
+
return "cruises";
|
|
23
|
+
if (surface === "accommodations")
|
|
24
|
+
return "accommodations";
|
|
25
|
+
return "products";
|
|
26
|
+
}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Re-exports the shared HTTP plumbing from `@voyant-travel/inventory-react/client`
|
|
3
|
+
* so the catalog-react package doesn't duplicate the fetcher / error-shaping
|
|
4
|
+
* logic. If/when this drifts, extract into `@voyant-travel/react/client` and
|
|
5
|
+
* point both packages at it.
|
|
6
|
+
*/
|
|
7
|
+
import type { z } from "zod";
|
|
8
|
+
export type VoyantFetcher = (url: string, init?: RequestInit) => Promise<Response>;
|
|
9
|
+
export declare const defaultFetcher: VoyantFetcher;
|
|
10
|
+
export declare class VoyantApiError extends Error {
|
|
11
|
+
readonly status: number;
|
|
12
|
+
readonly body: unknown;
|
|
13
|
+
constructor(message: string, status: number, body: unknown);
|
|
14
|
+
}
|
|
15
|
+
export interface FetchWithValidationOptions {
|
|
16
|
+
baseUrl: string;
|
|
17
|
+
fetcher: VoyantFetcher;
|
|
18
|
+
}
|
|
19
|
+
export declare function fetchWithValidation<TOut>(path: string, schema: z.ZodType<TOut>, options: FetchWithValidationOptions, init?: RequestInit): Promise<TOut>;
|
|
20
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAE5B,MAAM,MAAM,aAAa,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;AAElF,eAAO,MAAM,cAAc,EAAE,aACoB,CAAA;AAEjD,qBAAa,cAAe,SAAQ,KAAK;IACvC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;gBAEV,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO;CAM3D;AAaD,MAAM,WAAW,0BAA0B;IACzC,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,aAAa,CAAA;CACvB;AAED,wBAAsB,mBAAmB,CAAC,IAAI,EAC5C,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,EACvB,OAAO,EAAE,0BAA0B,EACnC,IAAI,CAAC,EAAE,WAAW,GACjB,OAAO,CAAC,IAAI,CAAC,CAgCf"}
|