@v-office/website-headless 0.1.1 → 1.0.0
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/README.md +2177 -54
- package/dist/enhance/index.d.mts +501 -180
- package/dist/enhance/index.mjs +1388 -472
- package/dist/index-CPMXjfFH.d.mts +2724 -0
- package/dist/index.d.mts +2602 -1165
- package/dist/index.mjs +8568 -1735
- package/dist/parts-CS1VBrPt.mjs +648 -0
- package/dist/sdk-claim-CsYFzvEc.mjs +72 -0
- package/dist/sdk-create-Bsd9lqj_.mjs +9 -0
- package/instructions/CHANGELOG.md +9 -0
- package/instructions/MIGRATION.md +9 -0
- package/instructions/versions/0.1.1/CHANGELOG.md +7 -0
- package/instructions/versions/0.1.1/MIGRATION.md +4 -0
- package/instructions/versions/0.1.1/release.json +3 -0
- package/instructions/versions/0.2.0/CHANGELOG.md +17 -0
- package/instructions/versions/0.2.0/MIGRATION.md +16 -0
- package/instructions/versions/0.2.0/release.json +3 -0
- package/instructions/versions/1.0.0/CHANGELOG.md +170 -0
- package/instructions/versions/1.0.0/MIGRATION.md +252 -0
- package/instructions/versions/1.0.0/release.json +10 -0
- package/package.json +14 -11
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { k as acquireShared } from "./parts-CS1VBrPt.mjs";
|
|
2
|
+
//#region src/sdk/errors.ts
|
|
3
|
+
/** Nesting deeper than this is cut: a cause chain is a handful of levels, never more. */
|
|
4
|
+
const MAX_DEPTH = 6;
|
|
5
|
+
/**
|
|
6
|
+
* Every failure arrives as a `CoreSDKError` (`source`, `operation`, `message`, `cause`), and the
|
|
7
|
+
* sentence that names the fault is in `cause` — a GraphQL `errors` array, a transport
|
|
8
|
+
* `{ status, statusText, body }`, or a nested `CoreSDKError`. `console.error(error)` does not print it,
|
|
9
|
+
* and neither does `Object.entries`: `Error` declares `name`, `message` and `cause` non-enumerable
|
|
10
|
+
* (verified on the real class: `Object.keys(new CoreSDKError(…))` is `["source", "operation", "_tag"]`).
|
|
11
|
+
* So those three are read by hand, `cause` first after the message so the reason reads under it.
|
|
12
|
+
*/
|
|
13
|
+
function unwrap(value, depth, seen) {
|
|
14
|
+
if (value === null || typeof value !== "object") return value;
|
|
15
|
+
if (depth > MAX_DEPTH) return "[truncated]";
|
|
16
|
+
if (seen.has(value)) return "[circular]";
|
|
17
|
+
seen.add(value);
|
|
18
|
+
if (Array.isArray(value)) return value.map((entry) => unwrap(entry, depth + 1, seen));
|
|
19
|
+
const out = {};
|
|
20
|
+
if (value instanceof Error) {
|
|
21
|
+
out["name"] = value.name;
|
|
22
|
+
out["message"] = value.message;
|
|
23
|
+
if (value.cause !== void 0) out["cause"] = unwrap(value.cause, depth + 1, seen);
|
|
24
|
+
}
|
|
25
|
+
for (const [key, entry] of Object.entries(value)) if (key !== "stack") out[key] = unwrap(entry, depth + 1, seen);
|
|
26
|
+
return out;
|
|
27
|
+
}
|
|
28
|
+
/** See {@link DescribeSdkError}. */
|
|
29
|
+
const describeSdkError = function describeSdkError(error) {
|
|
30
|
+
try {
|
|
31
|
+
return JSON.stringify(unwrap(error, 0, /* @__PURE__ */ new WeakSet()), null, 2) ?? String(error);
|
|
32
|
+
} catch {
|
|
33
|
+
return String(error);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
/** See {@link LogSdkFailure}. */
|
|
37
|
+
const logSdkFailure = function logSdkFailure(scope, error) {
|
|
38
|
+
console.error(`[sdk] ${scope} failed:`, describeSdkError(error));
|
|
39
|
+
};
|
|
40
|
+
//#endregion
|
|
41
|
+
//#region src/sdk/claim.ts
|
|
42
|
+
/** See {@link SdkInput}. */
|
|
43
|
+
const sdkInput = function sdkInput(setup, mode) {
|
|
44
|
+
const options = mode === "all-rentals-at-once" ? {
|
|
45
|
+
...setup.options,
|
|
46
|
+
searchAllRentalsAtOnce: true
|
|
47
|
+
} : setup.options;
|
|
48
|
+
return options ? {
|
|
49
|
+
config: setup.config,
|
|
50
|
+
options
|
|
51
|
+
} : { config: setup.config };
|
|
52
|
+
};
|
|
53
|
+
/** JSON with object keys sorted at every level, so two equal setups written in a different key order
|
|
54
|
+
* share one instance. `undefined` members drop out, as in JSON. */
|
|
55
|
+
function stableJson(value) {
|
|
56
|
+
return JSON.stringify(value, (_key, entry) => entry !== null && typeof entry === "object" && !Array.isArray(entry) ? Object.fromEntries(Object.entries(entry).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) : entry);
|
|
57
|
+
}
|
|
58
|
+
/** See {@link ClaimSdk}. */
|
|
59
|
+
const claimSdk = function claimSdk(setup, mode, construct) {
|
|
60
|
+
const claim = acquireShared("sdk", stableJson({
|
|
61
|
+
setup,
|
|
62
|
+
mode
|
|
63
|
+
}), () => construct(setup, mode), (sdk) => {
|
|
64
|
+
sdk.dispose().catch((error) => logSdkFailure("sdk.dispose", error));
|
|
65
|
+
});
|
|
66
|
+
return {
|
|
67
|
+
sdk: claim.value,
|
|
68
|
+
release: claim.release
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
//#endregion
|
|
72
|
+
export { logSdkFailure as i, sdkInput as n, describeSdkError as r, claimSdk as t };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { n as sdkInput } from "./sdk-claim-CsYFzvEc.mjs";
|
|
2
|
+
import { createWebsiteSDK } from "@v-office/website-sdk";
|
|
3
|
+
//#region src/sdk/create.ts
|
|
4
|
+
/** See {@link CreateSdk}. */
|
|
5
|
+
const createSdk = function createSdk(setup, mode = "default") {
|
|
6
|
+
return createWebsiteSDK(sdkInput(setup, mode));
|
|
7
|
+
};
|
|
8
|
+
//#endregion
|
|
9
|
+
export { createSdk as t };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
<!-- generated by `node .github/release/cli.mts index`; do not edit by hand -->
|
|
4
|
+
|
|
5
|
+
Release notes of `@v-office/website-headless`, newest first. Each version has its own directory.
|
|
6
|
+
|
|
7
|
+
- `versions/1.0.0/CHANGELOG.md`: release notes for 1.0.0
|
|
8
|
+
- `versions/0.2.0/CHANGELOG.md`: release notes for 0.2.0
|
|
9
|
+
- `versions/0.1.1/CHANGELOG.md`: release notes for 0.1.1
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Migration
|
|
2
|
+
|
|
3
|
+
<!-- generated by `node .github/release/cli.mts index`; do not edit by hand -->
|
|
4
|
+
|
|
5
|
+
Migration guides of `@v-office/website-headless`, newest first. Apply them in order when skipping versions.
|
|
6
|
+
|
|
7
|
+
- `versions/1.0.0/MIGRATION.md`: Migration: 0.2.0 to 1.0.0
|
|
8
|
+
- `versions/0.2.0/MIGRATION.md`: Migration: 0.1.1 to 0.2.0
|
|
9
|
+
- `versions/0.1.1/MIGRATION.md`: Migration to 0.1.1
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Changelog: 0.1.1
|
|
2
|
+
|
|
3
|
+
Baseline of the shipped release notes for `@v-office/website-headless`. The package is the framework-free behavior layer of the website platform: the checkout flow as a store, validation and insurance rules, the checkout's structural requirements and the progressive-enhancement engines.
|
|
4
|
+
|
|
5
|
+
Changes before 0.1.1 are recorded in this repository's git history only. From
|
|
6
|
+
this version on, every release carries its own `CHANGELOG.md` and
|
|
7
|
+
`MIGRATION.md` in `instructions/versions/<version>/`.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Changelog: 0.2.0
|
|
2
|
+
|
|
3
|
+
The initial availability of the booking date picker now carries the v9 rules for children and
|
|
4
|
+
pets, so a guest selector can stop offering a party the quote would refuse.
|
|
5
|
+
|
|
6
|
+
## Added
|
|
7
|
+
|
|
8
|
+
- `fetchInitialAvailability` returns `childrenAllowed` and `maxPets` in its
|
|
9
|
+
`InitialAvailability`. On v9 with `@v-office/website-sdk` 2.28.0 or later,
|
|
10
|
+
`childrenAllowed` is `false` when the rental takes no children, babies included, and
|
|
11
|
+
`maxPets` is the most pets the quote accepts, `0` for none. Both are `null` on v10, with an
|
|
12
|
+
older SDK and when the rental states no rule; `null` means no limit.
|
|
13
|
+
|
|
14
|
+
## Unchanged
|
|
15
|
+
|
|
16
|
+
- `maxPersons` and `maxAdults` keep their meaning. On v9 they carry the rental's limits from
|
|
17
|
+
`@v-office/website-sdk` 2.28.0 on, where they were `null` before.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Migration: 0.1.1 to 0.2.0
|
|
2
|
+
|
|
3
|
+
## 1. Upgrade
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
pnpm add @v-office/website-headless@0.2.0
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
No changes are required for code that reads `fetchInitialAvailability`. Code that builds an
|
|
10
|
+
`InitialAvailability` itself, such as a test fixture, adds `childrenAllowed: null` and
|
|
11
|
+
`maxPets: null`.
|
|
12
|
+
|
|
13
|
+
A site with its own guest selector can use the two fields: offer neither children nor babies
|
|
14
|
+
when `childrenAllowed` is `false`, and cap pets at `maxPets`, hiding them at `0`. Keep a
|
|
15
|
+
selection the guest arrived with visible until they remove it, so a party the quote refuses
|
|
16
|
+
can still be corrected.
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# Changelog: 1.0.0
|
|
2
|
+
|
|
3
|
+
1.0.0 is a redesign of the behaviour layer, with no 0.x compatibility. Every behaviour the
|
|
4
|
+
website surfaces need now lives here as a store or a pure rule (the search form, the live search,
|
|
5
|
+
the results map, the quote, the checkout and the contact form), every runner takes the SDK first,
|
|
6
|
+
one SDK instance serves a whole page, and the fleet's per-site rulings became options with safe
|
|
7
|
+
defaults instead of one site's behaviour for everyone. `@v-office/website-components` 1.0.0 renders
|
|
8
|
+
these stores; a site that builds its own UI uses the same stores and is held to the same rules.
|
|
9
|
+
Storage keys, URL keys and the German sort values are unchanged, so links and every guest's
|
|
10
|
+
Merkliste survive the switch. `MIGRATION.md` lists what to change, in order.
|
|
11
|
+
|
|
12
|
+
## Added
|
|
13
|
+
|
|
14
|
+
### SDK and wiring
|
|
15
|
+
|
|
16
|
+
- `SdkSetup` (`{ config, options? }`): the one plain-data SDK input every surface takes, written
|
|
17
|
+
once per site.
|
|
18
|
+
- `sdkConfigFromEnv(env)` with `SDK_ENV` and `SdkEnvError`: the fleet's env names
|
|
19
|
+
(`CMS_BACKEND`, `CMS_STAGE`, `HUB_API_KEY`, `HUB_GRAPHQL_URL`, `HUB_V0_API_BASE_URL`,
|
|
20
|
+
`HUB_V1_API_BASE_URL`, `IMAGE_PROXY_BASE_URL`, `VOFFICE_LOCAL_DEV_ACCESS_TOKEN`,
|
|
21
|
+
`VOFFICE_API_ENDPOINT`, `VOFFICE_SEARCH_ENDPOINT`, `VOFFICE_IMAGE_BASE_URL`) to an
|
|
22
|
+
`SdkConfig`. No backend default; one error names every missing or conflicting variable, never a
|
|
23
|
+
value.
|
|
24
|
+
- `shareSdk(setup, mode?)` and `loadSharedSdk(setup, mode?)`: every surface on a page with an
|
|
25
|
+
equal setup gets the same instance, so one 1 request/second queue and one cache serve the
|
|
26
|
+
page. `loadSharedSdk` imports the SDK on first use. `resolveSdk`, `SdkSource` (an instance or a
|
|
27
|
+
getter) and `SdkMode` (`"default"`, `"all-rentals-at-once"`).
|
|
28
|
+
- `walkSearchPages`, `fetchSearchPage`, `SEARCH_PAGE_SIZE`: the one paged walker every search
|
|
29
|
+
uses, 100 results per request.
|
|
30
|
+
|
|
31
|
+
### Stores and rules, per surface
|
|
32
|
+
|
|
33
|
+
- **Search form:** `createSearchForm` (the bar's state, a GET form that never calls the SDK),
|
|
34
|
+
`createDateSearch`, `createGuestSearch`, `describePeriod`, `formatStayRange`, and the rental
|
|
35
|
+
name jump (`matchRentals`, `loadRentalIndex`, `rentalIndexEntries`).
|
|
36
|
+
- **URL parameters:** `SEARCH_PARAMS`, `readSearch`, `writeSearch`, `searchHref`,
|
|
37
|
+
`SearchContract` (`backend`, `nightRangeSearch`, `aliases`), `readParty`, `partyPairs`.
|
|
38
|
+
- **Guests:** one party model (`Party`, `GuestOptions`, `GuestLimits`, `guestBounds`,
|
|
39
|
+
`clampParty`, `agesComplete`) for the search bar, the quote and the checkout. `limits.pets`: 0 =
|
|
40
|
+
no dogs, 1 = a toggle, more = a stepper. `GuestCaps` takes 0.2.0's v9 `childrenAllowed` and
|
|
41
|
+
`maxPets`; `guestBounds` applies them as 0.2.0's components did.
|
|
42
|
+
- **Search store:** `createSearchStore` (the page's one live search, with filters, sort, chips,
|
|
43
|
+
favourites scope and the empty and error outcomes), `acquireSearchStore`, `connectSearchStore`,
|
|
44
|
+
result items for prerendered listings (`catalogueResultItem`, `resultCardSource`), sort
|
|
45
|
+
(`SortKey`, `SORT_URL_VALUE`, `DEFAULT_SORT_OPTIONS`, `sortResults`), filter curation
|
|
46
|
+
(`FilterSpec`, `resolveFilters`, `measureFilters`), links (`handoverQuery`,
|
|
47
|
+
`externalSearchHref`, `sanitizeSearch`, `resultsQueryFlagScript`), `describeOutcome`.
|
|
48
|
+
- **Offers:** `offersWindowQuery`, `findOfferWindows`, `findCampaignStart`, `campaignGroup`,
|
|
49
|
+
`campaignChips`, `offerWindowHref`; result links carry `offer=` labels to the rental page.
|
|
50
|
+
- **Results map:** `createResultsMapController` over the search store, pins, framing, same-spot
|
|
51
|
+
layout, and the address decision `rentalAddressLine` with `RentalAddressMode` (no default).
|
|
52
|
+
- **Rental catalogue:** `loadRentalCatalogue(sdk, { locale, … })` gives `RentalDetail[]` for the
|
|
53
|
+
rental pages, JSON-LD and cards; `fetchRentalFlags`, slugs, counts, distances, amenity and
|
|
54
|
+
description previews, reviewer names, image URL rules.
|
|
55
|
+
- **Cards:** `CardSource` and `cardSource(rental, { href, … })`, the one card input;
|
|
56
|
+
`toCardModel`, `rentalFactsMap`, `loadRentalFacts`, `matches`.
|
|
57
|
+
- **Showcase:** `buildShowcaseGroups`, `selectRentals`, `selectRelated`, `rotationWindow`,
|
|
58
|
+
`resolveSearchGroups`.
|
|
59
|
+
- **Quote:** `createQuoteFlow` and `acquireQuoteFlow` (the booking widget as a store),
|
|
60
|
+
`createStayPicker`, `dayPriceLabel` with `DayPrices`, the rental page's URL contract
|
|
61
|
+
(`readQuoteHandover`, `writeQuoteParams`, `checkoutHref`, `stayQuery`), the cancellation-policy
|
|
62
|
+
rules (`describePolicyOffer`, `offeredPolicies`, `policyStance`, `policyTitle`, `policyTerms`,
|
|
63
|
+
`PolicyChoiceMode`), `BookableFrom` for a site that is not bookable online yet.
|
|
64
|
+
- **Checkout:** `readCheckoutHandover`, `checkoutSearch`, `createGuestForm` (the guest data form
|
|
65
|
+
with the fleet's validation timing), `readPaymentReturn`, option bookings (`?kind=option`).
|
|
66
|
+
- **Contact form:** `createContactForm` with field specs, validation and the PMS submit;
|
|
67
|
+
`DEFAULT_CONTACT_FIELDS`, `CONTACT_LIMITS`.
|
|
68
|
+
- **Favourites:** `favoritesStorageKey(namespace)`, `createFavorites`, `acquireFavorites`.
|
|
69
|
+
- **Countries and legal documents:** `countryOptions`, `dialCode`, `fetchLegalDocuments`.
|
|
70
|
+
- **Shared helpers:** `IsoDay` / `IsoMonth` and their arithmetic, `formatPriceLabel` with
|
|
71
|
+
`PriceCents`, `parseTextBlocks` and `normalizeStructuredDocument` (sanitized PMS text),
|
|
72
|
+
`routeHref`, `browserStorage`, the tracking-parameter list, the closed `GLYPHS` vocabulary.
|
|
73
|
+
|
|
74
|
+
### Events and part vocabularies
|
|
75
|
+
|
|
76
|
+
- `QUOTE_EVENT` (`voffice:quote`), `CHECKOUT_EVENT` (`voffice:checkout`) and `CONTACT_EVENT`
|
|
77
|
+
(`voffice:contact`) with their detail types. The stores hand them to `onEvent`; the components
|
|
78
|
+
dispatch them on `window`, and a site's own UI dispatches the same shapes. No detail carries
|
|
79
|
+
personal data.
|
|
80
|
+
- `ENHANCE_PARTS` and `GATE_MARKERS`, exported from both entries: the markup contract of the
|
|
81
|
+
enhance engines and the markers the platform's page gates read.
|
|
82
|
+
- `CHECKOUT_PARTS` gains `consent` and `insurancePremium`; `CHECKOUT_STRUCTURE` gains
|
|
83
|
+
`consents-before-submit` and `premium-before-submit`; `SUBMIT_WORDING` (per booking kind, German
|
|
84
|
+
and English) and `BOOKING_KIND_ATTRIBUTE` (`data-booking-kind` on the order button).
|
|
85
|
+
|
|
86
|
+
### Copy templates
|
|
87
|
+
|
|
88
|
+
- `fillTemplate(template, values)`: fills `{token}`s; an unknown token stays visible.
|
|
89
|
+
- `Template<K>`: the type of a copy string whose tokens are `K`. `fillTemplate` needs a value for
|
|
90
|
+
every token a template declares and takes no other.
|
|
91
|
+
- `defineCopy<T>()(overrides)`: checks a site's copy overrides at compile time (unknown keys, wrong
|
|
92
|
+
types, tokens the key does not declare) and returns them unchanged. `TokensOf` and
|
|
93
|
+
`CheckedCopy` are the types behind it.
|
|
94
|
+
- The contact messages type their templates: `extraRequired` and `fieldInvalid` take `{label}`,
|
|
95
|
+
`messageTooLong` `{max}`, `sendFailed` `{email}`.
|
|
96
|
+
|
|
97
|
+
### `./enhance`
|
|
98
|
+
|
|
99
|
+
- `enhance(root, options)`: one call in the layout binds every engine below the root, idempotent,
|
|
100
|
+
returns a `Dispose`. Engines: `bindStrip`, `bindLayer`, `bindGallery`, `bindReveal`,
|
|
101
|
+
`bindClamp`, `bindShare`, `bindImageFade`, `bindFavorites`, and the MapLibre engines
|
|
102
|
+
(`createMapSurface`, `bindResultMarkers`, `bindPlaceMarkers`, `observeResultCardHover`). The
|
|
103
|
+
map engines take the `maplibre-gl` loader from the caller; the package does not depend on it.
|
|
104
|
+
|
|
105
|
+
## Changed
|
|
106
|
+
|
|
107
|
+
- **Peer dependency:** `@v-office/website-sdk >=2.25.0 <3` (was `>=2.13.0 <3`). 1.0.0 uses the
|
|
108
|
+
unavailable `code` (2.16), v9 `matchingPeriods` (2.22), `searchAllRentalsAtOnce` (2.23), the Hub
|
|
109
|
+
`/Setup` fix and `v9Reviews.criteria` (2.25).
|
|
110
|
+
- **`createSdk(setup, mode?)`** takes an `SdkSetup` (was `createSdk(config, options?)`).
|
|
111
|
+
`SdkConfig` is the SDK's own config type, with `backend` explicit on v10.
|
|
112
|
+
- **Every SDK runner takes the SDK first:** `fetchQuote(sdk, request)`,
|
|
113
|
+
`runLiveSearch(sdk, search, options)` (returns a `SearchPage`), `submitBooking(sdk, quote,
|
|
114
|
+
contact, options)`, `payWithOption(sdk, option)`, `fetchInitialAvailability(sdk, input)`,
|
|
115
|
+
`fetchEndAvailability(sdk, input)`, `addQuoteService`, `removeQuoteService`,
|
|
116
|
+
`clearQuoteServices`, `selectQuotePolicy`, `selectQuoteInsurance`,
|
|
117
|
+
`pricePolicyBranches(sdk, base)`.
|
|
118
|
+
- **`createCheckoutFlow(sdk, options)`:** `handover`, `returnPath` and `messages` are required;
|
|
119
|
+
the state is `phase`, `stay`, `services`, `policy`, `insurance`, `voucher`, `consents`,
|
|
120
|
+
`blockers` (why the order button is off), `submit`, `confirmation` and `payment`. New actions
|
|
121
|
+
`setDates`, `setParty`, `loadGuestCaps` (the arrival map's caps for the guest editor, loaded
|
|
122
|
+
when it opens), `selectPolicy`, `setConsent`, `retry`; `openInsuranceDocument(url)` and
|
|
123
|
+
`confirmInsuranceDocuments(confirmed)`; `pay(option)`. Events go to `onEvent`. The flow never
|
|
124
|
+
scrolls the window.
|
|
125
|
+
- **Reshaped types:** `Stay` is `{ start, end, party }` in ISO days; `SearchResultItem` carries
|
|
126
|
+
`price: StayPrice`, `counts` and `periods`; `AlternativePeriod`, `FilterDef`, `ActiveFilter`
|
|
127
|
+
and `CheckoutIssue` are reshaped; `adaptGuestQuote` returns a `QuoteView` whose `price` is a
|
|
128
|
+
`PriceView`; `stayKeyOf` and `occupancyOf` take a `Stay` / `Party`;
|
|
129
|
+
`freeCancellationUntil(policy, arrival)` takes one policy and an `IsoDay`;
|
|
130
|
+
`detectCountry(countries, languages, fallback?)` no longer reads `navigator`.
|
|
131
|
+
- **Dates** are `IsoDay` / `IsoMonth` strings at every public boundary.
|
|
132
|
+
|
|
133
|
+
## Removed
|
|
134
|
+
|
|
135
|
+
- **Site-specific defaults:** `CHECKOUT_RETURN_PATH`, `DEFAULT_RENTALS_PATH`,
|
|
136
|
+
`LiveSearchOptions.rentalsPath` and the German `SORT_OPTIONS` labels. Routes and copy are the
|
|
137
|
+
site's.
|
|
138
|
+
- **0.1.x search, sort, filter and region helpers:** `SearchPayload`, `SearchImage`,
|
|
139
|
+
`SORT_PARAM`, `SortValue`, `SortOption`, `DEFAULT_SORT`, `activeSort`, `isPricedQuery`,
|
|
140
|
+
`isSortAvailable`, `resolveSdkSort`, `FilterType`, `isBooleanFilterActive`,
|
|
141
|
+
`activeFiltersFromQuery`, `PETS_PARAM`, `PETS_COUNT_PARAM`, `petsCountInQuery`,
|
|
142
|
+
`REGION_PARAM`, `activeRegions`, `scopeToRegions`.
|
|
143
|
+
- **0.1.x date and price helpers:** `NIGHT_RANGE_ENABLED`, `NIGHT_RANGE_KEYS`,
|
|
144
|
+
`parseNightRange`, `parseParam`, `parseGermanParam`, `isoDate`, `stayParams`, `tidyPrice`,
|
|
145
|
+
`nightlyPrice`.
|
|
146
|
+
- **0.1.x quote and checkout names:** `QuoteLine`, `PriceQuote`, `fullRefundRule`,
|
|
147
|
+
`policyFreeCancellationUntil`, `BookingContactForm`, `saveBookingSnapshot` and
|
|
148
|
+
`BookingSnapshotGuests` (`submitBooking` writes the snapshot), `VALIDATED_FIELDS`, `fieldError`,
|
|
149
|
+
`CheckoutFlowApi`, `CheckoutFlowState`, `CheckoutFlowActions`, `CheckoutFlowMessages`,
|
|
150
|
+
`BUTTON_SOLUTION_PATTERN`, `CountryGroupsLike`, and the checkout flow's `getSdk`, `stay`,
|
|
151
|
+
`chosenPolicy`, `filterOfferedPolicies` and `onRateConfigurationFault` options.
|
|
152
|
+
- **`./enhance`:** `initImageCarousels`, `initRentalCarousel`, `initHistoryOverlay`,
|
|
153
|
+
`initGallery`, `HistoryOverlayOptions`, `GalleryOptions`, `GalleryLayer`, and the
|
|
154
|
+
`[data-rental-tabs]` / `.rental-track` selectors. Dots are repainted through `data-active`; no
|
|
155
|
+
Tailwind class is written.
|
|
156
|
+
|
|
157
|
+
## Unchanged
|
|
158
|
+
|
|
159
|
+
- Storage keys: `<namespace>-merkliste` (favourites), `voffice:checkout-context` and
|
|
160
|
+
`voffice:booking-return` (payment return). The `merkliste:changed` event.
|
|
161
|
+
- Every URL key and the German sort values (`empfohlen`, `preis-auf`, `preis-ab`, `bewertung`,
|
|
162
|
+
`groesse`, `groesse-auf`, `gaeste`, `schlafzimmer`, `zufall`).
|
|
163
|
+
- The gate markers on the same elements (`data-rental-stats`, `data-result-card`, `data-fav*`,
|
|
164
|
+
`data-img-*`, …), and legacy `.card-images` / `data-img-*` strips still bind.
|
|
165
|
+
- `book()`, `pay()` and the contact `submit()` stay user-initiated only, and the § 7 VVG
|
|
166
|
+
insurance document gate is not configurable.
|
|
167
|
+
- `describeSdkError` and `logSdkFailure`.
|
|
168
|
+
- `fetchInitialAvailability` returns 0.2.0's `childrenAllowed` and `maxPets`. The `StayPicker`'s
|
|
169
|
+
`caps` carry them for the quote, and the checkout's `loadGuestCaps` for its guest editor, so
|
|
170
|
+
both bound the party by the v9 rules.
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
# Migration: 0.2.0 to 1.0.0
|
|
2
|
+
|
|
3
|
+
1.0.0 is not compatible with 0.x. Do the steps in this order, in one change. A site that also
|
|
4
|
+
renders `@v-office/website-components` moves it to 1.0.0 in the same change and follows that
|
|
5
|
+
package's `MIGRATION.md` after this one. A site that has only copied kit code and never imported
|
|
6
|
+
0.x follows the same steps: replace the kit's SDK seam, stores and helpers with the 1.0.0 names
|
|
7
|
+
below. The README (in this package) has every option and default.
|
|
8
|
+
|
|
9
|
+
## 1. Dependencies
|
|
10
|
+
|
|
11
|
+
Pin exact versions and install, so the lockfile matches:
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
pnpm add --save-exact @v-office/website-headless@1.0.0 @v-office/website-sdk@2.28.1
|
|
15
|
+
# with the components:
|
|
16
|
+
pnpm add --save-exact @v-office/website-components@1.0.0
|
|
17
|
+
# so the build reports the contact surface:
|
|
18
|
+
pnpm add --save-exact @v-office/site-manifest@0.3.0
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
- Pin `@v-office/website-sdk` 2.28.1. From 2.28.0, v9 availability states the occupancy, children
|
|
22
|
+
and pets rules the guest selectors apply; never pin below the version a site already has. From
|
|
23
|
+
2.27.0, with site-manifest 0.3.0, the build reports the SDK it
|
|
24
|
+
constructed, and the platform keeps the website's public token and SDK options current. Construct
|
|
25
|
+
the SDK from one setup only (`createSdk` and `shareSdk` with the same `SdkSetup` count as one).
|
|
26
|
+
`sdkMode: "all-rentals-at-once"` beside it counts as a second SDK on the same token: the token
|
|
27
|
+
stays current, the stored SDK options are kept. A second setup with another token corrects neither.
|
|
28
|
+
- The peer range is `@v-office/website-sdk >=2.25.0 <3`. 2.25.0 fixes the v9 Hub `/Setup` answer
|
|
29
|
+
without configured add-ons; delete a site's `fetch` patch for `/api/v1/Setup` in this change,
|
|
30
|
+
never earlier.
|
|
31
|
+
- `maplibre-gl` is not a dependency of this package. A site that shows a map pins `maplibre-gl`
|
|
32
|
+
5.x itself.
|
|
33
|
+
|
|
34
|
+
## 2. The SDK setup
|
|
35
|
+
|
|
36
|
+
Write the SDK input once, in `src/data/sdk.ts`, and import it everywhere. Spell out every env
|
|
37
|
+
name: Vite inlines only the `import.meta.env.NAME` it sees, so `import.meta.env` passed whole can
|
|
38
|
+
arrive without them in a build.
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import { sdkConfigFromEnv, type SdkSetup } from "@v-office/website-headless";
|
|
42
|
+
|
|
43
|
+
export const SDK: SdkSetup = {
|
|
44
|
+
config: sdkConfigFromEnv({
|
|
45
|
+
CMS_BACKEND: import.meta.env.CMS_BACKEND,
|
|
46
|
+
CMS_STAGE: import.meta.env.CMS_STAGE,
|
|
47
|
+
HUB_API_KEY: import.meta.env.HUB_API_KEY,
|
|
48
|
+
HUB_GRAPHQL_URL: import.meta.env.HUB_GRAPHQL_URL,
|
|
49
|
+
HUB_V0_API_BASE_URL: import.meta.env.HUB_V0_API_BASE_URL,
|
|
50
|
+
HUB_V1_API_BASE_URL: import.meta.env.HUB_V1_API_BASE_URL,
|
|
51
|
+
IMAGE_PROXY_BASE_URL: import.meta.env.IMAGE_PROXY_BASE_URL,
|
|
52
|
+
VOFFICE_LOCAL_DEV_ACCESS_TOKEN: import.meta.env.VOFFICE_LOCAL_DEV_ACCESS_TOKEN,
|
|
53
|
+
VOFFICE_API_ENDPOINT: import.meta.env.VOFFICE_API_ENDPOINT,
|
|
54
|
+
VOFFICE_SEARCH_ENDPOINT: import.meta.env.VOFFICE_SEARCH_ENDPOINT,
|
|
55
|
+
VOFFICE_IMAGE_BASE_URL: import.meta.env.VOFFICE_IMAGE_BASE_URL,
|
|
56
|
+
}),
|
|
57
|
+
// Only the options the site passed to the SDK before (translationOverrides,
|
|
58
|
+
// customAttributes, rentalHighlightPrioritization, …): the setup is serialized into every island.
|
|
59
|
+
options: {},
|
|
60
|
+
};
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
- **The env.** `CMS_BACKEND` must be `v9` or `v10`; there is no default any more (kit code read an
|
|
64
|
+
unset value as v9). The token is `HUB_API_KEY` on v9 and `VOFFICE_LOCAL_DEV_ACCESS_TOKEN` on
|
|
65
|
+
v10. Set either `CMS_STAGE` (`development` or `production`, the platform's endpoints) or every
|
|
66
|
+
URL variable of the backend (v9: `HUB_GRAPHQL_URL`, `HUB_V0_API_BASE_URL`,
|
|
67
|
+
`HUB_V1_API_BASE_URL`, `IMAGE_PROXY_BASE_URL`; v10: `VOFFICE_API_ENDPOINT`,
|
|
68
|
+
`VOFFICE_SEARCH_ENDPOINT`, `VOFFICE_IMAGE_BASE_URL`), never both. Do not rename a variable: the
|
|
69
|
+
platform's builder reads `HUB_API_KEY`. A missing or conflicting variable fails the build with an
|
|
70
|
+
`SdkEnvError` that names the variables.
|
|
71
|
+
- **Build time** (Astro frontmatter, loaders, scripts): one instance for every loader, disposed
|
|
72
|
+
after the last one.
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
const sdk = createSdk(SDK);
|
|
76
|
+
try {
|
|
77
|
+
const rentals = await loadRentalCatalogue(sdk, { locale: "de-DE" });
|
|
78
|
+
// … every other loader takes the same sdk
|
|
79
|
+
} finally {
|
|
80
|
+
await sdk.dispose();
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
- **Browser:** `const shared = shareSdk(SDK)` (or `await loadSharedSdk(SDK)` where the only SDK use
|
|
85
|
+
is a submit), pass `shared.sdk` to the stores, call `shared.release()` when the UI goes. Every
|
|
86
|
+
surface given the same `SDK` shares one instance and one rate limit; a different setup is a
|
|
87
|
+
second instance with its own 1 request/second queue.
|
|
88
|
+
- Replace `createSdk(config, options)` with `createSdk({ config, options })`, and every
|
|
89
|
+
`sdkConfigFor(…)` built per surface with the one `SDK` constant. Delete the site's own SDK
|
|
90
|
+
modules (a `voffice.ts`, a `backend.ts` / `backendKind()`, an SDK error helper).
|
|
91
|
+
|
|
92
|
+
## 3. Site constants
|
|
93
|
+
|
|
94
|
+
Write each decision once in `src/data/` and pass the same constant to every place that needs it:
|
|
95
|
+
|
|
96
|
+
| constant | type | what it decides |
|
|
97
|
+
| ----------------- | ----------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
|
|
98
|
+
| `SDK` | `SdkSetup` | the SDK input (step 2) |
|
|
99
|
+
| `SEARCH_CONTRACT` | `SearchContract` (`backend`, `nightRangeSearch`, `aliases`) | how the search runs on this backend; `nightRangeSearch: false` where the v9 Hub answers the ± pair unpriced |
|
|
100
|
+
| `GUESTS` | `GuestOptions` (`limits`, `childAge`, `defaultAdults`) | the party limits; `limits.pets` 0 = no dogs, 1 = a toggle, more = a stepper |
|
|
101
|
+
| `DAY_PRICES` | `DayPrices` (`"rounded"`, `"exact"`, `"none"`) | prices in calendar cells; a site with a no-price ruling sets `"none"` |
|
|
102
|
+
| `POLICY_CHOICE` | `PolicyChoiceMode` (`"rental-page"`, `"checkout"`) | where the cancellation rate is chosen, one value for quote and checkout |
|
|
103
|
+
| `BOOKABLE` | `BookableFrom` (`from`, `noticeUntil?`, `contact?`) | a site that is not bookable online before a date |
|
|
104
|
+
| `ADDRESS` | `RentalAddressMode` | how much of the address is published; required, there is no default |
|
|
105
|
+
| `FAVORITES_KEY` | `favoritesStorageKey(namespace)` | the Merkliste's storage key |
|
|
106
|
+
|
|
107
|
+
`FAVORITES_KEY` must stay byte-identical to the key the site wrote before (`<namespace>-merkliste`).
|
|
108
|
+
Write the site's exact namespace expression, its fallback included (for example
|
|
109
|
+
`favoritesStorageKey(import.meta.env.PUBLIC_STORAGE_NS || "<the old fallback>")`), and compare the
|
|
110
|
+
key a deployed page writes before and after. A changed key empties every guest's list, and nothing
|
|
111
|
+
migrates it. `favoritesStorageKey` throws on an empty namespace.
|
|
112
|
+
|
|
113
|
+
## 4. What replaces what
|
|
114
|
+
|
|
115
|
+
| 0.x | 1.0.0 |
|
|
116
|
+
| ----------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
|
|
117
|
+
| `createSdk(config, options?)` | `createSdk({ config, options }, mode?)`; browser `shareSdk` / `loadSharedSdk` |
|
|
118
|
+
| `fetchQuote(rentalId, period, occupancy, sdk, locale, voucher?)` | `fetchQuote(sdk, { rentalId, period, occupancy, locale, voucher })`; a booking widget uses `createQuoteFlow` |
|
|
119
|
+
| `fetchInitialAvailability(sdk, rentalId, from, through, locale)`, `fetchEndAvailability(…)` | `fetchInitialAvailability(sdk, input)`, `fetchEndAvailability(sdk, input)`; a calendar uses `createStayPicker` |
|
|
120
|
+
| `submitBooking(quote, contact, sdk, returnPath?)`, `payWithOption(option, sdk)` | `submitBooking(sdk, quote, contact, { returnPath, kind? })`, `payWithOption(sdk, option)` |
|
|
121
|
+
| `runLiveSearch(sdk, search, locale, cursor?, options)` → `SearchPayload` | `runLiveSearch(sdk, search, { locale, backend, rentalHref, … })` → `SearchPage`; a results page uses `createSearchStore` |
|
|
122
|
+
| `DEFAULT_RENTALS_PATH`, `rentalsPath` | `rentalHref`, a required route template such as `"/unterkuenfte/{slug}"` |
|
|
123
|
+
| `CHECKOUT_RETURN_PATH` | `returnPath`, required on `createCheckoutFlow` and `submitBooking` |
|
|
124
|
+
| `SORT_PARAM`, `SortValue`, `SORT_OPTIONS`, `DEFAULT_SORT`, `activeSort`, `resolveSdkSort`, `isSortAvailable`, `isPricedQuery` | `SortKey`, `SORT_URL_VALUE`, `DEFAULT_SORT_OPTIONS`, `sortFromSearch`, `toSdkSort`, `sortResults`; labels are copy |
|
|
125
|
+
| `FilterType`, `FilterDef`, `activeFiltersFromQuery`, `isBooleanFilterActive` | `FilterSpec` + `resolveFilters(getFilters output, spec)`, `readSelection`, `writeSelection`, `activeFilters`, `withoutFilter` |
|
|
126
|
+
| `PETS_PARAM`, `PETS_COUNT_PARAM`, `petsCountInQuery` | `readParty`, `partyPairs` (`petsCount` on search pages, `pets` on rental and checkout pages) |
|
|
127
|
+
| `REGION_PARAM`, `activeRegions`, `scopeToRegions` | `readSearch` / `writeSearch` (`SEARCH_PARAMS.region`) and the search store's scope |
|
|
128
|
+
| `parseNightRange`, `NIGHT_RANGE_*`, `parseParam`, `parseGermanParam`, `stayParams` | `readSearch`, `writeSearch`, `searchHref` with `SEARCH_CONTRACT`; `stayQuery`, `checkoutHref` for rental links |
|
|
129
|
+
| `isoDate(Date)` | `IsoDay` strings: `todayIso`, `addDays`, `nightsBetween`, `formatIsoDate` |
|
|
130
|
+
| `tidyPrice`, `nightlyPrice` | `formatPriceLabel(label, cents)`; no amount is recomputed, so there is no nightly price |
|
|
131
|
+
| `QuoteLine`, `PriceQuote` | `PriceView`, `PriceLine` (`QuoteView.price`) |
|
|
132
|
+
| `fullRefundRule`, `policyFreeCancellationUntil`, `freeCancellationUntil(policies, Date)` | `freeCancellationUntil(policy, arrival)`, `describePolicyOffer`, `offeredPolicies`, `policyStance` |
|
|
133
|
+
| `SearchImage` | `CardImage` |
|
|
134
|
+
| `BookingContactForm` | `BookingContact` |
|
|
135
|
+
| `saveBookingSnapshot` | nothing: `submitBooking` writes it; the return page reads it with `readPaymentReturn` |
|
|
136
|
+
| `VALIDATED_FIELDS`, `fieldError(name, value, …)` | `createGuestForm`; field rules `emailError`, `phoneError`, `postalcodeError`, `streetWarning` |
|
|
137
|
+
| `BUTTON_SOLUTION_PATTERN` | `SUBMIT_WORDING` (per booking kind); `assertCheckoutStructure` checks it |
|
|
138
|
+
| `CountryGroupsLike`, a site's country list | `countryOptions({ locale, featured })` at build time, `dialCode`, `detectCountry(countries, languages)` |
|
|
139
|
+
| `CheckoutFlowState`, `CheckoutFlowActions`, `CheckoutFlowApi`, `CheckoutFlowMessages` | `CheckoutState`, `CheckoutActions`, `CheckoutFlow`, `CheckoutMessages` |
|
|
140
|
+
|
|
141
|
+
Types that kept their name changed shape: `Stay` is `{ start, end, party }` with `IsoDay` strings
|
|
142
|
+
(`stayKeyOf` and `occupancyOf` take it and a `Party`); `SearchResultItem` has `price: StayPrice`,
|
|
143
|
+
`counts` and `periods` instead of `priceLabel`, `pills`, `isNew` and the `guests` / `bedrooms` /
|
|
144
|
+
`area` fields; `AlternativePeriod`, `ActiveFilter` and `CheckoutIssue` are reshaped. Let the type
|
|
145
|
+
checker find every use.
|
|
146
|
+
|
|
147
|
+
## 5. The checkout flow
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
// 0.1.x
|
|
151
|
+
createCheckoutFlow({
|
|
152
|
+
rentalId,
|
|
153
|
+
getSdk,
|
|
154
|
+
stay,
|
|
155
|
+
chosenPolicy,
|
|
156
|
+
filterOfferedPolicies,
|
|
157
|
+
messages,
|
|
158
|
+
locale,
|
|
159
|
+
onRateConfigurationFault,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// 1.0.0
|
|
163
|
+
const flow = createCheckoutFlow(shared.sdk, {
|
|
164
|
+
rentalId,
|
|
165
|
+
locale,
|
|
166
|
+
returnPath: "/buchen/bestaetigung", // the site's route
|
|
167
|
+
handover: readCheckoutHandover(location.search, { allowOption: false }),
|
|
168
|
+
messages, // the five sentences of CheckoutMessages
|
|
169
|
+
policyChoice: POLICY_CHOICE,
|
|
170
|
+
guests: GUESTS,
|
|
171
|
+
onEvent: (detail) => window.dispatchEvent(new CustomEvent(CHECKOUT_EVENT, { detail })),
|
|
172
|
+
});
|
|
173
|
+
flow.start();
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
- The stay comes from the handover and is edited in place (`setDates`, `setParty`); the rate rule
|
|
177
|
+
is internal (`selectPolicy` with `policyChoice: "checkout"`).
|
|
178
|
+
- Call `loadGuestCaps()` when your guest editor opens. `state.bounds` then follow the rental's
|
|
179
|
+
availability caps and, on v9, its children and pets rules, as 0.2.0's guest selector did.
|
|
180
|
+
- Disable the order button while `state.blockers` is not empty and name the first blocker;
|
|
181
|
+
`submit` replaces `bookState`, `payment` replaces `payBusyKey` / `payError`.
|
|
182
|
+
- `pay(option)` takes the option only. `openInsuranceDocument(url)` and
|
|
183
|
+
`confirmInsuranceDocuments(confirmed)` replace `openInsuranceDoc` and
|
|
184
|
+
`confirmInsuranceDocs(productKey, confirmed)`; `clearIssue` and `update` are gone.
|
|
185
|
+
- The flow does not scroll: scroll to the confirmation when `phase.kind` becomes `"booked"`.
|
|
186
|
+
- Run `assertCheckoutStructure` on the rendered checkout in a test. A custom checkout renders
|
|
187
|
+
`CHECKOUT_PARTS` (now with `checkout-consent` and `checkout-insurance-premium`) and
|
|
188
|
+
`BOOKING_KIND_ATTRIBUTE` on the order button.
|
|
189
|
+
|
|
190
|
+
## 6. Enhancement: one `enhance()` call
|
|
191
|
+
|
|
192
|
+
Remove every `initImageCarousels`, `initRentalCarousel`, `initGallery` and `initHistoryOverlay`
|
|
193
|
+
call (and per-section init scripts). Call `enhance` once, in the layout:
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
import { enhance } from "@v-office/website-headless/enhance";
|
|
197
|
+
import { FAVORITES_KEY } from "../data/favorites";
|
|
198
|
+
|
|
199
|
+
// With Astro's ClientRouter: bind on page-load, dispose before the swap.
|
|
200
|
+
let dispose = () => {};
|
|
201
|
+
document.addEventListener("astro:page-load", () => {
|
|
202
|
+
dispose = enhance(document, { favorites: { storageKey: FAVORITES_KEY } });
|
|
203
|
+
});
|
|
204
|
+
document.addEventListener("astro:before-swap", () => dispose());
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
- Without ClientRouter, call `enhance(document, { favorites: { storageKey: FAVORITES_KEY } })`
|
|
208
|
+
once after the page loads. Favourites are bound only with a `storageKey`.
|
|
209
|
+
- Markup binds through `data-part` values (`ENHANCE_PARTS`: `strip`, `strip-track`, `layer`,
|
|
210
|
+
`reveal`, `clamp`, `share`, `favorite`, `rental-gallery`, …). Legacy `.card-images` strips with
|
|
211
|
+
`data-img-*`, `[data-share]` and `[data-fav]` markup still bind; `[data-rental-tabs]` and
|
|
212
|
+
`.rental-track` do not, so move those to `data-part="strip"` markup. A layer is a `<dialog
|
|
213
|
+
data-part="layer">` with an id.
|
|
214
|
+
- The engines write state attributes (`data-active`, `data-state`), never Tailwind classes. Site
|
|
215
|
+
CSS that keyed on a class an init function added keys on the attribute now.
|
|
216
|
+
|
|
217
|
+
## 7. Events
|
|
218
|
+
|
|
219
|
+
- `onRateConfigurationFault` is gone. Pass `onEvent` to `createQuoteFlow` and
|
|
220
|
+
`createCheckoutFlow`; with the components, listen on `window` for `voffice:quote`,
|
|
221
|
+
`voffice:checkout` and `voffice:contact` (`detail.type`, no personal data).
|
|
222
|
+
- The kits' window globals (`__searchStore`, `__favoritesMode`, `__offersMode`,
|
|
223
|
+
`__coordsByRentalId`, `__slideLoader`, …) are gone. A script that read one goes silent without an
|
|
224
|
+
error: move it to the store it needs or to a `voffice:*` event.
|
|
225
|
+
- `merkliste:changed` (`{ id, saved }`) is unchanged.
|
|
226
|
+
|
|
227
|
+
## 8. Delete
|
|
228
|
+
|
|
229
|
+
- The site's SDK seam and its `fetch` patch for the Hub `/Setup` answer (step 1 and 2).
|
|
230
|
+
- Copied kit stores, state machines and helpers that the names in step 4 replace: search, sort,
|
|
231
|
+
filters, regions, dates, prices, quote, checkout, validation, countries, favourites, carousel,
|
|
232
|
+
gallery and overlay code.
|
|
233
|
+
- Route and copy constants the package no longer provides (`CHECKOUT_RETURN_PATH`, German sort
|
|
234
|
+
labels): routes go into the site's props, copy into the UI.
|
|
235
|
+
|
|
236
|
+
## 9. Check
|
|
237
|
+
|
|
238
|
+
Run the site's tests, `astro check` (0 errors) and a build. Never press the final booking, payment
|
|
239
|
+
or contact submit against a real PMS to test: they create a real reservation, start a real payment
|
|
240
|
+
and send a real request.
|
|
241
|
+
|
|
242
|
+
## What stays the same
|
|
243
|
+
|
|
244
|
+
- Storage keys: `<namespace>-merkliste`, `voffice:checkout-context`, `voffice:booking-return`.
|
|
245
|
+
- Every URL key (`start`, `end`, `adults`, `children`, `childrenAges`, `petsCount`, `pets`,
|
|
246
|
+
`region`, `sort`, `policy`, `kind`, `offer`, …) and the German sort values (`empfohlen`,
|
|
247
|
+
`preis-auf`, …), so existing links, bookmarks and mails keep working.
|
|
248
|
+
- The gate markers (`data-rental-stats`, `data-result-card`, `data-fav*`, `data-img-*`, …) on the
|
|
249
|
+
same elements.
|
|
250
|
+
- `book()`, `pay()` and the contact `submit()` stay user-initiated only; the § 7 VVG insurance
|
|
251
|
+
document gate cannot be switched off.
|
|
252
|
+
- `describeSdkError` and `logSdkFailure`.
|