@travories/frontend-sdk 0.1.4 → 0.1.5

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.
Files changed (2) hide show
  1. package/README.md +334 -169
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  [![types](https://img.shields.io/npm/types/@travories/frontend-sdk?logo=typescript&logoColor=white)](https://www.npmjs.com/package/@travories/frontend-sdk)
6
6
  [![license](https://img.shields.io/npm/l/@travories/frontend-sdk?color=brightgreen)](./LICENSE)
7
7
 
8
- > Official Travories SDK. Drop the full **package detail page** and **landing carousels** into any React app, or just use the API client to fetch packages on your own terms.
8
+ > Official Travories SDK. Drop **landing carousels**, the full **package detail page**, the **agency profile page**, and a real **booking flow** into any React app or just use the API client to fetch data on your own terms.
9
9
 
10
10
  ```bash
11
11
  npm install @travories/frontend-sdk
@@ -22,38 +22,30 @@ export default function Page({ slug }: { slug: string }) {
22
22
  }
23
23
  ```
24
24
 
25
- That's a complete, production-grade package detail page: gallery, itinerary with map, day-by-day routes, booking sidebar, host card. ~95 KB gzipped.
25
+ That's a complete, production-grade package detail page: gallery, itinerary with map and per-day routes, booking sidebar, host card. ~100 KB gzipped, no Tailwind required in your app.
26
26
 
27
27
  ---
28
28
 
29
- ## Features
30
-
31
- - **Framework-agnostic API client** — fetch packages, hosts, routes, home sections. Works in Node, browser, Next.js server components, edge runtimes.
32
- - **Drop-in pages** — `<PackageDetailView />` for detail, `<HomeSections />` for landing. One prop and you're done.
33
- - **Composable layouts** — `<HomeSectionsProvider>` + `<PackagesSection>` lets you interleave SDK sections with your own content (FAQs, banners, etc.), all sharing one fetch.
34
- - **Real maps** — Leaflet-powered map with per-day GeoJSON routes flown to on day change.
35
- - **Bundled styling** — Tailwind precompiled and scoped to `.tvr-package-detail`. No Tailwind required in your app, no class conflicts with your own.
36
- - **Full TypeScript types** — every prop, every API response shape, ergonomic union types.
37
- - **ESM + CJS + sourcemaps** — works with any bundler, any Node version ≥ 18.
38
- - **SSR-first** — pass `initialBundle` / `initialSections` to skip client fetches and render on the server.
39
-
40
- ---
41
-
42
- ## Contents
29
+ ## Table of contents
43
30
 
44
31
  - [Install](#install)
45
32
  - [Quick start](#quick-start)
46
- - [Detail page](#detail-page)
47
- - [Landing carousels](#landing-carousels)
33
+ - [Package detail page](#package-detail-page)
34
+ - [Landing — all four carousels](#landing--all-four-carousels)
35
+ - [Landing — interleaved sections (FAQ between carousels)](#landing--interleaved-sections-faq-between-carousels)
36
+ - [Agency profile page](#agency-profile-page)
48
37
  - [Just the data](#just-the-data)
49
- - [Components](#components)
50
- - [Hooks](#hooks)
38
+ - [Booking flow](#booking-flow)
39
+ - [Components reference](#components-reference)
40
+ - [Hooks reference](#hooks-reference)
51
41
  - [Client configuration](#client-configuration)
52
- - [SSR with Next.js](#ssr-with-nextjs)
53
- - [Composable landing layouts](#composable-landing-layouts)
42
+ - [SSR with Next.js / Remix](#ssr-with-nextjs--remix)
54
43
  - [Styling](#styling)
55
- - [TypeScript](#typescript)
56
- - [API reference](#api-reference)
44
+ - [TypeScript types](#typescript-types)
45
+ - [API endpoints called](#api-endpoints-called)
46
+ - [Edge cases & FAQ](#edge-cases--faq)
47
+ - [Browser support](#browser-support)
48
+ - [Versioning & releases](#versioning--releases)
57
49
  - [Support](#support)
58
50
  - [License](#license)
59
51
 
@@ -65,19 +57,27 @@ That's a complete, production-grade package detail page: gallery, itinerary with
65
57
  npm install @travories/frontend-sdk
66
58
  ```
67
59
 
68
- **Peer dependencies** (skip if you only use the API client):
60
+ **Peer dependencies** (skip if you only use the API client / Node):
69
61
 
70
62
  ```bash
71
63
  npm install react react-dom
72
64
  ```
73
65
 
74
- Requires React 17+, Node 18+ (uses built-in `fetch`).
66
+ | Requirement | Version |
67
+ |---|---|
68
+ | Node | ≥ 18 (uses built-in `fetch`) |
69
+ | React | ≥ 17 |
70
+ | TypeScript | optional but recommended ≥ 5 |
71
+
72
+ `leaflet` and `react-leaflet` are runtime deps, included automatically when you install the SDK — needed for the itinerary map.
75
73
 
76
74
  ---
77
75
 
78
76
  ## Quick start
79
77
 
80
- ### Detail page
78
+ ### Package detail page
79
+
80
+ The all-in-one drop-in. Pass `client` + `slug` — the SDK fetches everything and renders.
81
81
 
82
82
  ```tsx
83
83
  import { TravoriesClient, PackageDetailView } from "@travories/frontend-sdk";
@@ -91,16 +91,23 @@ function PackagePage({ slug }: { slug: string }) {
91
91
  client={client}
92
92
  slug={slug}
93
93
  currency="USD"
94
- onReserve={(pkg) => {
95
- // hook your booking flow here
96
- window.location.href = `/checkout/${pkg.slug}`;
94
+ onReserve={async ({ pkg, arrival, travelers }) => {
95
+ // see "Booking flow" section below for the canonical pattern
96
+ const { data: bookingId } = await client.bookings.initiate({
97
+ packageId: pkg.slug,
98
+ arrivalDate: arrival,
99
+ travelers,
100
+ });
101
+ window.location.href = `/checkout/${bookingId}`;
97
102
  }}
103
+ agencyHrefFor={(host) => `/agency/${host.slug}`}
104
+ openAgencyInNewTab
98
105
  />
99
106
  );
100
107
  }
101
108
  ```
102
109
 
103
- ### Landing carousels
110
+ ### Landing — all four carousels
104
111
 
105
112
  ```tsx
106
113
  import { TravoriesClient, HomeSections } from "@travories/frontend-sdk";
@@ -112,7 +119,6 @@ function Landing() {
112
119
  return (
113
120
  <HomeSections
114
121
  client={client}
115
- currency="USD"
116
122
  hrefFor={(pkg) => `/package/${pkg.slug}`}
117
123
  openInNewTab
118
124
  />
@@ -122,6 +128,55 @@ function Landing() {
122
128
 
123
129
  Renders the four canonical sections: **Popular**, **Trending Right Now**, **Top Rated**, **More to Explore**.
124
130
 
131
+ ### Landing — interleaved sections (FAQ between carousels)
132
+
133
+ When you want SDK sections mixed with your own content (hero, FAQ, banners), wrap with `<HomeSectionsProvider>` and drop `<PackagesSection>` anywhere inside. **All sections share one fetch.**
134
+
135
+ ```tsx
136
+ import {
137
+ TravoriesClient,
138
+ HomeSectionsProvider,
139
+ PackagesSection,
140
+ } from "@travories/frontend-sdk";
141
+
142
+ const client = new TravoriesClient({ baseUrl: "https://api.travories.com" });
143
+
144
+ function Landing() {
145
+ return (
146
+ <HomeSectionsProvider client={client}>
147
+ <Hero /> {/* your own */}
148
+ <PackagesSection section="popular" hrefFor={...} openInNewTab />
149
+ <PackagesSection section="trending" hrefFor={...} openInNewTab />
150
+ <FAQ /> {/* your own */}
151
+ <PackagesSection section="topRated" title="Editor's picks" />
152
+ <PackagesSection section="moreToExplore" />
153
+ <Newsletter /> {/* your own */}
154
+ </HomeSectionsProvider>
155
+ );
156
+ }
157
+ ```
158
+
159
+ Available section keys: `"popular" | "trending" | "topRated" | "moreToExplore"`.
160
+
161
+ ### Agency profile page
162
+
163
+ ```tsx
164
+ import { TravoriesClient, AgencyDetailView } from "@travories/frontend-sdk";
165
+
166
+ function AgencyPage({ slug }: { slug: string }) {
167
+ return (
168
+ <AgencyDetailView
169
+ client={client}
170
+ slug={slug}
171
+ packageHrefFor={(pkg) => `/package/${pkg.slug}`}
172
+ openPackagesInNewTab
173
+ />
174
+ );
175
+ }
176
+ ```
177
+
178
+ Renders: breadcrumb → title → host card + overview + verified badge → partner associations → "Popular packages from {agency}" carousel.
179
+
125
180
  ### Just the data
126
181
 
127
182
  ```ts
@@ -129,74 +184,145 @@ import { TravoriesClient } from "@travories/frontend-sdk";
129
184
 
130
185
  const client = new TravoriesClient({ baseUrl: "https://api.travories.com" });
131
186
 
132
- const pkg = await client.getPackageBySlug("everest-base-camp");
133
- const bundle = await client.getPackageBundle("everest-base-camp");
134
- // ↑ pkg + host + attractions + routes in one call
135
- const sections = await client.packages.getHomeSections();
187
+ const pkg = await client.getPackageBySlug("everest-base-camp");
188
+ const bundle = await client.getPackageBundle("everest-base-camp");
189
+ // ↑ pkg + host + attractions + per-day GeoJSON routes, in one call
190
+ const sections = await client.packages.getHomeSections();
191
+ const agency = await client.getAgencyBundle("travel-route-pvt-ltd");
192
+ const routes = await client.packages.getRoutes("everest-base-camp");
193
+ ```
194
+
195
+ Returns `null` on 404 / network errors by default (silent mode). Pass `{ silent: false }` to throw instead.
196
+
197
+ ---
198
+
199
+ ## Booking flow
200
+
201
+ End-to-end booking takes 3 lines of consumer code:
202
+
203
+ ```tsx
204
+ <PackageDetailView
205
+ client={client}
206
+ slug={slug}
207
+ onReserve={async ({ pkg, arrival, travelers }) => {
208
+ const { data: bookingId } = await client.bookings.initiate({
209
+ packageId: pkg.slug,
210
+ arrivalDate: arrival, // ISO date string e.g. "2027-08-15"
211
+ travelers, // [{ name: "Adult", count: 2 }, …]
212
+ });
213
+ // bookingId is a UUID returned by the BE.
214
+ // Hand off to your payment flow:
215
+ window.location.href = `/checkout/${bookingId}`;
216
+ // …or open in a new tab:
217
+ // window.open(`/checkout/${bookingId}`, "_blank", "noopener,noreferrer");
218
+ }}
219
+ />
136
220
  ```
137
221
 
138
- No React, no UI just typed data. Use in scripts, edge functions, Node servers, anywhere.
222
+ **What happens visually:** the user picks a date + travelers in the built-in BookingCardLite. When they click **Book Now**, the button switches to **"Initiating…"** and disables itself until `onReserve` resolves (it auto-detects a returned `Promise`). On success, your callback navigates wherever it needs to. On error, throw or `alert` — the button re-enables automatically.
223
+
224
+ **Errors:** `client.bookings.initiate()` throws `TravoriesApiError` on a non-2xx response. Wrap in try/catch:
225
+
226
+ ```tsx
227
+ onReserve={async (payload) => {
228
+ try {
229
+ const { data } = await client.bookings.initiate({ ... });
230
+ navigateToCheckout(data);
231
+ } catch (err) {
232
+ alert(`Failed: ${err instanceof Error ? err.message : String(err)}`);
233
+ }
234
+ }}
235
+ ```
139
236
 
140
237
  ---
141
238
 
142
- ## Components
239
+ ## Components reference
143
240
 
144
241
  ### Page-level drop-ins
145
242
 
146
- | Component | What it renders |
243
+ | Component | Renders |
147
244
  |---|---|
148
- | `<PackageDetailView />` | Full detail page: gallery, overview, key facts, itinerary + map, included/excluded/to-pack, host. |
149
- | `<HomeSections />` | All four landing sections in one block (one fetch). |
150
- | `<HomeSectionsProvider />` | Fetches the four sections once, exposes via context. Wrap your tree to use `<PackagesSection>` anywhere inside. |
151
- | `<PackagesSection section="trending" />` | One specific section (Popular / Trending / Top Rated / More to Explore). Drop multiple inside a Provider — they share one fetch. |
245
+ | `<PackageDetailView />` | Full package detail page |
246
+ | `<AgencyDetailView />` | Full agency profile page |
247
+ | `<HomeSections />` | All four landing carousels in one block |
248
+ | `<HomeSectionsProvider />` | Wraps your tree; fetches once; sub-sections share data |
249
+ | `<PackagesSection section="..." />` | One specific section, consumes the Provider |
152
250
 
153
- ### Composable sub-components
251
+ ### Sub-components for custom layouts
154
252
 
155
- For custom layouts, every visual primitive is exported:
253
+ All exported individually:
156
254
 
157
255
  | Component | Purpose |
158
256
  |---|---|
159
- | `<PackagesCarousel />` | Horizontal scrollable strip with title + arrows. Pass your own list of packages. |
160
- | `<PackageCard />` | Single card. Use in grids, masonry, anywhere. |
161
- | `<TopSection />` | Detail-page header: breadcrumb, title, rating, gallery. |
162
- | `<GallerySection />` | 5-image gallery grid + lightbox. |
163
- | `<PackageOverview />` | Description + key facts panel. |
164
- | `<Itinerary />` | Day tabs + day card + map (with FlyToBounds). |
165
- | `<PackageMap />` | Leaflet wrapper with markers + GeoJSON polylines. |
166
- | `<PackageIncluded />` | What's-included checklist. |
167
- | `<PackageExcludedAndToPack />` | What's-excluded + what-to-pack lists. |
168
- | `<HostCard />`, `<HostAbout />` | Host / agency card + about block. |
169
- | `<BookingCardLite />` | Booking sidebar: date picker, traveler stepper, price math, Reserve CTA. |
257
+ | `<PackagesCarousel title packages />` | Horizontal scrollable carousel with arrows |
258
+ | `<PackageCard pkg />` | Single card (image + title + duration · stars · price) |
259
+ | `<TopSection topData />` | Detail-page header (breadcrumb, title, rating, gallery) |
260
+ | `<GallerySection media />` | 5-image grid + click-to-open lightbox |
261
+ | `<PackageOverview description topData />` | Description + key facts panel |
262
+ | `<Itinerary Description routes />` | Day tabs + day card + map (sticky on scroll) |
263
+ | `<PackageMap locations routes dayNumber />` | Leaflet map with markers + GeoJSON polylines |
264
+ | `<PackageIncluded thingsToDo />` | What's-included checklist |
265
+ | `<PackageExcludedAndToPack thingsExcluded thingsToPack />` | Two checklists |
266
+ | `<HostCard hostData onAgencyClick />` | Agency card with stats |
267
+ | `<HostAbout hostName description />` | "About {host}" prose block |
268
+ | `<BookingCardLite DescriptionData onReserve />` | Booking sidebar |
269
+ | `<AgencyAssociations associations />` | Partner-badge strip |
270
+
271
+ ### Shared `<PackageDetailView />` props
272
+
273
+ | Prop | Type | Notes |
274
+ |---|---|---|
275
+ | `client` | `TravoriesClient` | Required when not using `initialBundle` |
276
+ | `slug` | `string` | Required when not using `initialBundle` |
277
+ | `initialBundle` | `PackageBundle \| null` | Pre-fetched (SSR pattern). Skips the internal fetch |
278
+ | `currency` | `string` | Default `"USD"`. Anything `Intl.NumberFormat` accepts |
279
+ | `onReserve` | `(payload: ReservePayload) => void \| Promise<void>` | Return a Promise to enable the "Initiating…" CTA state |
280
+ | `onSelectHost` | `(host: PackageHost) => void` | Fired when host card is clicked |
281
+ | `agencyHrefFor` | `(host) => string` | If set, host card becomes an `<a href>` |
282
+ | `openAgencyInNewTab` | `boolean` | Adds `target="_blank"` on the host anchor |
283
+ | `className` | `string` | Extra class on root |
284
+ | `renderLoading` | `() => ReactNode` | Custom skeleton |
285
+ | `renderNotFound` | `() => ReactNode` | Custom 404 |
286
+
287
+ ### Shared `<AgencyDetailView />` props
288
+
289
+ | Prop | Type | Notes |
290
+ |---|---|---|
291
+ | `client` / `slug` / `initialBundle` | — | Same dual mode as `<PackageDetailView />` |
292
+ | `currency` | `string` | For package card prices |
293
+ | `onSelectPackage` | `(pkg: HomePackageCard) => void` | Click handler |
294
+ | `packageHrefFor` | `(pkg) => string` | Renders package cards as anchors |
295
+ | `openPackagesInNewTab` | `boolean` | Adds `target="_blank"` |
296
+ | `renderLoading` / `renderNotFound` | — | Override defaults |
170
297
 
171
298
  ---
172
299
 
173
- ## Hooks
300
+ ## Hooks reference
174
301
 
175
- For headless / build-your-own-UI consumers:
302
+ | Hook | Returns |
303
+ |---|---|
304
+ | `usePackageBySlug(client, slug)` | `{ data: TravoriesPackage \| null, loading, error, refetch }` |
305
+ | `usePackageBundle(client, slug)` | `{ data: PackageBundle \| null, loading, error, refetch }` |
306
+ | `useHomeSections(client)` | `{ data: HomeSectionsResponse \| null, loading, error, refetch }` |
307
+ | `useHomeSectionsContext()` | Same shape — reads from `<HomeSectionsProvider>` (shared fetch) |
308
+ | `useAgencyBundle(client, slug)` | `{ data: AgencyBundle \| null, loading, error, refetch }` |
176
309
 
177
- ```tsx
178
- import { usePackageBundle, useHomeSections } from "@travories/frontend-sdk";
310
+ All hooks accept `null` for `client` / `slug` to **stay inert** (no fetch). Use when you already have data via `initialBundle` and just need the hook to satisfy rules-of-hooks.
179
311
 
180
- function Custom() {
312
+ Example with custom UI:
313
+
314
+ ```tsx
315
+ function Custom({ slug }: { slug: string }) {
181
316
  const { data, loading, error, refetch } = usePackageBundle(client, slug);
182
- // data: { pkg, host, attractions, routes }
183
317
 
184
- const home = useHomeSections(client);
185
- // home.data: { popular, trending, topRated, moreToExplore }
318
+ if (loading) return <MySpinner />;
319
+ if (error) return <button onClick={refetch}>Retry: {error.message}</button>;
320
+ if (!data) return <My404 />;
186
321
 
187
- // ... render however you want
322
+ return <article><h1>{data.pkg.title}</h1>{/* ... */}</article>;
188
323
  }
189
324
  ```
190
325
 
191
- | Hook | Returns |
192
- |---|---|
193
- | `usePackageBySlug(client, slug)` | `{ data: TravoriesPackage | null, loading, error, refetch }` |
194
- | `usePackageBundle(client, slug)` | `{ data: PackageBundle | null, loading, error, refetch }` |
195
- | `useHomeSections(client)` | `{ data: HomeSectionsResponse | null, loading, error, refetch }` |
196
- | `useHomeSectionsContext()` | Same as above, but reads from `<HomeSectionsProvider>` context (one shared fetch). |
197
-
198
- All hooks accept `null` instead of a client to stay inert (useful when you already have data via `initialBundle` / `initialSections`).
199
-
200
326
  ---
201
327
 
202
328
  ## Client configuration
@@ -204,37 +330,40 @@ All hooks accept `null` instead of a client to stay inert (useful when you alrea
204
330
  ```ts
205
331
  new TravoriesClient({
206
332
  baseUrl: "https://api.travories.com", // required
207
- apiKey: "tvr_xxx", // optional — sent as x-api-key
208
- authToken: () => session?.accessToken, // optional — sent as Bearer
209
- defaultHeaders: { "x-trace-id": "abc" }, // optional
333
+ apiKey: "tvr_xxx", // optional — sent as `x-api-key`
334
+ authToken: () => session?.accessToken, // optional — sent as `Bearer`
335
+ defaultHeaders: { "x-trace-id": "abc" }, // optional — merged into every request
210
336
  fetchImpl: customFetch, // optional — supply your own fetch
211
337
  });
212
338
  ```
213
339
 
214
- `authToken` accepts a string, a function, or an async function — useful when the token rotates.
340
+ `authToken` accepts a **string**, a **sync function**, or an **async function** — useful when the token rotates per request.
215
341
 
216
- Errors fall back silently and return `null` by default. Pass `silent: false` per-call to throw `TravoriesApiError`:
342
+ ### Available resources
217
343
 
218
344
  ```ts
219
- import { TravoriesApiError } from "@travories/frontend-sdk";
345
+ client.packages // PackagesResource
346
+ client.agencies // AgenciesResource
347
+ client.bookings // BookingsResource
348
+ ```
220
349
 
221
- try {
222
- const pkg = await client.packages.getBySlug("missing", { silent: false });
223
- } catch (err) {
224
- if (err instanceof TravoriesApiError) {
225
- console.error(err.status, err.url, err.body);
226
- }
227
- }
350
+ ### Convenience shortcuts on the client itself
351
+
352
+ ```ts
353
+ client.getPackageBySlug(slug)
354
+ client.getPackageBundle(slug)
355
+ client.getAgencyBySlug(slug)
356
+ client.getAgencyBundle(slug)
228
357
  ```
229
358
 
230
359
  ---
231
360
 
232
- ## SSR with Next.js
361
+ ## SSR with Next.js / Remix
233
362
 
234
- The recommended pattern: fetch on the server, hand off via `initialBundle` / `initialSections`. The component skips its internal fetch and renders synchronously.
363
+ Fetch on the server, hand off via `initialBundle` / `initialSections`. The component skips its internal fetch and renders synchronously.
235
364
 
236
365
  ```tsx
237
- // app/packages/[slug]/page.tsx
366
+ // app/packages/[slug]/page.tsx — Next.js App Router
238
367
  import { TravoriesClient, PackageDetailView } from "@travories/frontend-sdk";
239
368
  import "@travories/frontend-sdk/styles.css";
240
369
 
@@ -243,102 +372,59 @@ const client = new TravoriesClient({ baseUrl: process.env.TRAVORIES_API_URL! });
243
372
  export default async function Page({ params }: { params: { slug: string } }) {
244
373
  const bundle = await client.getPackageBundle(params.slug);
245
374
  if (!bundle) return <NotFound />;
246
-
247
375
  return <PackageDetailView initialBundle={bundle} />;
248
376
  }
249
377
  ```
250
378
 
251
379
  ```tsx
252
- // app/page.tsx (landing)
253
- import { TravoriesClient, HomeSections } from "@travories/frontend-sdk";
254
- import "@travories/frontend-sdk/styles.css";
255
-
256
- const client = new TravoriesClient({ baseUrl: process.env.TRAVORIES_API_URL! });
257
-
258
- export default async function Landing() {
259
- const sections = await client.packages.getHomeSections();
260
- return (
261
- <HomeSections
262
- initialSections={sections}
263
- hrefFor={(p) => `/package/${p.slug}`}
264
- />
265
- );
266
- }
380
+ // app/page.tsx landing
381
+ const sections = await client.packages.getHomeSections();
382
+ return <HomeSections initialSections={sections} hrefFor={...} />;
267
383
  ```
268
384
 
269
- Works with Pages Router (`getServerSideProps`), Remix loaders, and React Router data API too — the pattern is the same.
270
-
271
- ---
272
-
273
- ## Composable landing layouts
274
-
275
- When you want **SDK sections interleaved with your own content** (FAQs, testimonials, ads), wrap the page in `<HomeSectionsProvider>` and drop `<PackagesSection>` anywhere inside. Sections share one fetch.
276
-
277
385
  ```tsx
278
- import {
279
- TravoriesClient,
280
- HomeSectionsProvider,
281
- PackagesSection,
282
- } from "@travories/frontend-sdk";
283
-
284
- const client = new TravoriesClient({ baseUrl: "https://api.travories.com" });
285
-
286
- function Landing() {
287
- return (
288
- <HomeSectionsProvider client={client}>
289
- <Hero />
290
- <PackagesSection section="popular" hrefFor={(p) => `/package/${p.slug}`} />
291
- <PackagesSection section="trending" />
292
- <FAQ /> {/* your own */}
293
- <Newsletter /> {/* your own */}
294
- <PackagesSection section="topRated" title="Editor's picks" />
295
- <PackagesSection section="moreToExplore" />
296
- <Footer />
297
- </HomeSectionsProvider>
298
- );
299
- }
386
+ // app/agency/[slug]/page.tsx
387
+ const agency = await client.getAgencyBundle(params.slug);
388
+ if (!agency) return <NotFound />;
389
+ return <AgencyDetailView initialBundle={agency} packageHrefFor={...} />;
300
390
  ```
301
391
 
302
- Each section can override `title`, `subtitle`, `currency`, `onSelect`, `hrefFor`, `openInNewTab`, `hideWhenLoading`, `hideWhenEmpty`.
303
-
304
- For a totally custom block, drop down to `useHomeSectionsContext()`:
305
-
306
- ```tsx
307
- function StatsBar() {
308
- const { data } = useHomeSectionsContext();
309
- if (!data) return null;
310
- return <div>{data.popular.length + data.trending.length} packages featured</div>;
311
- }
312
- ```
392
+ Same shape works with Remix / React Router data loaders.
313
393
 
314
394
  ---
315
395
 
316
396
  ## Styling
317
397
 
318
- The SDK ships precompiled, scoped CSS:
398
+ The SDK ships **precompiled CSS** scoped under a wrapper class — your app's styling is never affected.
319
399
 
320
400
  ```ts
321
401
  import "@travories/frontend-sdk/styles.css";
322
402
  ```
323
403
 
324
- **Scope guarantee**: all utility classes are scoped under `.tvr-package-detail` so they cannot collide with your own Tailwind setup. Your app's CSS is never affected.
404
+ **Scope guarantee**: every utility class is generated under `.tvr-package-detail` selectors, so there's no collision risk with your own Tailwind / CSS Modules / whatever.
325
405
 
326
- **Re-skinning** override the design tokens at the wrapper:
406
+ **Re-skinning**: override the design tokens with CSS:
327
407
 
328
408
  ```css
329
409
  .tvr-package-detail {
330
- /* CSS variables you can override */
331
410
  --tvr-radius: 0.75rem;
411
+ /* …any other CSS vars you've added */
332
412
  }
333
413
  ```
334
414
 
335
- Tailwind tokens like `text-primary-normal` and `bg-secondary` remain customizable via standard CSS overrides if needed — the precompiled output is plain CSS.
415
+ For more aggressive customization, increase specificity:
416
+
417
+ ```css
418
+ .my-app .tvr-package-detail .text-primary-normal {
419
+ color: #1d4ed8;
420
+ }
421
+ ```
336
422
 
337
423
  ---
338
424
 
339
- ## TypeScript
425
+ ## TypeScript types
340
426
 
341
- Every export ships with full type definitions. Notable types:
427
+ Every export ships with full `.d.ts` definitions. Notable types:
342
428
 
343
429
  ```ts
344
430
  import type {
@@ -353,11 +439,21 @@ import type {
353
439
  PackageRoutesDay,
354
440
  HomePackageCard,
355
441
  HomeSectionsResponse,
442
+ AgencyHostDetails,
443
+ AgencyAssociationItem,
444
+ AgencyBundle,
356
445
  MajorAttractionItem,
357
446
  TravoriesImage,
358
447
 
448
+ // Booking
449
+ BookingTraveler,
450
+ BookingInitiateRequest,
451
+ BookingInitiateResponse,
452
+ ReservePayload,
453
+
359
454
  // Component props
360
455
  PackageDetailViewProps,
456
+ AgencyDetailViewProps,
361
457
  HomeSectionsProps,
362
458
  HomeSectionsProviderProps,
363
459
  PackagesSectionProps,
@@ -367,8 +463,9 @@ import type {
367
463
  UsePackageBySlugResult,
368
464
  UsePackageBundleResult,
369
465
  UseHomeSectionsResult,
466
+ UseAgencyBundleResult,
370
467
 
371
- // Errors
468
+ // HTTP
372
469
  HttpClientConfig,
373
470
  HttpRequestOptions,
374
471
  } from "@travories/frontend-sdk";
@@ -376,28 +473,96 @@ import type {
376
473
 
377
474
  ---
378
475
 
379
- ## API reference
476
+ ## API endpoints called
380
477
 
381
- ### `client.packages` methods
478
+ For network/CORS troubleshooting, here's exactly what each method hits:
382
479
 
383
- | Method | Endpoint | Returns |
384
- |---|---|---|
385
- | `getBySlug(slug)` | `GET /agency-package/by-slug/:slug` | `TravoriesPackage \| null` |
386
- | `getHost(slug)` | `GET /agency-info/by-package-slug/:slug` | `PackageHostResponse \| null` |
387
- | `getMajorAttractions(slug)` | `GET /major-attractions/package/:slug` | `MajorAttractionsResponse \| null` |
388
- | `getRoutes(slug)` | `GET /agency-package/routes/:slug` | `PackageRoutesResponse \| null` |
389
- | `getHomeSections()` | `GET /agency-package/home/sections` | `HomeSectionsResponse \| null` |
390
- | `getBundle(slug)` | parallel combines the first four | `PackageBundle \| null` |
480
+ | Method | Endpoint |
481
+ |---|---|
482
+ | `client.packages.getBySlug(slug)` | `GET /agency-package/by-slug/:slug` |
483
+ | `client.packages.getHost(slug)` | `GET /agency-info/by-package-slug/:slug` |
484
+ | `client.packages.getMajorAttractions(slug)` | `GET /major-attractions/package/:slug` |
485
+ | `client.packages.getRoutes(slug)` | `GET /agency-package/routes/:slug` |
486
+ | `client.packages.getHomeSections()` | `GET /agency-package/home/sections` |
487
+ | `client.packages.getBundle(slug)` | parallel: by-slug + host + attractions + routes |
488
+ | `client.agencies.getBySlug(slug)` | `GET /agency-info/by-slug/:slug` |
489
+ | `client.agencies.getAssociations(slug)` | `GET /agency-association/agency/slug/:slug` |
490
+ | `client.agencies.getPackages(slug)` | `GET /agency-package/by-agency/:slug?limit&page` |
491
+ | `client.agencies.getBundle(slug)` | parallel: 3 calls above |
492
+ | `client.bookings.initiate(payload)` | `POST /booking/initiate` |
493
+
494
+ All `GET`s are silent by default (return `null` on 404 / network error). The `POST` throws `TravoriesApiError` so you can surface failures to the user.
495
+
496
+ ---
497
+
498
+ ## Edge cases & FAQ
499
+
500
+ **Q: What happens if the package slug doesn't exist?**
501
+ A: `<PackageDetailView>` shows the not-found state (override with `renderNotFound`). Hooks return `{ data: null, loading: false, error: null }`.
502
+
503
+ **Q: What if the API is unreachable / CORS / network down?**
504
+ A: Silent endpoints log to the console and return `null`. Components display the not-found state. The booking endpoint (POST) throws, so wrap it in try/catch.
505
+
506
+ **Q: How do I show "Sold out" / disable Reserve?**
507
+ A: Don't pass `onReserve` — the button auto-disables and shows "Contact host to book".
508
+
509
+ **Q: My consumer doesn't have auth yet — how do I hide the wishlist heart?**
510
+ A: It's already hidden by default. Opt in by passing `showHeart` + `liked` + `onLike` to `<PackageCard>`.
511
+
512
+ **Q: Can I show only one section from the home payload?**
513
+ A: Yes — either filter via `<HomeSections order={["trending"]} />` or use `<PackagesSection section="trending" />` inside a `<HomeSectionsProvider>`.
514
+
515
+ **Q: Does the SDK fetch data multiple times if I render `<HomeSections>` and `<PackageDetailView>` on the same page?**
516
+ A: Each component does its own fetch independently. Use the Provider pattern for `HomeSections` to share its fetch across multiple `<PackagesSection>`. Package detail and agency detail each do their own bundle fetch.
517
+
518
+ **Q: How do I get the user to a new tab on card click?**
519
+ A: Pass `hrefFor={(p) => "/package/" + p.slug}` + `openInNewTab` on `<PackagesCarousel>` / `<PackagesSection>` / `<HomeSections>`. Cards render as real `<a href target="_blank">` so right-click → "Open in new tab" also works.
520
+
521
+ **Q: Can I change a section's title?**
522
+ A: Pass `title` to `<PackagesSection>`, or use the `titles` map on `<HomeSections>`.
523
+
524
+ **Q: How do I handle currency conversion?**
525
+ A: Pass `currency="EUR"` etc. — uses `Intl.NumberFormat` under the hood. The SDK doesn't convert; it just formats the amount the BE returned.
526
+
527
+ **Q: Can I use the SDK on the server only (no React)?**
528
+ A: Yes. Import only the client + types: `import { TravoriesClient } from "@travories/frontend-sdk"`. The React pieces are tree-shaken out if you never touch them.
529
+
530
+ **Q: Does it work in Edge runtimes (Vercel Edge, Cloudflare Workers)?**
531
+ A: Yes — the client uses only the built-in `fetch`. No Node-specific APIs.
532
+
533
+ **Q: Bundle size?**
534
+ A: ~100 KB gzipped (ESM, including Leaflet which is the heaviest dep). Tree-shakeable — Node-only consumers get ~10 KB.
535
+
536
+ **Q: How do I pre-warm the Leaflet map for faster perceived load?**
537
+ A: Map only mounts client-side (it's wrapped in an effect that imports `react-leaflet` on demand). Nothing to pre-warm.
538
+
539
+ **Q: My styles look broken — what's wrong?**
540
+ A: Make sure you imported `@travories/frontend-sdk/styles.css` exactly once at your app entry point. Don't import multiple times — that's fine but wasteful.
541
+
542
+ ---
543
+
544
+ ## Browser support
545
+
546
+ Last 2 versions of Chrome, Firefox, Safari, Edge. IE 11 is not supported.
547
+
548
+ `fetch`, `URL`, `IntersectionObserver`, CSS Grid, and `Intl.NumberFormat` are required.
549
+
550
+ ---
551
+
552
+ ## Versioning & releases
553
+
554
+ The SDK uses [semver](https://semver.org/). While < `1.0`, **breaking changes** are released as **minor** bumps (e.g. `0.1.x` → `0.2.0`), not majors. At `1.0` we switch to strict semver.
391
555
 
392
- Shortcut methods on the client itself: `client.getPackageBySlug(slug)`, `client.getPackageBundle(slug)`.
556
+ Release cadence is on-demand. Subscribe to the [releases page](https://github.com/Travories/frontend-sdk/releases) for notifications.
393
557
 
394
558
  ---
395
559
 
396
560
  ## Support
397
561
 
398
- - **Docs / source**: [GitHub](https://github.com/Travories/frontend-sdk)
399
- - **Issues / requests**: [github.com/Travories/frontend-sdk/issues](https://github.com/Travories/frontend-sdk/issues)
400
- - **API**: [api.travories.com](https://api.travories.com)
562
+ - **Source** / **issues**: [github.com/Travories/frontend-sdk](https://github.com/Travories/frontend-sdk)
563
+ - **Demo**: [github.com/Travories/frontend-sdk-preview](https://github.com/Travories/frontend-sdk-preview) — clone + `npm install` + `npm run dev`
564
+ - **API** (rate limits, auth, raw responses): [api.travories.com](https://api.travories.com)
565
+ - **Production site**: [travories.com](https://travories.com)
401
566
 
402
567
  ---
403
568
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travories/frontend-sdk",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Official Travories SDK — fetch packages by slug and render the full Travories package detail page in any React app.",
5
5
  "license": "MIT",
6
6
  "author": {