efficient-web-builder 0.0.0 → 0.7.1
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/CHANGELOG.md +230 -0
- package/LICENSE +10 -0
- package/README.md +230 -2
- package/dist/chunks/EditorPreviewCheckout-NMJHO2WR.js +4 -0
- package/dist/chunks/PreviewFooter-E3AVT663.js +1 -0
- package/dist/chunks/SectionEditorShell-CNJJULHO.js +1 -0
- package/dist/chunks/SiteEditorProvider-4NZYBEVE.js +1 -0
- package/dist/chunks/StorefrontEditorChrome-OF6DKLWG.js +1 -0
- package/dist/chunks/chunk-6C5FL63W.js +2 -0
- package/dist/chunks/chunk-A2QNXDQ6.js +11 -0
- package/dist/chunks/chunk-G3X5WFSD.js +1 -0
- package/dist/chunks/chunk-INA2LIFW.js +230 -0
- package/dist/chunks/chunk-JSOIOPWC.js +1 -0
- package/dist/chunks/chunk-NTGUUW2T.js +1 -0
- package/dist/chunks/chunk-PAXN7G3Y.js +1 -0
- package/dist/chunks/chunk-QVG6K5WY.js +15 -0
- package/dist/chunks/chunk-QVG6K5WY.js.LEGAL.txt +27 -0
- package/dist/chunks/chunk-UBYBQ3KR.js +1 -0
- package/dist/chunks/chunk-V2G3QFLM.js +143 -0
- package/dist/chunks/chunk-V2G3QFLM.js.LEGAL.txt +4 -0
- package/dist/chunks/chunk-Y6SLVHK3.js +1 -0
- package/dist/chunks/chunk-YMLSAFSM.js +1 -0
- package/dist/chunks/fitment-data-6OYGRNMJ.js +1 -0
- package/dist/chunks/gallery-builds-data-SWA6XKFF.js +1 -0
- package/dist/chunks/headers-3CAQXQEY.js +1 -0
- package/dist/chunks/render-client-O7623TWA.js +1 -0
- package/dist/contract.d.ts +48 -0
- package/dist/contract.js +1 -0
- package/dist/editor.css +1 -0
- package/dist/index.d.ts +541 -0
- package/dist/index.js +10844 -0
- package/package.json +47 -3
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.7.1 — 2026-10-06
|
|
4
|
+
|
|
5
|
+
- **The Fitment Finder and the gallery builds load in a host's editor.** Their
|
|
6
|
+
calls to the website's own public routes (`/api/v1/fitment/*`,
|
|
7
|
+
`/api/v1/gallery-builds`) carried an `X-Portal-Audience` header. Those routes
|
|
8
|
+
answer a cross-origin caller with open CORS that allows only `Accept` and
|
|
9
|
+
`Content-Type`, so the browser's preflight refused the call: the Fitment
|
|
10
|
+
Finder showed "Vehicle list unavailable" and the gallery came back empty.
|
|
11
|
+
These calls now send no audience; every other call is unchanged.
|
|
12
|
+
|
|
13
|
+
- **The engine peer is `efficient-easel ^0.99.2`** (it was `^0.99.0`). This
|
|
14
|
+
package now imports the engine by its Easel names (`createEasel`,
|
|
15
|
+
`useEasel`, `EaselProvider`, `efficient-easel/easel`), which 0.99.2 added.
|
|
16
|
+
The engine still exports the old names, so a host's own code does not have
|
|
17
|
+
to change; a host only needs 0.99.2 or later installed. Nothing an operator
|
|
18
|
+
sees changes.
|
|
19
|
+
|
|
20
|
+
- **Grid cells fill their height in the canvas.** A block in a Grid cell now
|
|
21
|
+
stretches to the cell's height in the editor, as it does on the published
|
|
22
|
+
page. The canvas sheet (built from the storefront's `app/globals.css`) makes
|
|
23
|
+
the editor's drag wrapper in a Grid a one-column grid, so the block inside it
|
|
24
|
+
stretches. Published pages are unchanged.
|
|
25
|
+
- **Box rows fill their height in the canvas.** The same fix for a Box laid out
|
|
26
|
+
in one row and a Slide box set to One row: when the row stretches (Align
|
|
27
|
+
Stretch, or not set), the drag wrapper is a one-column grid, so the block
|
|
28
|
+
inside it is the row's height as on the published page. Start, Center and End
|
|
29
|
+
rows and Stacked boxes are unchanged. Published pages are unchanged.
|
|
30
|
+
|
|
31
|
+
- **The editor opens a page with its live prices.** Under the EFFICIENT
|
|
32
|
+
backend, `pages.load` by slug now only finds the page, then reads it by id.
|
|
33
|
+
Only that read re-prices the wheel copy a linked Product Grid or Product
|
|
34
|
+
Hero keeps in the page, the same way the published page does. Before, a
|
|
35
|
+
stale copy (USD 0) drew "Price on request" on cards the live page priced.
|
|
36
|
+
An embedder's own backend is not affected.
|
|
37
|
+
|
|
38
|
+
- **The canvas gets the live page's head.** The editor's canvas frame now runs
|
|
39
|
+
the same fit-words boot the published `<body>` runs (`lib/fit-words.ts`), so
|
|
40
|
+
a heading word wider than its box shrinks to fit instead of breaking inside
|
|
41
|
+
the word ("THE UNPAVE / D", "Monoblo / ck"). It runs in the frame's own
|
|
42
|
+
document; the embedder's page is never touched.
|
|
43
|
+
- **The canvas fonts fall back as the live page's do.** Each `--font-*`
|
|
44
|
+
variable is bound to next/font's stack (`'Family', 'Family Fallback'`) and
|
|
45
|
+
the canvas sheet declares next/font's metric-matched `"<Family> Fallback"`
|
|
46
|
+
faces, so a glyph outside a web font's subset (→, ″) and text drawn before
|
|
47
|
+
the web font arrives take the widths the live page gives them. The faces are
|
|
48
|
+
local fonts: an editor over an embedder's own backend (no Google Fonts sheet)
|
|
49
|
+
gets them too.
|
|
50
|
+
- **The Mobile canvas is 390px wide** (it was 388: the device frame's 1px
|
|
51
|
+
border sat inside its width), and Tablet is 800 (was 798). The device edge
|
|
52
|
+
is now an outline that takes no width.
|
|
53
|
+
- **An empty slot no longer covers the picture behind it.** The editor sheet
|
|
54
|
+
(`dist/editor.css`) no longer paints its own solid fill on efficient-easel's
|
|
55
|
+
empty-slot cell, so the slot looks as easel 0.99.1 draws it: nothing at
|
|
56
|
+
rest, so a hero's picture or video shows; the outline and "+ Add block" on
|
|
57
|
+
hover or focus. Published pages are unchanged.
|
|
58
|
+
|
|
59
|
+
## 0.7.0 — 2026-10-05
|
|
60
|
+
|
|
61
|
+
- **Renamed: `efficient-web` is now `efficient-web-builder`.** 0.7.0 is the
|
|
62
|
+
first release under this name, and `efficient-web` stops at 0.3.0. 0.4.0,
|
|
63
|
+
0.5.0 and 0.6.0 below were never published under either name, so an embedder
|
|
64
|
+
moving from `efficient-web` 0.3.0 takes all four sets of changes together.
|
|
65
|
+
- **To migrate:** change the dependency name (`"efficient-web": "^0.3.0"`
|
|
66
|
+
becomes `"efficient-web-builder": "^0.7.0"`) and the import specifiers:
|
|
67
|
+
`efficient-web` becomes `efficient-web-builder`, and
|
|
68
|
+
`efficient-web/editor.css` becomes `efficient-web-builder/editor.css`. An
|
|
69
|
+
install that lists the package by name (pnpm's `minimumReleaseAgeExclude`)
|
|
70
|
+
renames that entry too.
|
|
71
|
+
- The messages the package logs or throws now open with
|
|
72
|
+
`efficient-web-builder:` instead of `efficient-web:`.
|
|
73
|
+
- **A host chooses which blocks its editor offers.** `WebsiteEditor` and
|
|
74
|
+
`WebsiteSectionEditor` take an optional `blocks`: the `sets` offered
|
|
75
|
+
(`general`, `commerce`, `service`), an `allow` and a `deny` list, the host's
|
|
76
|
+
own blocks (`host`, each an `efficient-easel/blocks` `ComponentConfig`), the
|
|
77
|
+
Add-panel group each stands in (`categories`), and live `metadata` every
|
|
78
|
+
block receives as `puck.metadata`. See the README.
|
|
79
|
+
- **Without `blocks` nothing changes.** The editor is given the same block
|
|
80
|
+
registry, the same object, and offers every block it has. An embedder that
|
|
81
|
+
passes nothing compiles and behaves as in 0.6.0.
|
|
82
|
+
- **Every block has a set.** `general` blocks draw from their own props and
|
|
83
|
+
what the host hands them; `commerce` blocks are the shop; `service` blocks
|
|
84
|
+
need one of the EFFICIENT backend's site services (forms, bookings, reviews,
|
|
85
|
+
the newsletter, the blog, menus). 105 blocks: 53 general, 27 commerce, 25
|
|
86
|
+
service.
|
|
87
|
+
- **Offered, not registered.** A block a host leaves out is in no Add-panel
|
|
88
|
+
group, no slot's menu and no drop target, and no section is seeded with it. A
|
|
89
|
+
page that already holds one still draws it and it can be deleted.
|
|
90
|
+
- **A section is offered only with the blocks it is built from.** A Hero is not
|
|
91
|
+
offered where its slides are not; a section that is merely wrapped in a Box
|
|
92
|
+
when it is added lands whole instead.
|
|
93
|
+
- **With a set left out, the controls that need it are not drawn**: a link has
|
|
94
|
+
no "Blog post" or "Product" target, a section's optional "Blog post source"
|
|
95
|
+
is hidden, the Form picker is inert, and the pinned Header and Footer are
|
|
96
|
+
not seeded.
|
|
97
|
+
- **`blocks.metadata` is live.** Change the object and the blocks in the canvas
|
|
98
|
+
redraw in place; it is never written to the page or the undo history. The
|
|
99
|
+
rest of `blocks` is read once, when the editor mounts.
|
|
100
|
+
- **A host's own block gets none of the rows the editor adds to its own
|
|
101
|
+
sections** (padding, width, anchor, theme, animation). It is drawn by its own
|
|
102
|
+
`render`, edited with the fields it declares, added from the Add panel, and
|
|
103
|
+
hidden by the Layers tree's eye.
|
|
104
|
+
- **New entry: `efficient-web-builder/contract`.** One small file with no imports, for
|
|
105
|
+
a host's server: `BLOCK_SETS`, `GENERAL_BLOCKS`, `COMMERCE_BLOCKS`,
|
|
106
|
+
`SERVICE_BLOCKS`, `blockSetOf(type)` and `blockTypesIn(layout)` — every block
|
|
107
|
+
type a saved page uses, including inside slots. Hiding a block in the editor
|
|
108
|
+
is not a security boundary; checking the saved page is.
|
|
109
|
+
- New types: `WebsiteEditorBlocks`, `WebsiteBlockCategory`, `WebsiteBlockSet`.
|
|
110
|
+
`ComponentConfig` is the engine's (`efficient-easel/blocks`).
|
|
111
|
+
|
|
112
|
+
## 0.6.0 — 2026-10-05
|
|
113
|
+
|
|
114
|
+
- **The engine peer is `efficient-easel`.** The engine is called Efficient
|
|
115
|
+
Easel, and its package was renamed from `efficient-editor` at 0.96.0. This
|
|
116
|
+
package now imports `efficient-easel/*` and asks for `efficient-easel`
|
|
117
|
+
`^0.96.0` in place of `efficient-editor` `^0.94.1`. A host moves the two
|
|
118
|
+
together: install `efficient-easel` and import its sheets from
|
|
119
|
+
`efficient-easel/*.css`.
|
|
120
|
+
- The engine's sheets inside `efficient-web/editor.css` are 0.96.0's. That
|
|
121
|
+
release is 0.95.0 under the new name: no class, variable or rendered byte
|
|
122
|
+
differs, and the `es-` classes keep their names.
|
|
123
|
+
|
|
124
|
+
## 0.5.0 — 2026-10-04
|
|
125
|
+
|
|
126
|
+
- **Built on efficient-editor 0.94.1.** The peer range moves from `^0.93.0` to
|
|
127
|
+
`^0.94.1`, and the engine's sheets inside `efficient-web/editor.css` are
|
|
128
|
+
0.94.1's. Install the two together: the formatting bar above a copy box is
|
|
129
|
+
drawn by the engine and styled by this package's sheet, so 0.94's bar on
|
|
130
|
+
0.93's sheet would not match.
|
|
131
|
+
- The formatting bar's buttons take the engine's 0.94 look: 32px, 6px corners,
|
|
132
|
+
and a pressed button that stays tinted under the pointer. Their tooltips are
|
|
133
|
+
the engine's: Strong, Italic, Strikethrough and Emphasis, each with the mark
|
|
134
|
+
it writes.
|
|
135
|
+
- **Two word styles are renamed on the bar: Emphasis is now Highlight, and
|
|
136
|
+
Strong is now Bold**, because the engine's own buttons took those two words.
|
|
137
|
+
The stored keys (`highlight`, `strong`) and the marks (`@@`, `++`) do not move.
|
|
138
|
+
|
|
139
|
+
## 0.4.0 — 2026-10-04
|
|
140
|
+
|
|
141
|
+
- **The editor can run on a backend that is not EFFICIENT's.** `WebsiteEditor`
|
|
142
|
+
and `WebsiteSectionEditor` take an optional `backend`: the embedder's own
|
|
143
|
+
implementation of `WebsiteBackend` — pages (list, load, save, version,
|
|
144
|
+
publish, create, remove), the edit lock, the site record, media, section
|
|
145
|
+
templates, website templates, the Preview link. `pages`, `site.get` and
|
|
146
|
+
`media.resolve` are required; every other group or method is optional, and
|
|
147
|
+
the editor draws no control for one that is missing. See the README and
|
|
148
|
+
`doc/adr/0002`.
|
|
149
|
+
- **Without `backend` nothing changes.** It is `createEfficientBackend()`:
|
|
150
|
+
`/api/storefront/*`, authorised by the token the host delivers with
|
|
151
|
+
`storefront-editor:auth`, with `auth-expired` on a 401 — as in 0.3.0. An
|
|
152
|
+
embedder that passes nothing compiles and behaves as before.
|
|
153
|
+
- **Failures are kinds, not HTTP bodies.** A backend rejects with
|
|
154
|
+
`WebsiteBackendError` (`unauthorized`, `changed-elsewhere`, `lock-taken`,
|
|
155
|
+
`not-found`, `invalid`, `unavailable`); a refused save carries who saved, a
|
|
156
|
+
taken page who holds it.
|
|
157
|
+
- **`PageBundle`** — a page is saved as `{ layout, translations?, seo? }`.
|
|
158
|
+
0.4.0 edits `layout`; `translations` and `seo` are reserved for per-language
|
|
159
|
+
content, so a host's storage is shaped once.
|
|
160
|
+
- **Under an embedder's backend the package makes no request of its own**: no
|
|
161
|
+
token is waited for (`ready` is announced once), its own HTTP client is
|
|
162
|
+
closed, the canvas loads no remote font sheet, and stored media is resolved
|
|
163
|
+
by `backend.media.resolve`.
|
|
164
|
+
- **One session per mounted editor.** The token, the website being edited and
|
|
165
|
+
the editor's caches belong to the mount, so a re-mount for another website
|
|
166
|
+
starts from nothing (one editor at a time is still the rule).
|
|
167
|
+
- New exports: `createEfficientBackend`, `WebsiteBackendError`, and the types
|
|
168
|
+
`WebsiteBackend`, `WebsitePages`, `WebsiteEditLock`, `WebsiteSite`,
|
|
169
|
+
`WebsiteMedia`, `WebsiteSectionTemplates`, `WebsiteTemplates`,
|
|
170
|
+
`WebsitePreview`, `PageBundle`, `PageDocument`, `PageLayout`, `PageRef`,
|
|
171
|
+
`SiteScope`, `NewPageInput`, `SaveExpectation`, `EditorPageRow`, `SiteRecord`,
|
|
172
|
+
`SiteRecordPatch`, `SaveConflict`, `LockHolder`, `SectionNode`,
|
|
173
|
+
`SectionTemplate`, `SectionTemplateRecord`, `WebsiteBackendErrorKind`,
|
|
174
|
+
`EfficientBackendOptions`.
|
|
175
|
+
- Not behind the interface yet (unchanged under EFFICIENT's backend, absent
|
|
176
|
+
under another): site styles, the pinned header and footer, live chat, social
|
|
177
|
+
links, menus, blog, forms, the catalog, the sitemap panel and "duplicate to
|
|
178
|
+
page". A Form section's picker (0.3.0) is inert under another backend: no
|
|
179
|
+
list, no "New form", no `storefront-editor:open-form`.
|
|
180
|
+
|
|
181
|
+
## 0.3.0 — 2026-10-04
|
|
182
|
+
|
|
183
|
+
- **Editing a form waits for nothing.** A Form picker's "Edit form" sends
|
|
184
|
+
`storefront-editor:open-form` with `mode: "edit"` and **no `requestId`**, and
|
|
185
|
+
does not wait for an answer: the embedder's form builder is a window the
|
|
186
|
+
merchant works in beside the editor, not a question the picker asked. The
|
|
187
|
+
picker's buttons are no longer disabled while the builder is open.
|
|
188
|
+
`requestId` is now optional on the message type.
|
|
189
|
+
- **A block follows a renamed form by id.** When a forms re-read no longer
|
|
190
|
+
explains a block's stored slug but still holds the form that slug last
|
|
191
|
+
resolved to, the block is re-pointed at the form's new slug. This replaces
|
|
192
|
+
following the slug on the reply to an edit request, and also covers a rename
|
|
193
|
+
made outside the editor. A deleted form is never replaced by another.
|
|
194
|
+
- **"New form" still asks and waits** (`mode: "create"`, with a `requestId`,
|
|
195
|
+
answered by `form-saved` / `form-closed`). An embedder is expected to answer
|
|
196
|
+
it quickly, for example with a popup that names and creates the form; one
|
|
197
|
+
that answers with its whole form builder still works, and the fifteen-minute
|
|
198
|
+
reply backstop stays for it.
|
|
199
|
+
- An embedder that answered an edit request with `form-closed` keeps working:
|
|
200
|
+
the reply is for a request nobody is waiting on, and is ignored.
|
|
201
|
+
|
|
202
|
+
## 0.2.0 — 2026-10-04
|
|
203
|
+
|
|
204
|
+
- **The media library is a panel of the editor.** `WebsiteEditor` and
|
|
205
|
+
`WebsiteSectionEditor` take an optional `mediaPanel`: the embedder's media
|
|
206
|
+
library, as a component the editor draws in its own left panel. The rail's
|
|
207
|
+
Media button opens it as a view beside Pages, Layers and Site styles, and
|
|
208
|
+
every image field opens it in pick mode ("Choose image · Background image"),
|
|
209
|
+
so the field's choice is made in the editor instead of in a second window.
|
|
210
|
+
New types: `MediaPanelComponent`, `MediaPanelProps`, `MediaPickRequest`.
|
|
211
|
+
- Without `mediaPanel` nothing changes: fields ask the host to open its picker
|
|
212
|
+
window with `storefront-editor:open-media-picker`, as in 0.1.0.
|
|
213
|
+
- **One head for every side panel, on both sides.** Pages, Layers, Site styles,
|
|
214
|
+
the Media library, Add, Choose image and the right-hand settings panel all
|
|
215
|
+
wear the engine's `InspectorHead`: a glyph, the name, what it is about, the
|
|
216
|
+
panel's own buttons, then **Hide panel**, now on the right panel too. No ✕ in a panel head any
|
|
217
|
+
more: Add leaves with **Done**, choosing an image with **Cancel**, and Layers'
|
|
218
|
+
collapse-all glyph no longer reads as a second ✕.
|
|
219
|
+
- **No panel toggles in the top bar.** They duplicated the heads' Hide panel
|
|
220
|
+
button. A hidden panel leaves a 40px strip at its edge with **Show panel**
|
|
221
|
+
(the same picture), the way back for both sides.
|
|
222
|
+
|
|
223
|
+
## 0.1.0 — 2026-10-03
|
|
224
|
+
|
|
225
|
+
- **First release.** The storefront website editor (page and template editor,
|
|
226
|
+
standalone section editor) for an app that mounts it in its own page: an
|
|
227
|
+
in-process host (`createEditorHost`) in place of the shop's postMessage
|
|
228
|
+
bridge, the shop's sections with Next.js replaced at the module boundary, and
|
|
229
|
+
the canvas styled from the storefront's own compiled stylesheet inside its
|
|
230
|
+
frame.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Copyright (c) 2026 Efficient Pty Ltd. All rights reserved.
|
|
2
|
+
|
|
3
|
+
This software and its documentation are proprietary to Efficient Pty Ltd and
|
|
4
|
+
are confidential. No licence is granted to use, copy, modify, merge, publish,
|
|
5
|
+
distribute, sublicense or sell this software, in whole or in part, except
|
|
6
|
+
under a written agreement with Efficient Pty Ltd.
|
|
7
|
+
|
|
8
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
9
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
10
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
package/README.md
CHANGED
|
@@ -1,4 +1,232 @@
|
|
|
1
1
|
# efficient-web-builder
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
The EFFICIENT **website builder** — the website editor (the page, template and
|
|
4
|
+
section editors) and every section the storefront renders — for an app that
|
|
5
|
+
edits a website **inside its own page**. The admin portal's Storefront editor
|
|
6
|
+
windows mount it.
|
|
7
|
+
|
|
8
|
+
Published as `efficient-web` up to 0.3.0; 0.7.0 is the first release under
|
|
9
|
+
this name (`CHANGELOG.md` says how to migrate).
|
|
10
|
+
|
|
11
|
+
It is not a second copy of anything: `build.mjs` compiles the shop's own
|
|
12
|
+
`components/` and `lib/` from this repository, starting at `src/index.tsx`.
|
|
13
|
+
The shop itself never installs it; its pages import the same source directly.
|
|
14
|
+
|
|
15
|
+
## Use
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
import "efficient-web-builder/editor.css";
|
|
19
|
+
import { WebsiteEditor, createEditorHost } from "efficient-web-builder";
|
|
20
|
+
|
|
21
|
+
const { host, deliver } = createEditorHost({
|
|
22
|
+
onMessage: (m) => { /* toasts, open the media picker, the form builder … */ },
|
|
23
|
+
address: { search: () => "", show: () => {}, load: retargetWindow, reload: remount },
|
|
24
|
+
});
|
|
25
|
+
// Grant a token when the editor says it is ready, and again on auth-expired:
|
|
26
|
+
// deliver({ type: "storefront-editor:auth", token, siteId, can })
|
|
27
|
+
|
|
28
|
+
<WebsiteEditor host={host} target={{ kind: "page", slug: "homepage", siteId }} site={{ slug, origin }} />;
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
- **Messages** are the `storefront-editor:*` protocol the editor once spoke
|
|
32
|
+
over postMessage from the shop's `/edit` routes, as function calls — no
|
|
33
|
+
origin, no iframe.
|
|
34
|
+
`EditorToHostMessage` / `HostToEditorMessage` type every one.
|
|
35
|
+
- **`site`** — the website's `slug` (public catalog reads name it with
|
|
36
|
+
`?site=`, because the embedder's own host names no website) and its public
|
|
37
|
+
`origin` (the routes only the storefront serves, and the Preview link).
|
|
38
|
+
Re-mount the editor with a `key` to move to another website.
|
|
39
|
+
- **One editor mounted at a time.** Parts of the editor keep the open website
|
|
40
|
+
and page in module state; route every target into one window.
|
|
41
|
+
- **Styles.** `efficient-web-builder/editor.css` is the chrome, scoped under
|
|
42
|
+
`.sf-editor`; the engine's sheets are imported from the embedder's own
|
|
43
|
+
`efficient-easel`. The canvas is styled from inside its own frame (the
|
|
44
|
+
storefront's compiled `app/globals.css`), so neither page's styles reach the
|
|
45
|
+
other.
|
|
46
|
+
- **Peers:** `react`/`react-dom` 19, `efficient-easel`, `efficient-shell`.
|
|
47
|
+
|
|
48
|
+
## A backend of your own (0.4.0)
|
|
49
|
+
|
|
50
|
+
The editor reaches its server only through a `WebsiteBackend`
|
|
51
|
+
(`doc/adr/0002`). Leave `backend` out and it is EFFICIENT's — `/api/storefront/*`,
|
|
52
|
+
authorised by the token you deliver with `storefront-editor:auth`, exactly as
|
|
53
|
+
above. A product with its own backend passes its own:
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
import { WebsiteEditor, WebsiteBackendError, type WebsiteBackend } from "efficient-web-builder";
|
|
57
|
+
|
|
58
|
+
const backend: WebsiteBackend = {
|
|
59
|
+
pages: {
|
|
60
|
+
list: () => api.pages(), // EditorPageRow[]
|
|
61
|
+
load: (ref) => api.page(ref), // { id, layout, version } | null
|
|
62
|
+
save: async (id, doc, expect) => {
|
|
63
|
+
const res = await api.savePage(id, doc.layout, expect.version);
|
|
64
|
+
if (res.conflict) {
|
|
65
|
+
throw new WebsiteBackendError("changed-elsewhere", undefined, {
|
|
66
|
+
conflict: { savedBy: res.by, savedByYou: res.mine, secondsAgo: res.ago },
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
return { version: res.version };
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
site: { get: () => api.site() }, // { name }
|
|
73
|
+
media: { resolve: (ref) => (ref.startsWith("file:") ? cdn(ref) : null) },
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
<WebsiteEditor host={host} target={{ kind: "page", slug: "home" }} site={null} backend={backend} />;
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
- **Required:** `pages` (`list`, `load`, `save`), `site.get`, `media.resolve`.
|
|
80
|
+
**Optional**, each switching its own control on: `pages.version`,
|
|
81
|
+
`pages.publish` (the main button reads Publish, not Save), `pages.create` /
|
|
82
|
+
`pages.remove` (inline in the page list), `lock` (the "somebody else is
|
|
83
|
+
editing" notices and take-over), `site.update` (the Maps key), `preview`
|
|
84
|
+
(the Preview link), `sectionTemplates`, `templates`. A missing group is never
|
|
85
|
+
a dead button: the editor does not draw what it cannot do.
|
|
86
|
+
- **Failures are kinds.** Reject with `WebsiteBackendError`: `unauthorized`,
|
|
87
|
+
`changed-elsewhere` (+ `conflict`), `lock-taken` (+ `holder`), `not-found`,
|
|
88
|
+
`invalid`, `unavailable`. The editor never reads a status code or a body.
|
|
89
|
+
- **No token.** How a call is authorised is the backend's business. The editor
|
|
90
|
+
waits for no `auth` message and needs none; it announces `ready` once, and
|
|
91
|
+
when a save, a publish or a lock claim is refused `unauthorized` it tells the
|
|
92
|
+
host `auth-expired` and lets the autosave try again.
|
|
93
|
+
- **No request of its own.** Under your backend the package's own HTTP client is
|
|
94
|
+
closed and the canvas loads no font sheet from Google.
|
|
95
|
+
- **`PageBundle`** is `{ layout, translations?, seo? }`. 0.4.0 reads and writes
|
|
96
|
+
`layout` only; store the bundle anyway — `translations` and `seo` are where
|
|
97
|
+
per-language content will live, and an absent member means "leave it".
|
|
98
|
+
- **Not behind the interface yet** (they work under EFFICIENT's backend, and are
|
|
99
|
+
absent under yours): site styles, the pinned header and footer, live chat,
|
|
100
|
+
social links, menus, blog, forms, the catalog, the sitemap panel, "duplicate
|
|
101
|
+
to page". Block settings that list such data (link targets other than a
|
|
102
|
+
custom URL, product / blog sources) list nothing, and a Form section's picker
|
|
103
|
+
is inert: it offers no list, no "New form" and sends no `open-form`.
|
|
104
|
+
- `backend` is read once, when the editor mounts — re-mount (a `key`) to change it.
|
|
105
|
+
|
|
106
|
+
## Which blocks the editor offers (0.7.0)
|
|
107
|
+
|
|
108
|
+
Every block is in one of three **sets**:
|
|
109
|
+
|
|
110
|
+
| Set | What it is |
|
|
111
|
+
|---|---|
|
|
112
|
+
| `general` | Draws from its own props and what the host hands it (media, the site's logo, its Maps key). Any host may offer it. |
|
|
113
|
+
| `commerce` | The shop: products, the catalogue, the cart, the checkout, the pinned Header. |
|
|
114
|
+
| `service` | Needs one of the EFFICIENT backend's site services: forms, bookings, reviews, the newsletter, the blog, menus, the pinned Footer. |
|
|
115
|
+
|
|
116
|
+
Which block is in which set is `GENERAL_BLOCKS`, `COMMERCE_BLOCKS` and
|
|
117
|
+
`SERVICE_BLOCKS` in `efficient-web-builder/contract`.
|
|
118
|
+
|
|
119
|
+
Leave `blocks` out and the editor offers all of them, exactly as before 0.7.0.
|
|
120
|
+
Pass it to choose:
|
|
121
|
+
|
|
122
|
+
```tsx
|
|
123
|
+
import { WebsiteEditor, type WebsiteEditorBlocks } from "efficient-web-builder";
|
|
124
|
+
import type { ComponentConfig } from "efficient-easel/blocks";
|
|
125
|
+
|
|
126
|
+
const Roster: ComponentConfig = {
|
|
127
|
+
label: "Roster",
|
|
128
|
+
fields: { heading: { type: "text", label: "Heading" } },
|
|
129
|
+
defaultProps: { heading: "On tonight" },
|
|
130
|
+
// `puck.metadata` is whatever you hand the editor as `blocks.metadata`.
|
|
131
|
+
render: ({ heading, puck }) => <RosterSection heading={heading} people={puck.metadata.roster} />,
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const blocks: WebsiteEditorBlocks = {
|
|
135
|
+
sets: ["general"], // default: all three
|
|
136
|
+
deny: ["CustomHTML"], // never these
|
|
137
|
+
// allow: [...], // or: only these (after `sets`)
|
|
138
|
+
host: { "acme-roster": Roster }, // your own blocks, by type
|
|
139
|
+
categories: [{ key: "acme", title: "Venue", components: ["acme-roster"], before: "openers" }],
|
|
140
|
+
metadata: { roster }, // live: change it and the canvas redraws
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
<WebsiteEditor host={host} target={target} site={null} backend={backend} blocks={blocks} />;
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
- **Offered, not registered.** A block you leave out is in no group of the Add
|
|
147
|
+
panel, no slot's "Add block" menu and no drop target, and no section is
|
|
148
|
+
seeded with it. It is still registered: a page that already holds one draws
|
|
149
|
+
it, and it can be selected and deleted.
|
|
150
|
+
- **A section is offered only with what it is built from.** Adding a Hero
|
|
151
|
+
writes Hero Slides, and "Brand Text" lays down Boxes, a Heading and a Text —
|
|
152
|
+
so a Hero is not offered where `HeroSlide` is not, and Brand Text is not
|
|
153
|
+
where the Box is not. `allow: ["Hero"]` therefore offers nothing; `allow:
|
|
154
|
+
["Hero", "HeroSlide"]` offers the Hero. To see what a section writes, add it
|
|
155
|
+
and run `blockTypesIn` on the saved layout. A section that is only *wrapped*
|
|
156
|
+
in a Box when it is added (most sections with a heading) is added whole
|
|
157
|
+
instead where the Box or the Section Header is not offered.
|
|
158
|
+
- **With a set left out, what needs it is not drawn.** Without `service`: no
|
|
159
|
+
"Blog post" link target, no "Blog post source" on Feature Banner, Article
|
|
160
|
+
Text and Post Card, an inert Form picker. Without `commerce`: no "Product"
|
|
161
|
+
link target, no catalog seed. The pinned Header and Footer need both.
|
|
162
|
+
- **Your own blocks.** Written as the engine's `ComponentConfig`, with its
|
|
163
|
+
field types (`text`, `textarea`, `number`, `select`, `radio`, `array`,
|
|
164
|
+
`object`, `custom`, `slot`). A type the editor already registers is refused —
|
|
165
|
+
the editor throws when it mounts — so give yours a prefix. A block is added
|
|
166
|
+
from the Add panel, in the group `categories` names; one named in no group is
|
|
167
|
+
registered and offered nowhere, and none is offered inside a Box. A
|
|
168
|
+
`categories` entry whose `key` is an existing group (`content`, `structure`,
|
|
169
|
+
…) adds to the end of that group.
|
|
170
|
+
- **Your blocks get none of the editor's own section rows.** The Padding,
|
|
171
|
+
Width, Height, Anchor, Theme, Corners and Animation rows, the four settings
|
|
172
|
+
tabs and click-to-edit text are added to the editor's own blocks by ~25
|
|
173
|
+
passes that run once, when the package loads, over lists fixed in its source.
|
|
174
|
+
A block that arrives when the editor mounts is not on those lists, so it is
|
|
175
|
+
drawn exactly as its `render` draws it and edited with exactly the fields it
|
|
176
|
+
declares. The one exception is the Layers tree's eye, which hides your block
|
|
177
|
+
as it hides any other (it stores `_hidden: true` on the node's props). Giving
|
|
178
|
+
host blocks the rest needs an `applyBlockPasses(type, block)` the package
|
|
179
|
+
does not have yet: each pass rewritten from a loop over a fixed list into a
|
|
180
|
+
function of one block, run in the same order.
|
|
181
|
+
- **`metadata` is live; the rest is read once.** `sets`, `allow`, `deny`,
|
|
182
|
+
`host` and `categories` are read when the editor mounts, with `backend` —
|
|
183
|
+
re-mount (a `key`) to change them. `metadata` is followed: hand the editor a
|
|
184
|
+
new object and every block in the canvas redraws in place. It is never
|
|
185
|
+
written to the page or to the undo history. Keep the object the same between
|
|
186
|
+
renders while the data is the same (a `useMemo`, a state value): a new object
|
|
187
|
+
each render redraws the canvas each render.
|
|
188
|
+
|
|
189
|
+
### Checking a saved page on your server
|
|
190
|
+
|
|
191
|
+
**Hiding a block in the editor is not a security boundary.** `blocks` decides
|
|
192
|
+
what the editor offers; a page is a JSON document, and whoever can call your
|
|
193
|
+
save endpoint can send one holding any block type. Check it where you store it:
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
import { GENERAL_BLOCKS, blockTypesIn } from "efficient-web-builder/contract";
|
|
197
|
+
|
|
198
|
+
const ALLOWED = new Set([...GENERAL_BLOCKS, "acme-roster"]);
|
|
199
|
+
|
|
200
|
+
function assertAllowed(layout: unknown) {
|
|
201
|
+
const refused = blockTypesIn(layout).filter((type) => !ALLOWED.has(type));
|
|
202
|
+
if (refused.length) throw new Error(`This page holds blocks this site does not allow: ${refused.join(", ")}`);
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
`efficient-web-builder/contract` is one small file that imports nothing — no React, no
|
|
207
|
+
engine, no editor — so a server can load it. It exports `BLOCK_SETS`,
|
|
208
|
+
`GENERAL_BLOCKS`, `COMMERCE_BLOCKS`, `SERVICE_BLOCKS`, `blockSetOf(type)` and
|
|
209
|
+
`blockTypesIn(layout)`.
|
|
210
|
+
|
|
211
|
+
`blockTypesIn` reads a layout by shape: any `{ type, props }` in the page's
|
|
212
|
+
`content`, in a `zones` entry, or in an array under a node's props is a block,
|
|
213
|
+
to any depth. It errs wide — something that merely looks like a block is
|
|
214
|
+
counted — so a check built on it can refuse a page it might have accepted, and
|
|
215
|
+
never the reverse. If `ALLOWED` is narrower than what your editor offers, list
|
|
216
|
+
the blocks your sections are built from as well (see above), or the editor will
|
|
217
|
+
build pages your server refuses.
|
|
218
|
+
|
|
219
|
+
## Release
|
|
220
|
+
|
|
221
|
+
The version here is bumped **by hand** in the pull request that wants the admin
|
|
222
|
+
to see a change (and a `CHANGELOG.md` section added). After it merges:
|
|
223
|
+
|
|
224
|
+
```
|
|
225
|
+
gh release create web-vX.Y.Z --target <merge sha> --notes-file <the CHANGELOG section>
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
`.github/workflows/release-web.yml` builds and publishes it (npm trusted
|
|
229
|
+
publishing). Then bump the admin portal's pin.
|
|
230
|
+
|
|
231
|
+
Closed source: © Efficient Pty Ltd, all rights reserved (`LICENSE`); the
|
|
232
|
+
published files are minified and ship no source maps.
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
"use client";import{a as Yt,b as Qe,c as Bt}from"./chunk-JSOIOPWC.js";import{$b as Xe,Cc as Mt,Ob as Ot,Pb as se,Rb as Lt,Sb as Tt,Tb as jt,Wb as qt,Xb as _t,Yb as we,Zb as Dt,_b as Ft,a as ee,ac as Ut,b as Rt,kd as Kt,md as Ze,nb as At,rd as $t,td as Wt}from"./chunk-A2QNXDQ6.js";import{E as _e,I as fe,P as T,t as It,u as Se,v as B}from"./chunk-PAXN7G3Y.js";import{a as Je,b as ar}from"./chunk-Y6SLVHK3.js";var Ht=Je((ea,zt)=>{"use strict";var or="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED";zt.exports=or});var Xt=Je((ta,Jt)=>{"use strict";var ir=Ht();function Vt(){}function Gt(){}Gt.resetWarningCache=Vt;Jt.exports=function(){function t(a,o,s,l,d,p){if(p!==ir){var u=new Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types");throw u.name="Invariant Violation",u}}t.isRequired=t;function e(){return t}var n={array:t,bigint:t,bool:t,func:t,number:t,object:t,string:t,symbol:t,any:t,arrayOf:e,element:t,elementType:t,instanceOf:e,node:t,objectOf:e,oneOf:e,oneOfType:e,shape:e,exact:e,checkPropTypes:Gt,resetWarningCache:Vt};return n.PropTypes=n,n}});var Qt=Je((aa,Zt)=>{"use strict";Zt.exports=Xt()();var na,ra});import{useEffect as $,useMemo as Be,useRef as le,useState as g}from"react";var w=ar(Qt(),1);import x from"react";function en(t,e){(e==null||e>t.length)&&(e=t.length);for(var n=0,a=Array(e);n<e;n++)a[n]=t[n];return a}function sr(t){if(Array.isArray(t))return t}function on(t,e,n){return(e=mr(e))in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}function cr(t,e){var n=t==null?null:typeof Symbol<"u"&&t[Symbol.iterator]||t["@@iterator"];if(n!=null){var a,o,s,l,d=[],p=!0,u=!1;try{if(s=(n=n.call(t)).next,e!==0)for(;!(p=(a=s.call(n)).done)&&(d.push(a.value),d.length!==e);p=!0);}catch(v){u=!0,o=v}finally{try{if(!p&&n.return!=null&&(l=n.return(),Object(l)!==l))return}finally{if(u)throw o}}return d}}function lr(){throw new TypeError(`Invalid attempt to destructure non-iterable instance.
|
|
2
|
+
In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}function tn(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(t);e&&(a=a.filter(function(o){return Object.getOwnPropertyDescriptor(t,o).enumerable})),n.push.apply(n,a)}return n}function nn(t){for(var e=1;e<arguments.length;e++){var n=arguments[e]!=null?arguments[e]:{};e%2?tn(Object(n),!0).forEach(function(a){on(t,a,n[a])}):Object.getOwnPropertyDescriptors?Object.defineProperties(t,Object.getOwnPropertyDescriptors(n)):tn(Object(n)).forEach(function(a){Object.defineProperty(t,a,Object.getOwnPropertyDescriptor(n,a))})}return t}function ur(t,e){if(t==null)return{};var n,a,o=pr(t,e);if(Object.getOwnPropertySymbols){var s=Object.getOwnPropertySymbols(t);for(a=0;a<s.length;a++)n=s[a],e.indexOf(n)===-1&&{}.propertyIsEnumerable.call(t,n)&&(o[n]=t[n])}return o}function pr(t,e){if(t==null)return{};var n={};for(var a in t)if({}.hasOwnProperty.call(t,a)){if(e.indexOf(a)!==-1)continue;n[a]=t[a]}return n}function sn(t,e){return sr(t)||cr(t,e)||fr(t,e)||lr()}function dr(t,e){if(typeof t!="object"||!t)return t;var n=t[Symbol.toPrimitive];if(n!==void 0){var a=n.call(t,e);if(typeof a!="object")return a;throw new TypeError("@@toPrimitive must return a primitive value.")}return(e==="string"?String:Number)(t)}function mr(t){var e=dr(t,"string");return typeof e=="symbol"?e:e+""}function et(t){"@babel/helpers - typeof";return et=typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?function(e){return typeof e}:function(e){return e&&typeof Symbol=="function"&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},et(t)}function fr(t,e){if(t){if(typeof t=="string")return en(t,e);var n={}.toString.call(t).slice(8,-1);return n==="Object"&&t.constructor&&(n=t.constructor.name),n==="Map"||n==="Set"?Array.from(t):n==="Arguments"||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?en(t,e):void 0}}var j=function(e,n,a){var o=!!a,s=x.useRef(a);x.useEffect(function(){s.current=a},[a]),x.useEffect(function(){if(!o||!e)return function(){};var l=function(){if(s.current)return s.current.apply(s,arguments)};return e.on(n,l),function(){e.off(n,l)}},[o,n,e,s])},tt=function(e){var n=x.useRef(e);return x.useEffect(function(){n.current=e},[e]),n.current},ge=function(e){return e!==null&&et(e)==="object"},gr=function(e){return ge(e)&&typeof e.then=="function"},yr=function(e){return ge(e)&&typeof e.elements=="function"&&typeof e.createToken=="function"&&typeof e.createPaymentMethod=="function"&&typeof e.confirmCardPayment=="function"},rn="[object Object]",hr=function t(e,n){if(!ge(e)||!ge(n))return e===n;var a=Array.isArray(e),o=Array.isArray(n);if(a!==o)return!1;var s=Object.prototype.toString.call(e)===rn,l=Object.prototype.toString.call(n)===rn;if(s!==l)return!1;if(!s&&!a)return e===n;var d=Object.keys(e),p=Object.keys(n);if(d.length!==p.length)return!1;for(var u={},v=0;v<d.length;v+=1)u[d[v]]=!0;for(var b=0;b<p.length;b+=1)u[p[b]]=!0;var m=Object.keys(u);if(m.length!==d.length)return!1;var h=e,A=n,k=function(S){return t(h[S],A[S])};return m.every(k)},cn=function(e,n,a){return ge(e)?Object.keys(e).reduce(function(o,s){var l=!ge(n)||!hr(e[s],n[s]);return a.includes(s)?(l&&console.warn("Unsupported prop change: options.".concat(s," is not a mutable property.")),o):l?nn(nn({},o||{}),{},on({},s,e[s])):o},null):null},ln="Invalid prop `stripe` supplied to `Elements`. We recommend using the `loadStripe` utility from `@stripe/stripe-js`. See https://stripe.com/docs/stripe-js/react#elements-props-stripe for details.",an=function(e){var n=arguments.length>1&&arguments[1]!==void 0?arguments[1]:ln;if(e===null||yr(e))return e;throw new Error(n)},vr=function(e){var n=arguments.length>1&&arguments[1]!==void 0?arguments[1]:ln;if(gr(e))return{tag:"async",stripePromise:Promise.resolve(e).then(function(o){return an(o,n)})};var a=an(e,n);return a===null?{tag:"empty"}:{tag:"sync",stripe:a}},br=function(e){!e||!e._registerWrapper||!e.registerAppInfo||(e._registerWrapper({name:"react-stripe-js",version:"6.7.0"}),e.registerAppInfo({name:"react-stripe-js",version:"6.7.0",url:"https://stripe.com/docs/stripe-js/react"}))},De=x.createContext(null);De.displayName="ElementsContext";var un=function(e,n){if(!e)throw new Error("Could not find Elements context; You need to wrap the part of your app that ".concat(n," in an <Elements> provider."));return e},rt=function(e){var n=e.stripe,a=e.options,o=e.children,s=x.useMemo(function(){return vr(n)},[n]),l=x.useState(function(){return{stripe:s.tag==="sync"?s.stripe:null,elements:s.tag==="sync"?s.stripe.elements(a):null}}),d=sn(l,2),p=d[0],u=d[1];x.useEffect(function(){var m=!0,h=function(k){u(function(C){return C.stripe?C:{stripe:k,elements:k.elements(a)}})};return s.tag==="async"&&!p.stripe?s.stripePromise.then(function(A){A&&m&&h(A)}):s.tag==="sync"&&!p.stripe&&h(s.stripe),function(){m=!1}},[s,p,a]);var v=tt(n);x.useEffect(function(){v!==null&&v!==n&&console.warn("Unsupported prop change on Elements: You cannot change the `stripe` prop after setting it.")},[v,n]);var b=tt(a);return x.useEffect(function(){if(p.elements){var m=cn(a,b,["clientSecret","fonts"]);m&&p.elements.update(m)}},[a,b,p.elements]),x.useEffect(function(){br(p.stripe)},[p.stripe]),x.createElement(De.Provider,{value:p},o)};rt.propTypes={stripe:w.default.any,options:w.default.object};var pn=function(e){var n=x.useContext(De);return un(n,e)},dn=function(){var e=pn("calls useElements()"),n=e.elements;return n},xr=function(e){var n=e.children,a=pn("mounts <ElementsConsumer>");return n(a)};xr.propTypes={children:w.default.func.isRequired};var mn=x.createContext(null);mn.displayName="CheckoutContext";var nt=function(e){var n=x.useContext(mn),a=x.useContext(De);if(n){if(a)throw new Error("You cannot wrap the part of your app that ".concat(e," in both a checkout provider and <Elements> provider."));return n}else return un(a,e)},kr=["mode"],Cr=function(e){return e.charAt(0).toUpperCase()+e.slice(1)},R=function(e,n,a){var o="".concat(Cr(e),"Element"),s=function(u){var v=u.id,b=u.className,m=u.options,h=m===void 0?{}:m,A=u.onBlur,k=u.onFocus,C=u.onReady,S=u.onChange,F=u.onEscape,L=u.onClick,J=u.onLoadError,te=u.onLoaderStart,Ee=u.onNetworksChange,ue=u.onConfirm,Re=u.onCancel,ne=u.onShippingAddressChange,Ie=u.onShippingRateChange,re=u.onSavedPaymentMethodRemove,Ae=u.onSavedPaymentMethodUpdate,ye=u.onAvailablePaymentMethodsChange,ae=nt("mounts <".concat(o,">")),Q="elements"in ae?ae.elements:null,W="checkoutState"in ae?ae.checkoutState:null,H=W?.type==="success"||W?.type==="loading"?W.sdk:null,he=x.useState(null),pe=sn(he,2),E=pe[0],ve=pe[1],_=x.useRef(null),oe=x.useRef(null);j(E,"blur",A),j(E,"focus",k),j(E,"escape",F),j(E,"click",L),j(E,"loaderror",J),j(E,"loaderstart",te),j(E,"networkschange",Ee),j(E,"confirm",ue),j(E,"cancel",Re),j(E,"shippingaddresschange",ne),j(E,"shippingratechange",Ie),j(E,"savedpaymentmethodremove",re),j(E,"savedpaymentmethodupdate",Ae),j(E,"availablepaymentmethodschange",ye),j(E,"change",S);var ie;C&&(e==="expressCheckout"?ie=C:ie=function(){C(E)}),j(E,"ready",ie),x.useLayoutEffect(function(){if(_.current===null&&oe.current!==null&&(Q||H)){var P=null;if(H){var V=H,be=H;switch(e){case"paymentForm":P=be.createForm(h);break;case"payment":P=V.createPaymentElement(h);break;case"address":if("mode"in h){var de=h.mode,xe=ur(h,kr);if(de==="shipping")P=V.createShippingAddressElement(xe);else if(de==="billing")P=V.createBillingAddressElement(xe);else throw new Error("Invalid options.mode. mode must be 'billing' or 'shipping'.")}else throw new Error("You must supply options.mode. mode must be 'billing' or 'shipping'.");break;case"expressCheckout":P=V.createExpressCheckoutElement(h);break;case"currencySelector":P=H.createCurrencySelectorElement();break;case"taxId":P=V.createTaxIdElement(h);break;case"contactDetails":P=V.createContactDetailsElement();break;default:throw new Error("<".concat(o,"> is not supported inside a checkout provider. Use an <Elements> provider instead."))}}else Q&&(P=Q.create(e,h));_.current=P,ve(P),P&&P.mount(oe.current)}},[Q,H,h]);var Y=tt(h);return x.useEffect(function(){if(_.current){var P=cn(h,Y,["paymentRequest"]);P&&"update"in _.current&&_.current.update(P)}},[h,Y]),x.useLayoutEffect(function(){return function(){if(_.current&&typeof _.current.destroy=="function")try{_.current.destroy(),_.current=null}catch{}}},[]),x.createElement("div",{id:v,className:b,ref:oe})},l=function(u){nt("mounts <".concat(o,">"));var v=u.id,b=u.className;return x.createElement("div",{id:v,className:b})},d=n?l:s;return d.propTypes={id:w.default.string,className:w.default.string,onChange:w.default.func,onBlur:w.default.func,onFocus:w.default.func,onReady:w.default.func,onEscape:w.default.func,onClick:w.default.func,onLoadError:w.default.func,onLoaderStart:w.default.func,onNetworksChange:w.default.func,onConfirm:w.default.func,onCancel:w.default.func,onShippingAddressChange:w.default.func,onShippingRateChange:w.default.func,onSavedPaymentMethodRemove:w.default.func,onSavedPaymentMethodUpdate:w.default.func,onAvailablePaymentMethodsChange:w.default.func,options:w.default.object},d.displayName=o,d.__elementType=e,d},I=typeof window>"u",Sr=x.createContext(null);Sr.displayName="EmbeddedCheckoutProviderContext";var fn=function(){var e=nt("calls useStripe()"),n=e.stripe;return n},ia=R("auBankAccount",I),sa=R("card",I),ca=R("cardNumber",I),la=R("cardExpiry",I),ua=R("cardCvc",I),pa=R("currencySelector",I),da=R("iban",I),gn=R("payment",I),ma=R("expressCheckout",I),fa=R("paymentRequestButton",I),ga=R("linkAuthentication",I),ya=R("contactDetails",I),ha=R("address",I),va=R("shippingAddress",I),ba=R("paymentMethodMessaging",I),xa=R("taxId",I),ka=R("issuingCardNumberDisplay",I),Ca=R("issuingCardCvcDisplay",I),Sa=R("issuingCardExpiryDisplay",I),wa=R("issuingCardPinDisplay",I),Pa=R("issuingCardCopyButton",I);function Me(t){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?Me=function(e){return typeof e}:Me=function(e){return e&&typeof Symbol=="function"&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},Me(t)}var vn="dahlia",wr=function(e){return e===3?"v3":e},bn="https://js.stripe.com",Pr="".concat(bn,"/").concat(vn,"/stripe.js"),Nr=/^https:\/\/js\.stripe\.com\/v3\/?(\?.*)?$/,Er=/^https:\/\/js\.stripe\.com\/(v3|[a-z]+)\/stripe\.js(\?.*)?$/,yn="loadStripe.setLoadParameters was called but an existing Stripe.js script already exists in the document; existing script parameters will be used",Rr=function(e){return Nr.test(e)||Er.test(e)},Ir=function(){for(var e=document.querySelectorAll('script[src^="'.concat(bn,'"]')),n=0;n<e.length;n++){var a=e[n];if(Rr(a.src))return a}return null},hn=function(e){var n=e&&!e.advancedFraudSignals?"?advancedFraudSignals=false":"",a=document.createElement("script");a.src="".concat(Pr).concat(n);var o=document.head||document.body;if(!o)throw new Error("Expected document.body not to be null. Stripe.js requires a <body> element.");return o.appendChild(a),a},Ar=function(e,n){!e||!e._registerWrapper||e._registerWrapper({name:"stripe-js",version:"9.9.0",startTime:n})},Pe=null,Fe=null,Ue=null,Or=function(e){return function(n){e(new Error("Failed to load Stripe.js",{cause:n}))}},Lr=function(e,n){return function(){window.Stripe?e(window.Stripe):n(new Error("Stripe.js not available"))}},Tr=function(e){return Pe!==null?Pe:(Pe=new Promise(function(n,a){if(typeof window>"u"||typeof document>"u"){n(null);return}if(window.Stripe&&e&&console.warn(yn),window.Stripe){n(window.Stripe);return}try{var o=Ir();if(o&&e)console.warn(yn);else if(!o)o=hn(e);else if(o&&Ue!==null&&Fe!==null){var s;o.removeEventListener("load",Ue),o.removeEventListener("error",Fe),(s=o.parentNode)===null||s===void 0||s.removeChild(o),o=hn(e)}Ue=Lr(n,a),Fe=Or(a),o.addEventListener("load",Ue),o.addEventListener("error",Fe)}catch(l){a(l);return}}),Pe.catch(function(n){return Pe=null,Promise.reject(n)}))},jr=function(e,n,a){if(e===null)return null;var o=n[0];if(typeof o!="string")throw new Error("Expected publishable key to be of type string, got type ".concat(Me(o)," instead."));var s=o.match(/^pk_test/),l=wr(e.version),d=vn;s&&l!==d&&console.warn("Stripe.js@".concat(l," was loaded on the page, but @stripe/stripe-js@").concat("9.9.0"," expected Stripe.js@").concat(d,". This may result in unexpected behavior. For more information, see https://docs.stripe.com/sdks/stripejs-versioning"));var p=e.apply(void 0,n);return Ar(p,a),p},Ne,xn=!1,kn=function(){return Ne||(Ne=Tr(null).catch(function(e){return Ne=null,Promise.reject(e)}),Ne)};Promise.resolve().then(function(){return kn()}).catch(function(t){xn||console.warn(t)});var Cn=function(){for(var e=arguments.length,n=new Array(e),a=0;a<e;a++)n[a]=arguments[a];xn=!0;var o=Date.now();return kn().then(function(s){return jr(s,n,o)})};var qr="sf:gclid",_r="sf:fbclid",Dr=2160*60*60*1e3;function Pn(){return typeof window<"u"}function Sn(t){let e=Se.getItem(t);if(!e)return"";try{let{v:n,t:a}=JSON.parse(e);return!n||typeof a!="number"||Date.now()-a>Dr?"":n}catch{return""}}function wn(t){if(!Pn())return"";let e=t.replace(/([.$?*|{}()[\]\\/+^])/g,"\\$1"),n=document.cookie.match(new RegExp(`(?:^|; )${e}=([^;]*)`));return n?decodeURIComponent(n[1]):""}function Nn(){if(!Pn())return{};let t=Sn(qr),e=Sn(_r),n=wn("_fbp"),a=wn("_fbc");!a&&e&&(a=`fb.1.${Date.now()}.${e}`);let o={};return t&&(o.gclid=t),e&&(o.fbclid=e),n&&(o.fbp=n),a&&(o.fbc=a),o}var En="sf:meta-purchases",Fr=20;function In(){return typeof window>"u"||window.parent!==window}function Ur(...t){try{if(In()||typeof window.fbq!="function")return;window.fbq(...t)}catch{}}var Rn=new Set;function Mr(t){if(Rn.has(t))return!1;Rn.add(t);try{let e=JSON.parse(Se.getItem(En)??"[]");if(e.includes(t))return!1;Se.setItem(En,JSON.stringify([...e,t].slice(-Fr)))}catch{}return!0}function Kr(t){return[...new Set(t.filter(we).flatMap(e=>e.components.map(n=>n.partNumber)))]}function An(t){let e=t.confirmation;In()||!Mr(e.orderNumber)||Ur("track","Purchase",{value:Number(e.grandTotal),currency:e.currency,content_type:"product",content_ids:Kr(t.lines)},{eventID:e.orderNumber})}var at="checkout-afterpay-context",Ke="checkout-gocardless-context",ot="checkout-zip-context",$e="checkout-klarna-context";function $r({evidence:t,parked:e}){return t?{action:"capture",evidence:t,parked:e}:e?{action:"settle",parked:e}:{action:"ignore"}}function Wr(t,e){let n=t.getItem(e);if(t.removeItem(e),!n)return null;try{return JSON.parse(n)}catch{return null}}var On={name:"Afterpay",legParam:"afterpay",legValues:["confirm","cancel"],storageKey:at,evidenceFrom(t,e){return e==="cancel"||(t.get("status")??"").toUpperCase()==="CANCELLED"?null:t.get("orderToken")},requestFrom:t=>({token:t})},Ln={name:"Zip",legParam:"zip",legValues:["return"],storageKey:ot,evidenceFrom(t){let e=(t.get("result")??"").toLowerCase();return e==="cancelled"||e==="declined"?null:t.get("checkoutId")},requestFrom:t=>({checkoutId:t})};async function it(t,e){let n=new URLSearchParams(e.search),a=n.get(t.legParam);if(!a||!t.legValues.includes(a))return;let o=Wr(e.storage,t.storageKey),s=t.evidenceFrom(n,a);e.scrubQuery();let l=$r({evidence:s,parked:o});if(l.action!=="ignore"){if(e.page.clearCart(),l.action==="settle"){e.page.note(`The ${t.name} payment was not completed \u2014 our team will follow up on payment.`),e.page.land(l.parked.confirmation);return}e.page.setConfirming(!0);try{let d=await e.capture(t.requestFrom(l.evidence));d.status==="paid"?e.page.markPaid():e.page.note(`${t.name} declined the payment \u2014 our team will follow up on payment.`),e.page.land(l.parked?.confirmation??d)}catch{l.parked?(e.page.note(`We could not confirm the ${t.name} payment \u2014 our team will follow up on payment.`),e.page.land(l.parked.confirmation)):e.page.fail(`We could not confirm your ${t.name} payment. Please contact us before trying again \u2014 your payment may already have been taken.`)}finally{e.page.setConfirming(!1)}}}import{Fragment as q,jsx as r,jsxs as c}from"react/jsx-runtime";function Tn(t){let e=t.indexOf(`
|
|
3
|
+
`);return e===-1?[t,""]:[t.slice(0,e),t.slice(e+1)]}function jn(t,{canParkContext:e}={canParkContext:!0}){let n=t.paymentMethods,a=[];return n.includes("card")&&a.push({key:"card",title:"Card",body:null,price:r("span",{className:"font-mono text-[12px] uppercase tracking-[0.2em] text-accent",children:"VISA \xB7 MC \xB7 AMEX"})}),n.includes("paypal")&&t.paypalClientId&&a.push({key:"paypal",title:"PayPal",body:null,price:r("span",{className:"font-mono text-[12px] uppercase tracking-[0.2em] text-accent",children:"PAYPAL \xB7 CARDS"})}),n.includes("afterpay")&&a.push({key:"afterpay",title:"Afterpay",body:null,price:r("span",{className:"font-mono text-[12px] uppercase tracking-[0.2em] text-accent",children:"4 \xD7 INTEREST-FREE"})}),n.includes("square")&&t.squareApplicationId&&t.squareLocationId&&a.push({key:"square",title:"Card (Square)",body:null,price:r("span",{className:"font-mono text-[12px] uppercase tracking-[0.2em] text-accent",children:"VISA \xB7 MC \xB7 AMEX"})}),n.includes("gocardless")&&e&&a.push({key:"gocardless",title:"Bank Direct Debit",body:null,price:r("span",{className:"font-mono text-[12px] uppercase tracking-[0.2em] text-accent",children:"PAY BY BANK"})}),n.includes("zip")&&a.push({key:"zip",title:"Zip",body:null,price:r("span",{className:"font-mono text-[12px] uppercase tracking-[0.2em] text-accent",children:"PAY IN 4"})}),n.includes("klarna")&&e&&a.push({key:"klarna",title:"Klarna",body:null,price:r("span",{className:"font-mono text-[12px] uppercase tracking-[0.2em] text-accent",children:"PAY LATER"})}),a.push({key:"bank",title:"Bank transfer",body:null,price:r("span",{className:"font-mono text-[12px] uppercase tracking-[0.2em] text-fg-dim",children:"Invoiced"})}),a}function lt({site:t,logo:e,preview:n=!1}){let{items:a,totals:o,coupon:s,refuseCoupon:l,deliveryOptions:d,shippingMethodId:p,setShippingMethodId:u,identify:v,clearCapturedCart:b}=Xe(),m=d.find(i=>i.id===p)?.pickupLocation??null,{country:h,countries:A,activeCountry:k,formatMoney:C}=se(),[S,F]=g(()=>jn(t)[0].key),[L,J]=g(!0);$(()=>{J(It(B))},[]);let[te,Ee]=g(!0),[ue,Re]=g(!1),[ne,Ie]=g(""),[re,Ae]=g(""),[ye,ae]=g(""),[Q,W]=g(""),[H,he]=g(""),[pe,E]=g(""),[ve,_]=g(""),[oe,ie]=g(""),[Y,P]=g(()=>h||A[0]?.code||""),[V,be]=g(""),[de,xe]=g([]),Mn=Be(()=>A.find(i=>i.code===Y)??k,[A,Y,k])?.postalLabel||"Postal Code",Kn=de[0]?.divisionType||"State / Province";$(()=>{if(!Y){xe([]);return}let i=!1;return T.listCountrySubdivisions(Y).then(f=>{i||(xe(f),be(""))}),()=>{i=!0}},[Y]);let[X,ut]=g(null),[pt,dt]=g([]),[Oe,Le]=g("new"),[He,mt]=g(!1),[ft,$n]=g("Home"),[gt,yt]=g(null),[U]=g(()=>Wt()),[Te,M]=g(null),[ht,ke]=g(null),[vt,Wn]=g(null),[Yn,Bn]=g(!1),[zn,Hn]=g(!1),[G,Ve]=g(null),[bt,me]=g(!1),[xt,Z]=g(null),[Vn,Gn]=g(!1),[Jn,kt]=g(!1),[Xn,Zn]=g(!1),[Qn,Ct]=g(!1),St=le(!1);$(()=>{n||St.current||!a.some(we)||(St.current=!0,qt({lines:a,currency:o.currency,value:o.subtotal-o.discount,coupon:s?.valid?s.code:void 0}))},[a,o,s,n]),$(()=>{v(ne,re.trim()||void 0)},[ne,re,v]);let wt=le(null),[je,er]=g(null);$(()=>{let i=wt.current;if(!je||!i)return;let f=()=>i.style.setProperty("--sf-checkout-header-h",`${je.offsetHeight}px`);f();let y=new ResizeObserver(f);return y.observe(je),()=>y.disconnect()},[je]),$(()=>{let i=y=>({setConfirming:y,land:M,markPaid:()=>me(!0),note:Z,fail:ke,clearCart:b}),f={search:window.location.search,storage:B,scrubQuery:()=>window.history.replaceState(null,"",window.location.pathname)};it(On,{...f,capture:T.captureAfterpayPayment,page:i(Gn)}),it(Ln,{...f,capture:T.captureZipPayment,page:i(Zn)})},[]),$(()=>{let f=new URLSearchParams(window.location.search).get("gocardless");if(f!=="confirm"&&f!=="cancel")return;let y=B.getItem(Ke);if(B.removeItem(Ke),window.history.replaceState(null,"",window.location.pathname),!y)return;let N;try{N=JSON.parse(y)}catch{return}if(b(),f==="cancel"){Z("The direct debit was not set up \u2014 our team will follow up on payment."),M(N.confirmation);return}kt(!0),T.completeGocardlessPayment({idempotencyKey:N.idempotencyKey}).then(O=>{O.status==="paid"?me(!0):O.status==="declined"?Z("The direct debit was declined \u2014 our team will follow up on payment."):Z("Your direct debit is set up \u2014 the payment is collecting and will clear in a few days.")}).catch(()=>{Z("We could not confirm the direct debit \u2014 our team will follow up on payment.")}).finally(()=>{M(N.confirmation),kt(!1)})},[]),$(()=>{let i=new URLSearchParams(window.location.search),f=i.get("klarna");if(f!=="confirm"&&f!=="cancel")return;let y=B.getItem($e);B.removeItem($e);let N=i.get("authorization_token");if(window.history.replaceState(null,"",window.location.pathname),!y)return;let O;try{O=JSON.parse(y)}catch{return}if(b(),f==="cancel"||!N){Z("The Klarna payment was not completed \u2014 our team will follow up on payment."),M(O.confirmation);return}Ct(!0),T.captureKlarnaPayment({idempotencyKey:O.idempotencyKey,authorizationToken:N}).then(D=>{D.status==="paid"?me(!0):D.status==="declined"?Z("Klarna declined the payment \u2014 our team will follow up on payment."):Z("Your Klarna payment is being reviewed \u2014 we'll confirm the outcome shortly.")}).catch(()=>{Z("We could not confirm the Klarna payment \u2014 our team will follow up on payment.")}).finally(()=>{M(O.confirmation),Ct(!1)})},[]);function Pt(i){let[f,y]=Tn(i.address);W(f),he(y),E(i.city),_(i.zip_code),i.country&&P(i.country),i.phone&&ie(i.phone)}$(()=>{let i=!1;return(async()=>{try{let[f,y,N]=await Promise.all([fe.me(),fe.myProfile().catch(()=>null),fe.myAddresses().catch(()=>[])]);if(i)return;ut(f);let O=N.filter(Ce=>Ce.is_active);dt(O),Ie(f.email);let D=(y?.contact_name??"").split(/\s+/);Ae(f.first_name||D[0]||""),ae(f.last_name||D.slice(1).join(" "));let Et=f.phone||y?.phone;Et&&ie(Et);let Ge=O.find(Ce=>Ce.id===f.default_delivery_location)??O[0];if(Ge)Le(Ge.id),Pt(Ge);else if(y){let[Ce,rr]=Tn(y.address??"");W(Ce),he(rr),E(y.city??""),_(y.zip_code??""),y.country&&P(y.country)}}catch{}})(),()=>{i=!0}},[]);function tr(){fe.logout(),ut(null),dt([]),Le("new"),mt(!1)}let qe=Be(()=>jn(t,{canParkContext:L}),[t,L]);$(()=>{qe.some(i=>i.key===S)||F(qe[0].key)},[qe,S]);let Nt=H.trim()?`${Q}
|
|
4
|
+
${H.trim()}`:Q;async function nr(){if(n||ue||a.length===0||o.repricing)return;let i=a.filter(we).flatMap(f=>At(f.components,f.qty));if(i.length===0){ke("Pick a size on each wheel's product page before checking out.");return}Re(!0),ke(null);try{let y=await(X?T.createOrderAuthed:T.createOrder)({customer:{email:ne,name:`${re} ${ye}`.trim(),phone:oe.trim()||void 0,address:Nt,city:pe,state:V||void 0,zipCode:ve,country:Y},items:i,currency:o.currency,idempotencyKey:U,shippingMethodId:p??void 0,couponCode:Tt(s),...Nn()});if(_t({confirmation:y,lines:a,adsSendTo:t.googleAdsId&&t.googleAdsConversionLabel?`${t.googleAdsId}/${t.googleAdsConversionLabel}`:null}),An({confirmation:y,lines:a}),b(),X&&He&&Oe==="new")try{await fe.createAddress({label:ft.trim()||"Home",contact_name:`${re} ${ye}`.trim(),phone:oe.trim(),address:Nt,city:pe,zip_code:ve,country:Y}),yt("Address saved to your account.")}catch{yt("We couldn't save the address to your account \u2014 you can add it under Account \u2192 Addresses.")}if(S==="card"&&t.paymentMethods.includes("card")){let N=await T.createPaymentIntent({idempotencyKey:U});Ve(y),Wn(N)}else if(S==="paypal"&&t.paymentMethods.includes("paypal"))Ve(y),Bn(!0);else if(S==="afterpay"&&t.paymentMethods.includes("afterpay")){let N=window.location.origin,O=await T.createAfterpayCheckout({idempotencyKey:U,confirmUrl:`${N}/checkout?afterpay=confirm`,cancelUrl:`${N}/checkout?afterpay=cancel`}),D={idempotencyKey:U,confirmation:y};B.setItem(at,JSON.stringify(D)),window.location.assign(O.redirectCheckoutUrl);return}else if(S==="square"&&t.paymentMethods.includes("square"))Ve(y),Hn(!0);else if(S==="gocardless"&&t.paymentMethods.includes("gocardless")){let N=window.location.origin,O=await T.createGocardlessBillingRequest({idempotencyKey:U,confirmUrl:`${N}/checkout?gocardless=confirm`,cancelUrl:`${N}/checkout?gocardless=cancel`}),D={idempotencyKey:U,confirmation:y};B.setItem(Ke,JSON.stringify(D)),window.location.assign(O.authorisationUrl);return}else if(S==="zip"&&t.paymentMethods.includes("zip")){let N=window.location.origin,O=await T.createZipCheckout({idempotencyKey:U,redirectUrl:`${N}/checkout?zip=return`}),D={idempotencyKey:U,confirmation:y};B.setItem(ot,JSON.stringify(D)),window.location.assign(O.redirectUrl);return}else if(S==="klarna"&&t.paymentMethods.includes("klarna")){let N=window.location.origin,O=await T.createKlarnaCheckout({idempotencyKey:U,confirmUrl:`${N}/checkout?klarna=confirm`,cancelUrl:`${N}/checkout?klarna=cancel`}),D={idempotencyKey:U,confirmation:y};B.setItem($e,JSON.stringify(D)),window.location.assign(O.redirectUrl);return}else M(y)}catch(f){let y=jt(f);y?(l(y),ke(`${y} Your order has not been placed. Place it again to continue without the code.`)):ke(f instanceof _e?f.message:"Could not place the order. Please try again."),Re(!1)}}return Te?c("div",{className:"flex min-h-screen flex-col items-center justify-center gap-4 bg-bg px-6 text-center text-fg",children:[r("h1",{className:"font-display text-3xl",children:bt?"Payment received":"Order received"}),c("p",{className:"text-fg-dim",children:[bt?c(q,{children:["Thank you \u2014 your payment for order"," ",r("span",{className:"font-mono text-fg",children:Te.orderNumber})," has gone through."," "]}):c(q,{children:["Thank you \u2014 your order ",r("span",{className:"font-mono text-fg",children:Te.orderNumber})," is being reviewed by our team."," "]}),X?c(q,{children:["Track it under"," ",r(ee,{href:"/account/orders",className:"text-accent underline",children:"your orders"}),"."]}):r(q,{children:"We\u2019ve emailed you a link to set a password and track it."})]}),xt&&r("p",{className:"text-sm text-fg-dim",children:xt}),gt&&r("p",{className:"text-sm text-fg-dim",children:gt}),!X&&c("p",{className:"text-sm text-fg-dim",children:["You can also"," ",r(ee,{href:`/track?order=${encodeURIComponent(Te.orderNumber)}`,className:"text-accent underline",children:"track this order"})," ","anytime with just your order number and email \u2014 no password needed."]}),r(ee,{href:"/wheels",className:"mt-2 border-2 border-line px-6 py-3 font-mono text-[12px] uppercase tracking-[0.24em] hover:border-accent",children:"Continue browsing"})]}):Vn?c("div",{className:"flex min-h-screen flex-col items-center justify-center gap-4 bg-bg px-6 text-center text-fg",children:[r("h1",{className:"font-display text-3xl",children:"Confirming your payment\u2026"}),r("p",{className:"font-mono text-[12px] uppercase tracking-[0.2em] text-fg-dim",children:"Checking with Afterpay \u2014 one moment."})]}):Jn?c("div",{className:"flex min-h-screen flex-col items-center justify-center gap-4 bg-bg px-6 text-center text-fg",children:[r("h1",{className:"font-display text-3xl",children:"Setting up your direct debit\u2026"}),r("p",{className:"font-mono text-[12px] uppercase tracking-[0.2em] text-fg-dim",children:"Confirming with GoCardless \u2014 one moment."})]}):Xn?c("div",{className:"flex min-h-screen flex-col items-center justify-center gap-4 bg-bg px-6 text-center text-fg",children:[r("h1",{className:"font-display text-3xl",children:"Confirming your payment\u2026"}),r("p",{className:"font-mono text-[12px] uppercase tracking-[0.2em] text-fg-dim",children:"Checking with Zip \u2014 one moment."})]}):Qn?c("div",{className:"flex min-h-screen flex-col items-center justify-center gap-4 bg-bg px-6 text-center text-fg",children:[r("h1",{className:"font-display text-3xl",children:"Confirming your payment\u2026"}),r("p",{className:"font-mono text-[12px] uppercase tracking-[0.2em] text-fg-dim",children:"Checking with Klarna \u2014 one moment."})]}):vt?r(Yr,{payment:vt,onPaid:()=>{me(!0),M(G)},onSkip:()=>M(G)}):Yn&&G?r(Hr,{confirmation:G,clientId:t.paypalClientId??"",idemKey:U,onPaid:()=>{me(!0),M(G)},onSkip:()=>M(G)}):zn&&G?r(Gr,{confirmation:G,applicationId:t.squareApplicationId??"",locationId:t.squareLocationId??"",environment:t.squareEnvironment??"production",idemKey:U,onPaid:()=>{me(!0),M(G)},onSkip:()=>M(G)}):c("div",{ref:wt,className:"bg-bg text-fg",children:[c("header",{ref:er,className:"sticky top-0 z-30 grid grid-cols-[1fr_auto_1fr] items-center gap-8 border-b border-line px-[var(--sf-site-gutter,40px)] py-[22px] backdrop-blur-md max-[640px]:px-[min(20px,var(--sf-site-gutter,40px))] max-[640px]:py-4",style:{background:"color-mix(in oklab, var(--bg) 92%, transparent)"},children:[c(ee,{href:"/",className:"inline-flex items-center gap-2.5 font-mono text-[13px] uppercase tracking-[0.26em] text-fg-dim transition-colors hover:text-accent",children:["\u2190",r("span",{className:"max-[640px]:hidden",children:"Back to shop"})]}),r(ee,{href:"/","aria-label":`${t.name} home`,className:"inline-flex items-center justify-center",children:r(Mt,{variant:"header",scrolled:!0,src:e?.url??null,reverse:e?.reverse??!1,reversedSrc:e?.reversed??null,inkIsDark:e?.inkIsDark??null,size:e?.size??"medium",sizePx:e?.sizePx??null,siteName:t.name})}),t.cardPayments?c("span",{className:"flex items-center justify-end gap-2.5 font-mono text-[12px] uppercase tracking-[0.26em] text-fg-dim",children:[r("span",{className:"text-green",children:"\u25CF"}),r("span",{className:"max-[640px]:hidden",children:"Encrypted \xB7"})," Stripe"]}):r("span",{})]}),c("section",{className:"mx-auto flex max-w-[var(--sf-site-width,1600px)] flex-wrap items-center justify-between gap-x-8 gap-y-3 border-b border-line bg-surface px-[var(--sf-site-gutter,40px)] py-4 font-mono text-[13px] font-medium uppercase tracking-[0.24em] text-fg max-[1100px]:px-[min(24px,var(--sf-site-gutter,40px))]",children:[r("span",{className:"inline-flex items-center bg-accent px-2.5 py-1 font-bold text-bg",children:"Checkout"}),r("nav",{"aria-label":"Checkout steps",className:"flex flex-wrap items-center gap-5",children:[{n:"01",label:"Cart",state:"done"},{n:"02",label:"Ship & pay",state:"active"},{n:"03",label:"Review",state:"pending"},{n:"04",label:"Confirmed",state:"pending"}].map(i=>c("span",{className:["relative flex items-center gap-2 transition-colors",i.state==="pending"?"text-fg-dim":"text-fg",i.state==="active"?"font-bold after:absolute after:inset-x-0 after:-bottom-1 after:h-[2px] after:bg-accent":""].join(" "),children:[r("span",{className:i.state==="done"?"text-accent font-bold":"",children:i.n}),r("span",{children:i.label})]},i.n))})]}),c("main",{className:"mx-auto grid max-w-[var(--sf-site-width,1600px)] grid-cols-[1fr_420px] items-start gap-16 px-[var(--sf-site-gutter,40px)] pt-9 pb-[100px] max-[1100px]:grid-cols-1 max-[1100px]:gap-10 max-[1100px]:px-[min(24px,var(--sf-site-gutter,40px))] max-[1100px]:pt-8 max-[1100px]:pb-20",children:[c("div",{children:[c(We,{num:"N\xB0 01",title:"Contact",children:[r(z,{label:"Email",hint:X?c(q,{children:["Signed in \xB7"," ",r("button",{type:"button",onClick:tr,className:"underline transition-colors hover:text-accent",children:"Not you? Sign out"})]}):void 0,children:r("input",{type:"email",value:ne,onChange:i=>Ie(i.target.value),placeholder:"you@example.com",readOnly:X!==null,className:K+(X?" opacity-70":"")})}),c(_n,{children:[r(z,{label:"First name",children:r("input",{type:"text",value:re,onChange:i=>Ae(i.target.value),className:K})}),r(z,{label:"Last name",children:r("input",{type:"text",value:ye,onChange:i=>ae(i.target.value),className:K})})]})]}),c(We,{num:"N\xB0 02",title:m?"Collection":"Shipping address",children:[m?c("div",{className:"grid gap-1.5 text-[14px] leading-[1.6]",children:[r("span",{className:"font-medium",children:m.name}),[m.address,m.city,m.state,m.zipCode,m.country].filter(Boolean).length>0&&r("span",{className:"opacity-80",children:[m.address,m.city,m.state,m.zipCode,m.country].filter(Boolean).join(", ")}),m.openingHours&&r("span",{className:"opacity-80",children:m.openingHours}),m.instructions&&r("span",{className:"opacity-80",children:m.instructions}),r("span",{className:"mt-1 font-mono text-[11px] uppercase tracking-[0.2em] text-fg-dim",children:"No delivery address needed"})]}):c(q,{children:[X!==null&&pt.length>0&&c("div",{className:"grid gap-2.5",children:[pt.map(i=>r(Ye,{active:Oe===i.id,onClick:()=>{Le(i.id),Pt(i)},title:i.label,body:i.formatted_address,price:null},i.id)),r(Ye,{active:Oe==="new",onClick:()=>Le("new"),title:"New address",body:null,price:null})]}),r(z,{label:"Address line 1",children:r("input",{type:"text",value:Q,onChange:i=>W(i.target.value),className:K})}),r(z,{label:"Address line 2",children:r("input",{type:"text",value:H,onChange:i=>he(i.target.value),placeholder:"Apartment, unit, garage bay\u2026",className:K})}),c(_n,{cols:"three",children:[r(z,{label:"City",children:r("input",{type:"text",value:pe,onChange:i=>E(i.target.value),className:K})}),r(z,{label:Mn,children:r("input",{type:"text",value:ve,onChange:i=>_(i.target.value),className:K})}),r(z,{label:"Country",children:r(Qe,{value:Y,onChange:i=>P(i.target.value),className:K+" pr-10",style:qn,children:A.map(i=>r("option",{value:i.code,children:i.name},i.code))})})]}),r(z,{label:Kn,children:de.length>0?c(Qe,{value:V,onChange:i=>be(i.target.value),className:K+" pr-10",style:qn,children:[r("option",{value:"",children:"Select\u2026"}),de.map(i=>r("option",{value:i.code||i.name,children:i.name},i.code||i.name))]}):r("input",{type:"text",value:V,onChange:i=>be(i.target.value),className:K})})]}),r(z,{label:"Phone",children:r("input",{type:"tel",value:oe,onChange:i=>ie(i.target.value),placeholder:"+44 7700 900000",className:K})}),!m&&X!==null&&Oe==="new"&&c(q,{children:[r(Un,{checked:He,onChange:mt,label:"Save this address to my account"}),He&&r(z,{label:"Address label",children:r("input",{type:"text",value:ft,onChange:i=>$n(i.target.value),placeholder:"Home",className:K})})]}),!m&&r(Un,{checked:te,onChange:Ee,label:"Billing address is the same as shipping"})]}),r(We,{num:"N\xB0 03",title:"Shipping",children:c("div",{className:"grid gap-2.5",children:[d.map(i=>{let f=i.pickupLocation,y=f?[f.address,f.city,f.state,f.zipCode].filter(Boolean).join(", "):null;return r(Ye,{active:p===i.id,onClick:()=>u(i.id),title:f?`${i.name} \u2014 ${f.name}`:i.name,body:f?[y,f.openingHours,f.instructions,i.description].filter(Boolean).join(" \xB7 "):i.description||null,price:o.freeShipping||i.amount===0?"Free":C(i.amount,o.currency),tag:i.tag||void 0},i.id)}),d.length===0&&r("p",{className:"font-mono text-[11px] uppercase tracking-[0.2em] text-fg-dim",children:"Shipping is arranged after your order is reviewed."})]})}),c(We,{num:"N\xB0 04",title:"Payment",children:[r("div",{className:"grid gap-2.5",children:qe.map(i=>r(Ye,{active:S===i.key,onClick:()=>F(i.key),title:i.title,body:i.body,price:i.price,tag:i.tag},i.key))}),S==="card"?r("p",{className:"font-mono text-[11px] uppercase tracking-[0.2em] text-fg-dim",children:"You\u2019ll enter card details on the secure payment step after placing the order."}):S==="paypal"?r("p",{className:"font-mono text-[11px] uppercase tracking-[0.2em] text-fg-dim",children:"You\u2019ll approve the payment with PayPal on the payment step after placing the order."}):S==="afterpay"?r("p",{className:"font-mono text-[11px] uppercase tracking-[0.2em] text-fg-dim",children:"You\u2019ll be taken to Afterpay to approve four interest-free payments, then brought straight back."}):S==="square"?r("p",{className:"font-mono text-[11px] uppercase tracking-[0.2em] text-fg-dim",children:"You\u2019ll enter card details on the secure payment step after placing the order."}):S==="gocardless"?r("p",{className:"font-mono text-[11px] uppercase tracking-[0.2em] text-fg-dim",children:"You\u2019ll set up a bank direct debit with GoCardless, then be brought straight back. Direct debits clear in a few days."}):S==="zip"?r("p",{className:"font-mono text-[11px] uppercase tracking-[0.2em] text-fg-dim",children:"You\u2019ll be taken to Zip to approve four instalments, then brought straight back."}):S==="klarna"?r("p",{className:"font-mono text-[11px] uppercase tracking-[0.2em] text-fg-dim",children:"You\u2019ll be taken to Klarna to choose how to pay, then brought straight back."}):r("p",{className:"font-mono text-[11px] uppercase tracking-[0.2em] text-fg-dim",children:"Our team emails you the invoice and bank details after review."})]})]}),c("aside",{className:"sticky top-[var(--sf-checkout-header-h,88px)] border border-line max-[1100px]:static",style:{background:"linear-gradient(180deg, color-mix(in oklab, var(--surface) 70%, var(--bg)) 0%, var(--bg) 100%)"},children:[c("header",{className:"flex items-center justify-between border-b border-accent bg-accent px-6 py-3.5 font-mono text-[13px] font-bold uppercase tracking-[0.28em] text-bg",children:[r("span",{children:"Your order"}),r(ee,{href:"/wheels",className:"text-bg/80 transition-colors hover:text-bg",children:"Edit \u2197"})]}),r("div",{className:"flex flex-col px-6 py-2",children:a.length===0?c("div",{className:"py-8 text-center font-serif text-[16px] italic leading-[1.5] text-fg-dim",children:["Your cart is empty. ",r(ee,{href:"/wheels",className:"text-accent underline-offset-4 hover:underline",children:"Browse the catalog \u2192"})]}):a.map(i=>r(Jr,{slug:i.slug,name:i.name,qty:i.qty,config:i.config,configSummary:Xr(i.config),price:Dt(i,o.currency)??0,currency:o.currency},i.id))}),r("div",{className:"border-t border-line px-6 pt-5",children:r(Bt,{customerEmail:ne})}),c("div",{className:"flex flex-col gap-2 border-t border-line mt-5 px-6 pt-5",children:[r(ce,{label:"Subtotal",value:C(o.subtotal,o.currency)}),o.discount>0&&s?.valid&&r(ce,{label:`Coupon \xB7 ${s.code}`,value:c("span",{className:"font-semibold text-green",children:["\u2212 ",C(o.discount,o.currency)]})}),s?.deferred&&r(ce,{label:`Coupon \xB7 ${s.code}`,value:Lt}),s?.valid&&s.effects?.gift&&r(ce,{label:`Free gift \xB7 ${s.effects.gift.partNumber}${s.effects.gift.quantity>1?` \xD7${s.effects.gift.quantity}`:""}`,value:r("span",{className:"font-semibold text-green",children:"Free"})}),r(ce,{label:"Shipping",value:o.freeShipping||o.shipping===0?r("span",{className:"font-semibold text-green",children:"Free"}):C(o.shipping,o.currency)}),o.tax&&(o.tax.charged?r(ce,{label:o.tax.inclusive?`${o.tax.name} included \xB7 ${o.tax.rate}%`:o.tax.name,value:C(o.tax.amount,o.currency)}):o.tax.name?r(ce,{label:o.tax.name,value:"Calculated at checkout"}):null)]}),c("div",{className:"mt-4 flex items-end justify-between border-y-2 border-accent px-6 py-4",style:{background:"color-mix(in oklab, var(--accent) 10%, var(--bg))"},children:[r("span",{className:"font-display text-[20px] font-bold uppercase tracking-[0.04em] text-fg",children:"Total"}),r("span",{className:"text-right font-display text-[34px] font-bold leading-none tracking-[-0.025em] text-fg",children:o.repricing?"\u2026":C(o.grand,o.currency)})]}),c("div",{className:"flex flex-col gap-2.5 px-6 pt-[18px] pb-6",children:[ht&&r("p",{className:"border border-red-300 bg-red-50 px-3 py-2 text-[13px] text-red-700",children:ht}),o.hasUnpricedLine&&c("p",{className:"border border-amber-300 bg-amber-50 px-3 py-2 text-[13px] text-amber-800",children:["One or more items have no price in ",o.currency,". Remove them, or switch region, to continue."]}),r("button",{type:"button",onClick:nr,disabled:ue||a.length===0||o.hasUnpricedLine||o.repricing,className:["group flex items-center justify-between border px-5 py-5 font-mono text-[14px] font-medium uppercase tracking-[0.3em] transition-all",a.length===0||ue||o.hasUnpricedLine||o.repricing?"cursor-not-allowed border-line bg-surface text-fg-dim":"border-btn bg-btn text-btn-ink hover:border-fg hover:bg-fg"].join(" "),children:ue?c(q,{children:["Building order\u2026 ",r("span",{children:"\xB7"})]}):o.repricing?c(q,{children:["Updating total\u2026 ",r("span",{children:"\xB7"})]}):c(q,{children:[S==="bank"?"Place order":"Continue to payment"," \xB7"," ",C(o.grand,o.currency),r("span",{className:"transition-transform group-hover:translate-x-1",children:"\u2192"})]})})]})]})]}),r("footer",{className:"flex flex-wrap items-center justify-between gap-4 border-t border-line px-[var(--sf-site-gutter,40px)] py-6 font-mono text-[12px] uppercase tracking-[0.22em] text-fg-dim max-[640px]:px-[min(20px,var(--sf-site-gutter,40px))]",children:r("span",{children:Kt({legalName:t.legalName,foundedYear:t.foundedYear,siteName:t.name})})})]})}var K="w-full border-2 border-line bg-bg px-4 py-3.5 font-display text-[17px] tracking-[-0.005em] text-fg outline-none transition-colors placeholder:text-fg-dim hover:border-fg-dim focus:border-accent",qn={backgroundImage:"linear-gradient(45deg, transparent 50%, var(--accent) 50%), linear-gradient(135deg, var(--accent) 50%, transparent 50%)",backgroundPosition:"calc(100% - 18px) 50%, calc(100% - 12px) 50%",backgroundSize:"6px 6px",backgroundRepeat:"no-repeat"};function We({num:t,title:e,children:n}){return c("section",{className:"mb-6 border border-line bg-bg",children:[c("header",{className:"flex items-center gap-4 border-b border-line bg-surface px-6 py-3.5",children:[r("span",{className:"inline-flex items-center justify-center bg-accent px-2.5 py-1 font-mono text-[11px] font-bold uppercase tracking-[0.24em] text-bg",children:t}),r("h2",{className:"font-display text-[26px] font-medium uppercase leading-none tracking-[-0.02em] text-fg",children:e})]}),r("div",{className:"flex flex-col gap-4 px-6 py-6",children:n})]})}function _n({cols:t="two",children:e}){return r("div",{className:["grid gap-3 max-[640px]:grid-cols-1",t==="two"?"grid-cols-2":"grid-cols-[1.2fr_1fr_1fr]"].join(" "),children:e})}function z({label:t,hint:e,children:n}){return c("div",{className:"relative flex flex-col gap-2",children:[r("label",{className:"font-mono text-[12px] font-semibold uppercase tracking-[0.22em] text-fg",children:t}),n,e&&r("span",{className:"font-mono text-[11px] uppercase tracking-[0.22em] text-fg-dim",children:e})]})}function Ye({active:t,onClick:e,title:n,body:a,price:o,tag:s}){return c("button",{type:"button",onClick:e,"aria-pressed":t,className:["relative grid grid-cols-[auto_1fr_auto] items-center gap-[18px] border bg-bg px-[22px] py-[18px] text-left transition-colors",t?"border-accent outline outline-1 outline-accent -outline-offset-1":"border-line hover:border-[color-mix(in_oklab,var(--accent)_50%,var(--line))]"].join(" "),style:t?{background:"color-mix(in oklab, var(--accent) 14%, var(--bg))"}:void 0,children:[t&&r("span",{"aria-hidden":!0,className:"absolute inset-y-0 left-0 w-[3px] bg-accent"}),r("span",{className:["flex h-[18px] w-[18px] items-center justify-center rounded-full border-2",t?"border-accent":"border-fg-dim"].join(" "),children:t&&r("span",{className:"block h-2.5 w-2.5 rounded-full bg-accent"})}),c("span",{className:"flex min-w-0 flex-col gap-1",children:[c("span",{className:"flex flex-wrap items-baseline gap-2.5 font-display text-[18px] font-medium tracking-[-0.01em] text-fg",children:[n,s&&r("span",{className:"border px-1.5 py-0.5 font-mono text-[11px] font-normal uppercase tracking-[0.24em] text-accent",style:{background:"color-mix(in oklab, var(--accent) 14%, transparent)",borderColor:"color-mix(in oklab, var(--accent) 45%, var(--line))"},children:s})]}),a&&r("span",{className:"font-mono text-[12px] uppercase tracking-[0.22em] text-fg-dim",children:a})]}),r("span",{className:"whitespace-nowrap font-display text-[18px] font-medium tracking-[-0.01em] text-fg",children:o})]})}function Yr({payment:t,onPaid:e,onSkip:n}){let{formatMoney:a}=se(),o=Be(()=>Cn(t.publishableKey),[t.publishableKey]),s=Be(()=>{if(typeof document>"u")return;let l=getComputedStyle(document.documentElement),d=(u,v)=>l.getPropertyValue(u).trim()||v,p=document.documentElement.getAttribute("data-theme")!=="light";return{theme:p?"night":"stripe",variables:{colorPrimary:d("--accent",p?"#c9a461":"#8a6a2a"),colorBackground:d("--surface",p?"#131313":"#ffffff"),colorText:d("--fg",p?"#f5f1e8":"#0a0a0a"),borderRadius:"0px"}}},[]);return r("div",{className:"flex min-h-screen flex-col items-center justify-center gap-6 bg-bg px-6 py-16 text-fg",children:c("div",{className:"w-full max-w-xl border border-line bg-bg",children:[c("header",{className:"flex items-center justify-between gap-4 border-b border-line bg-surface px-6 py-3.5",children:[r("span",{className:"inline-flex items-center justify-center bg-accent px-2.5 py-1 font-mono text-[11px] font-bold uppercase tracking-[0.24em] text-bg",children:t.orderNumber}),r("h1",{className:"font-display text-[26px] font-medium uppercase leading-none tracking-[-0.02em] text-fg",children:"Payment"})]}),c("div",{className:"flex flex-col gap-4 px-6 py-6",children:[c("p",{className:"font-mono text-[12px] uppercase tracking-[0.2em] text-fg-dim",children:["Your order is placed. Pay"," ",r("span",{className:"text-fg",children:a(Number(t.amount),t.currency)})," ","by card to confirm it now."]}),r(rt,{stripe:o,options:{clientSecret:t.clientSecret,appearance:s},children:r(Br,{payment:t,onPaid:e,onSkip:n})})]})]})})}function Br({payment:t,onPaid:e,onSkip:n}){let{formatMoney:a}=se(),o=fn(),s=dn(),[l,d]=g(!1),[p,u]=g(null);async function v(){if(!o||!s||l)return;d(!0),u(null);let b=await o.confirmPayment({elements:s,redirect:"if_required"});if(b.error){u(b.error.message??"Payment failed. Please try again."),d(!1);return}let m=b.paymentIntent?.status;if(m==="succeeded"||m==="processing"){e();return}u("Payment did not complete. Please try again."),d(!1)}return c("div",{className:"flex flex-col gap-4",children:[r(gn,{}),p&&r("p",{className:"border border-red-300 bg-red-50 px-3 py-2 text-[13px] text-red-700",children:p}),r("button",{type:"button",onClick:v,disabled:!o||!s||l,className:["group flex items-center justify-between border px-5 py-5 font-mono text-[14px] font-medium uppercase tracking-[0.3em] transition-all",!o||!s||l?"cursor-not-allowed border-line bg-surface text-fg-dim":"border-btn bg-btn text-btn-ink hover:border-fg hover:bg-fg"].join(" "),children:l?c(q,{children:["Processing\u2026 ",r("span",{children:"\xB7"})]}):c(q,{children:["Pay ",a(Number(t.amount),t.currency),r("span",{className:"transition-transform group-hover:translate-x-1",children:"\u2192"})]})}),r("button",{type:"button",onClick:n,disabled:l,className:"self-center font-mono text-[11px] uppercase tracking-[0.24em] text-fg-dim underline-offset-4 transition-colors hover:text-accent hover:underline",children:"Pay later \u2014 our team will be in touch"})]})}var Dn="paypal-sdk-js",st=null;function zr(t,e){let n=`${t}|${e}`;if(st?.key===n)return st.promise;let a=new Promise((o,s)=>{document.getElementById(Dn)?.remove();let l=document.createElement("script");l.id=Dn;let d=new URLSearchParams({"client-id":t,currency:e,intent:"capture"});l.src=`https://www.paypal.com/sdk/js?${d}`,l.onload=()=>{let p=window.paypal;p?o(p):s(new Error("PayPal SDK loaded without its namespace"))},l.onerror=()=>s(new Error("PayPal SDK failed to load")),document.head.appendChild(l)});return st={key:n,promise:a},a}function Hr({confirmation:t,clientId:e,idemKey:n,onPaid:a,onSkip:o}){let{formatMoney:s}=se(),l=le(null),[d,p]=g(!1),[u,v]=g(null),b=le(a);return b.current=a,$(()=>{let m=!1,h=null;return zr(e,t.currency).then(A=>{if(!(m||!l.current))return h=A.Buttons({style:{layout:"vertical",color:"gold",shape:"rect",label:"paypal"},createOrder:async()=>(await T.createPaypalOrder({idempotencyKey:n})).paypalOrderId,onApprove:async k=>{try{if((await T.capturePaypalOrder({idempotencyKey:n,paypalOrderId:k.orderID})).status==="paid"){b.current();return}v("PayPal did not complete the payment. Please try again.")}catch(C){v(C instanceof _e?C.message:"PayPal payment failed. Please try again.")}},onError:()=>{v("PayPal could not process the payment. Please try again.")}}),h.render(l.current).then(()=>{m||p(!0)})}).catch(()=>{m||v("PayPal could not be loaded. You can pay later \u2014 your order stands either way.")}),()=>{m=!0,h?.close?.()}},[e,t.currency,n]),r("div",{className:"flex min-h-screen flex-col items-center justify-center gap-6 bg-bg px-6 py-16 text-fg",children:c("div",{className:"w-full max-w-xl border border-line bg-bg",children:[c("header",{className:"flex items-center justify-between gap-4 border-b border-line bg-surface px-6 py-3.5",children:[r("span",{className:"inline-flex items-center justify-center bg-accent px-2.5 py-1 font-mono text-[11px] font-bold uppercase tracking-[0.24em] text-bg",children:t.orderNumber}),r("h1",{className:"font-display text-[26px] font-medium uppercase leading-none tracking-[-0.02em] text-fg",children:"PayPal"})]}),c("div",{className:"flex flex-col gap-4 px-6 py-6",children:[c("p",{className:"font-mono text-[12px] uppercase tracking-[0.2em] text-fg-dim",children:["Your order is placed. Pay"," ",r("span",{className:"text-fg",children:s(Number(t.grandTotal),t.currency)})," ","with PayPal to confirm it now."]}),!d&&!u&&r("p",{className:"font-mono text-[11px] uppercase tracking-[0.2em] text-fg-dim",children:"Loading PayPal\u2026"}),r("div",{ref:l}),u&&r("p",{className:"border border-red-300 bg-red-50 px-3 py-2 text-[13px] text-red-700",children:u}),r("button",{type:"button",onClick:o,className:"self-center font-mono text-[11px] uppercase tracking-[0.24em] text-fg-dim underline-offset-4 transition-colors hover:text-accent hover:underline",children:"Pay later \u2014 our team will be in touch"})]})]})})}var Fn="square-web-sdk",ct=null;function Vr(t){let e=t==="sandbox"?"sandbox":"production";if(ct?.key===e)return ct.promise;let n=new Promise((a,o)=>{document.getElementById(Fn)?.remove();let s=document.createElement("script");s.id=Fn,s.src=e==="sandbox"?"https://sandbox.web.squarecdn.com/v1/square.js":"https://web.squarecdn.com/v1/square.js",s.onload=()=>{let l=window.Square;l?a(l):o(new Error("Square SDK loaded without its namespace"))},s.onerror=()=>o(new Error("Square SDK failed to load")),document.head.appendChild(s)});return ct={key:e,promise:n},n}function Gr({confirmation:t,applicationId:e,locationId:n,environment:a,idemKey:o,onPaid:s,onSkip:l}){let{formatMoney:d}=se(),p=le(null),u=le(null),v=le(null),[b,m]=g(!1),[h,A]=g(!1),[k,C]=g(null);$(()=>{let F=!1,L=null;return Vr(a).then(async J=>{if(F||!p.current)return;let te=J.payments(e,n);v.current=te,L=await te.card(),await L.attach(p.current),u.current=L,F||m(!0)}).catch(()=>{F||C("Square could not be loaded. You can pay later \u2014 your order stands either way.")}),()=>{F=!0,L?.destroy?.()}},[e,n,a]);async function S(){let F=u.current;if(!(!F||h)){A(!0),C(null);try{let L=await F.tokenize();if(L.status!=="OK"||!L.token){C(L.errors?.[0]?.message??"Please check the card details and try again."),A(!1);return}let J;try{J=(await v.current?.verifyBuyer?.(L.token,{amount:String(t.grandTotal),currencyCode:t.currency,intent:"CHARGE"}))?.token}catch{}if((await T.chargeSquare({idempotencyKey:o,sourceId:L.token,...J?{verificationToken:J}:{}})).status==="paid"){s();return}C("Square did not complete the payment. Please try again.")}catch(L){C(L instanceof _e?L.message:"Square payment failed. Please try again.")}A(!1)}}return r("div",{className:"flex min-h-screen flex-col items-center justify-center gap-6 bg-bg px-6 py-16 text-fg",children:c("div",{className:"w-full max-w-xl border border-line bg-bg",children:[c("header",{className:"flex items-center justify-between gap-4 border-b border-line bg-surface px-6 py-3.5",children:[r("span",{className:"inline-flex items-center justify-center bg-accent px-2.5 py-1 font-mono text-[11px] font-bold uppercase tracking-[0.24em] text-bg",children:t.orderNumber}),r("h1",{className:"font-display text-[26px] font-medium uppercase leading-none tracking-[-0.02em] text-fg",children:"Card"})]}),c("div",{className:"flex flex-col gap-4 px-6 py-6",children:[c("p",{className:"font-mono text-[12px] uppercase tracking-[0.2em] text-fg-dim",children:["Your order is placed. Pay"," ",r("span",{className:"text-fg",children:d(Number(t.grandTotal),t.currency)})," ","by card to confirm it now."]}),!b&&!k&&r("p",{className:"font-mono text-[11px] uppercase tracking-[0.2em] text-fg-dim",children:"Loading card form\u2026"}),r("div",{ref:p}),k&&r("p",{className:"border border-red-300 bg-red-50 px-3 py-2 text-[13px] text-red-700",children:k}),r("button",{type:"button",onClick:S,disabled:!b||h,className:["group flex items-center justify-between border px-5 py-5 font-mono text-[14px] font-medium uppercase tracking-[0.3em] transition-all",!b||h?"cursor-not-allowed border-line bg-surface text-fg-dim":"border-btn bg-btn text-btn-ink hover:border-fg hover:bg-fg"].join(" "),children:h?c(q,{children:["Processing\u2026 ",r("span",{children:"\xB7"})]}):c(q,{children:["Pay ",d(Number(t.grandTotal),t.currency),r("span",{className:"transition-transform group-hover:translate-x-1",children:"\u2192"})]})}),r("button",{type:"button",onClick:l,disabled:h,className:"self-center font-mono text-[11px] uppercase tracking-[0.24em] text-fg-dim underline-offset-4 transition-colors hover:text-accent hover:underline",children:"Pay later \u2014 our team will be in touch"})]})]})})}function Un({checked:t,onChange:e,label:n,sub:a}){return c("div",{className:"flex items-start gap-3 border-t border-line py-3.5 first:border-t-0 first:pt-0",children:[r("button",{type:"button",onClick:()=>e(!t),"aria-pressed":t,"aria-label":n,className:["mt-px flex h-5 w-5 flex-shrink-0 items-center justify-center border transition-colors",t?"border-accent":"border-line"].join(" "),style:t?{background:"color-mix(in oklab, var(--accent) 18%, transparent)"}:void 0,children:t&&r("span",{className:"text-[13px] text-accent",children:"\u2713"})}),c("span",{className:"flex-1 font-display text-[16px] leading-[1.5] text-fg",children:[n,a&&r("small",{className:"mt-1 block font-mono text-[12px] uppercase tracking-[0.18em] text-fg-dim",children:a})]})]})}function Jr({slug:t,name:e,qty:n,config:a,configSummary:o,price:s,currency:l}){let{catalog:d}=Xe(),{formatMoney:p}=se(),u=d.find(k=>k.slug===t),v=u?.spokes??5,b=u?.mesh??!1,m=a.find(k=>k.gold)?.label,h=u?.finishes.find(k=>k.name===m&&k.src)??u?.finishes.find(k=>k.src),A=u?.finishes[0]?.color??Ut;return c("div",{className:"grid grid-cols-[60px_1fr_auto] items-center gap-3.5 border-b border-line py-4 last:border-b-0",children:[c("div",{className:"relative flex aspect-square items-center justify-center overflow-hidden border border-line bg-surface",children:[r("span",{className:"absolute -right-1.5 -top-1.5 z-[2] flex h-[18px] w-[18px] items-center justify-center rounded-full bg-accent font-mono text-[11px] font-bold text-bg",children:n}),h?.src?r(Rt,{src:h.src,alt:`${e.lead}${e.accent} \u2014 ${h.name}`,width:120,height:120,sizes:"60px",className:"relative block h-full w-full object-contain p-1.5"}):r("div",{className:"block w-[78%]",style:{color:A},children:r(Yt,{spokes:v,mesh:b,className:"block w-full"})})]}),c("div",{className:"flex min-w-0 flex-col gap-0.5",children:[c("span",{className:"font-display text-[17px] font-bold uppercase leading-none tracking-[-0.005em] text-fg",children:[e.lead,r("em",{className:"font-serif font-normal italic text-accent tracking-[-0.005em] normal-case",children:e.accent})]}),r("span",{className:"font-mono text-[12px] uppercase leading-[1.5] tracking-[0.18em] text-fg-dim",children:o})]}),r("span",{className:"text-right font-display text-[17px] font-bold tracking-[-0.005em] text-fg",children:p(s,l)})]})}function ce({label:t,value:e}){return c("div",{className:"flex items-center justify-between font-mono text-[13px] font-medium uppercase tracking-[0.18em] text-fg",children:[r("span",{children:t}),r("strong",{className:"font-display text-[16px] font-medium tracking-[-0.005em] normal-case text-fg",children:e})]})}function Xr(t){return t.map(e=>e.label).join(" \xB7 ")}import{jsx as ze}from"react/jsx-runtime";function Zr(){let{settings:t,websiteName:e}=$t(),n=e?{...Ze,name:e}:Ze,a=t?{url:t.logoUrl,reversed:t.logoReversed??null,inkIsDark:t.logoInkIsDark??null,reverse:t.reverseLogo,size:t.logoSize,sizePx:t.logoSizePx}:null;return ze("div",{style:{position:"fixed",inset:0,zIndex:2147482e3,overflowY:"auto",background:"var(--bg)",color:"var(--fg)"},children:ze(Ot,{children:ze(Ft,{preview:!0,children:ze(lt,{site:n,logo:a,preview:!0})})})})}export{Zr as default};
|