@zappie/sdk 0.1.17 → 0.1.19
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 +12 -0
- package/dist/checkout/embed.d.ts +148 -0
- package/dist/checkout/embed.js +240 -0
- package/dist/checkout/embed.js.map +7 -0
- package/dist/checkout/react.d.ts +150 -0
- package/dist/checkout/react.js +39 -0
- package/dist/checkout/react.js.map +7 -0
- package/dist/debug.js +1 -1
- package/dist/index.js +1 -1
- package/dist/studio/index.d.ts +39 -0
- package/dist/studio/index.js +314 -0
- package/dist/template/react.js +19 -0
- package/dist/template/react.js.map +2 -2
- package/package.json +14 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @zappie/sdk
|
|
2
2
|
|
|
3
|
+
## 0.1.19
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 628ac42: Add `@zappie/sdk/studio`, the Studio region selector. On the Studio dev server, `TemplateSettingsProvider` loads it in draft mode and posts `selectable` after `ready`. It outlines the region under the pointer with its merchant-facing name, steps out and back in with the arrow keys, moves through regions with Tab, and selects with a click, Enter or Space without following links or pressing buttons. It posts the selection as `region` and Escape as `select-cancel`, and announces names through a live region. It takes `select` only from the framing Studio dashboard in `STUDIO_ORIGINS`. Kit parts are named by `KIT_REGION_NAMES`. A production build drops the loader, so stand-in previews and live sites load none of it and post `selectable` with `on: false`.
|
|
8
|
+
|
|
9
|
+
## 0.1.18
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- f0c08ed: Open checkout over the page. `embedCheckout()` from `@zappie/sdk/checkout/embed` frames a checkout session as a modal on desktop or a bottom sheet on mobile, and redirects in in-app browsers and framed pages. Back and Escape ask the checkout to step back or close, and once a payment has started the checkout asks the shopper before it closes. A completed result waits for the server to confirm the session, and a completed session can't be reopened. `resumeCheckout()` drops a stale checkout entry from the address without reopening it. `useCheckout()` from `@zappie/sdk/checkout/react` creates the session and opens it.
|
|
14
|
+
|
|
3
15
|
## 0.1.17
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { WireLine, WireMoney } from '@zappie/sdk';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Address fields you can send with a delivery checkout request.
|
|
5
|
+
*/
|
|
6
|
+
type CheckoutAddress = {
|
|
7
|
+
line1?: string;
|
|
8
|
+
city?: string;
|
|
9
|
+
place_id?: string;
|
|
10
|
+
lat?: number;
|
|
11
|
+
lng?: number;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Pickup or delivery details for a checkout session, including an optional schedule.
|
|
15
|
+
*/
|
|
16
|
+
type CheckoutFulfilment = {
|
|
17
|
+
/** Dine-in orders are not checked out through a session. */
|
|
18
|
+
type: "pickup" | "delivery";
|
|
19
|
+
address?: CheckoutAddress;
|
|
20
|
+
scheduled_for?: string | null;
|
|
21
|
+
delivery_region_id?: string;
|
|
22
|
+
instructions?: string;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Cart lines, branch, fulfilment and return URLs you send to open checkout.
|
|
26
|
+
*/
|
|
27
|
+
type CheckoutRequest = {
|
|
28
|
+
branch_id: string;
|
|
29
|
+
menu_version: string;
|
|
30
|
+
lines: WireLine[];
|
|
31
|
+
fulfilment: CheckoutFulfilment;
|
|
32
|
+
contact_phone?: string;
|
|
33
|
+
promo_code?: string;
|
|
34
|
+
return_url: string;
|
|
35
|
+
cancel_url?: string;
|
|
36
|
+
source?: string;
|
|
37
|
+
campaign?: string;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* A checkout session returned by the service, with its amount, expiry and order status.
|
|
41
|
+
*/
|
|
42
|
+
type CheckoutSession = {
|
|
43
|
+
id: string;
|
|
44
|
+
status: "open" | "completed" | "expired" | "cancelled";
|
|
45
|
+
url?: string;
|
|
46
|
+
amount: WireMoney;
|
|
47
|
+
order_id: string | null;
|
|
48
|
+
return_url: string;
|
|
49
|
+
cancel_url?: string;
|
|
50
|
+
expires_at: string;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* A checkout session with a payment URL you can use to send the shopper to checkout.
|
|
54
|
+
*/
|
|
55
|
+
type OpenCheckoutSession = CheckoutSession & {
|
|
56
|
+
url: string;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* A terminal checkout result: a completed order, an expired session or a cancelled session.
|
|
60
|
+
*/
|
|
61
|
+
type CheckoutCompletion = (CheckoutSession & {
|
|
62
|
+
status: "completed";
|
|
63
|
+
order_id: string;
|
|
64
|
+
}) | (CheckoutSession & {
|
|
65
|
+
status: "expired" | "cancelled";
|
|
66
|
+
});
|
|
67
|
+
/**
|
|
68
|
+
* Optional customer API base URL and fetch implementation for the checkout client.
|
|
69
|
+
*/
|
|
70
|
+
type CheckoutClientOptions = {
|
|
71
|
+
customerBase?: string;
|
|
72
|
+
fetch?: typeof fetch;
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Cancellation signal and polling interval in milliseconds for awaiting checkout completion.
|
|
76
|
+
*/
|
|
77
|
+
type AwaitCompletionOptions = {
|
|
78
|
+
signal?: AbortSignal;
|
|
79
|
+
intervalMs?: number;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Create a client that opens checkout sessions, reads them and polls for completion.
|
|
84
|
+
*
|
|
85
|
+
* @param options - Override the default `/_zappie/api` base or fetch implementation.
|
|
86
|
+
* @returns A checkout client. Retain each idempotency key when retrying the same cart.
|
|
87
|
+
*/
|
|
88
|
+
declare function createCheckout(options?: CheckoutClientOptions): {
|
|
89
|
+
/** The caller retains this key and reuses it when retrying the same cart. */
|
|
90
|
+
open(body: CheckoutRequest, idempotencyKey: string): Promise<OpenCheckoutSession>;
|
|
91
|
+
session(id: string): Promise<CheckoutSession>;
|
|
92
|
+
awaitCompletion(id: string, options?: AwaitCompletionOptions): Promise<CheckoutCompletion>;
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* The session creation, lookup and completion methods returned by createCheckout.
|
|
96
|
+
*/
|
|
97
|
+
type CheckoutClient = ReturnType<typeof createCheckout>;
|
|
98
|
+
|
|
99
|
+
/** How checkout opens. Auto redirects in in-app browsers and framed pages, else a modal on desktop and a sheet on mobile. */
|
|
100
|
+
type CheckoutPresentation = "auto" | "modal" | "sheet" | "fullscreen" | "redirect";
|
|
101
|
+
/** The checkout look from toCheckoutTheme in @zappie/tokens/checkout. The checkout validates it before use. */
|
|
102
|
+
type CheckoutTheme = {
|
|
103
|
+
version: 1;
|
|
104
|
+
tokens: Partial<Record<`--z-${string}`, string>>;
|
|
105
|
+
logo?: {
|
|
106
|
+
url: string;
|
|
107
|
+
alt: string;
|
|
108
|
+
};
|
|
109
|
+
fonts?: {
|
|
110
|
+
family: string;
|
|
111
|
+
role: "body" | "display";
|
|
112
|
+
weights?: number[];
|
|
113
|
+
}[];
|
|
114
|
+
copy?: {
|
|
115
|
+
payButton?: string;
|
|
116
|
+
successTitle?: string;
|
|
117
|
+
};
|
|
118
|
+
};
|
|
119
|
+
/** Presentation, theme and step reporting for an embedded checkout. */
|
|
120
|
+
type EmbedOptions = {
|
|
121
|
+
presentation?: CheckoutPresentation;
|
|
122
|
+
theme?: CheckoutTheme;
|
|
123
|
+
/** Called with each checkout step, and whether a payment has started. */
|
|
124
|
+
onStep?: (step: string, paying: boolean) => void;
|
|
125
|
+
};
|
|
126
|
+
/** How an embedded checkout ended. A completed result is confirmed by the server first. */
|
|
127
|
+
type CheckoutResult = {
|
|
128
|
+
status: "completed";
|
|
129
|
+
orderId: string;
|
|
130
|
+
} | {
|
|
131
|
+
status: "cancelled";
|
|
132
|
+
reason: string;
|
|
133
|
+
} | {
|
|
134
|
+
status: "redirected";
|
|
135
|
+
};
|
|
136
|
+
/** Resolve auto to the presentation this browser gets. */
|
|
137
|
+
declare function checkoutPresentation(presentation?: CheckoutPresentation): Exclude<CheckoutPresentation, "auto">;
|
|
138
|
+
/** Ask the open checkout to close. Mid-payment it asks the shopper first. */
|
|
139
|
+
declare function closeCheckout(): void;
|
|
140
|
+
/** On load, drop a stale checkout entry from the address and read its session. It never reopens checkout. */
|
|
141
|
+
declare function resumeCheckout(checkout: CheckoutClient): Promise<CheckoutResult | null>;
|
|
142
|
+
/**
|
|
143
|
+
* Open a checkout session over the page and settle when it ends. Only Zappie's checkout, or one on this machine, is framed. Back and Escape ask the checkout to step back or
|
|
144
|
+
* close. A completed result waits for the server to confirm the session. A completed session can't be reopened.
|
|
145
|
+
*/
|
|
146
|
+
declare function embedCheckout(checkout: CheckoutClient, session: OpenCheckoutSession, options?: EmbedOptions): Promise<CheckoutResult>;
|
|
147
|
+
|
|
148
|
+
export { type CheckoutPresentation, type CheckoutResult, type CheckoutTheme, type EmbedOptions, checkoutPresentation, closeCheckout, embedCheckout, resumeCheckout };
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
// src/checkout/embed.ts
|
|
2
|
+
import { ZappieError } from "./sdk.js";
|
|
3
|
+
var PARAM = "zappie_checkout";
|
|
4
|
+
var finished = /* @__PURE__ */ new Set();
|
|
5
|
+
var request;
|
|
6
|
+
var media = (query) => matchMedia(query).matches;
|
|
7
|
+
function checkoutPresentation(presentation = "auto") {
|
|
8
|
+
if (presentation !== "auto") {
|
|
9
|
+
return presentation;
|
|
10
|
+
}
|
|
11
|
+
if (window.top !== window || /FBA[NV]|Instagram|TikTok|musical_ly|Bytedance|Line\//.test(
|
|
12
|
+
navigator.userAgent
|
|
13
|
+
)) {
|
|
14
|
+
return "redirect";
|
|
15
|
+
}
|
|
16
|
+
return media("(min-width:640px) and (pointer:fine),(min-width:1024px)") ? "modal" : "sheet";
|
|
17
|
+
}
|
|
18
|
+
function closeCheckout() {
|
|
19
|
+
request?.();
|
|
20
|
+
}
|
|
21
|
+
async function resumeCheckout(checkout) {
|
|
22
|
+
const url = new URL(location.href);
|
|
23
|
+
const id = url.searchParams.get(PARAM);
|
|
24
|
+
if (!id) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
url.searchParams.delete(PARAM);
|
|
28
|
+
history.replaceState(history.state, "", url);
|
|
29
|
+
const session = await checkout.session(id);
|
|
30
|
+
return session.status === "completed" && session.order_id ? { status: "completed", orderId: session.order_id } : null;
|
|
31
|
+
}
|
|
32
|
+
function embedCheckout(checkout, session, options = {}) {
|
|
33
|
+
const url = new URL(session.url);
|
|
34
|
+
if (request || session.status !== "open" || finished.has(session.id) || !(/^https:\/\/checkout\.(stg\.)?zappie\.ai$/.test(url.origin) || /^http:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/.test(url.origin))) {
|
|
35
|
+
return Promise.reject(
|
|
36
|
+
new ZappieError(
|
|
37
|
+
"invalid_request",
|
|
38
|
+
"This checkout session can't be opened"
|
|
39
|
+
)
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
const shown = checkoutPresentation(options.presentation);
|
|
43
|
+
if (shown === "redirect") {
|
|
44
|
+
location.assign(session.url);
|
|
45
|
+
return Promise.resolve({ status: "redirected" });
|
|
46
|
+
}
|
|
47
|
+
url.pathname = `/embed/${encodeURIComponent(session.id)}`;
|
|
48
|
+
const doc = document;
|
|
49
|
+
const body = doc.body;
|
|
50
|
+
const root = doc.createElement("div");
|
|
51
|
+
const panel = doc.createElement("div");
|
|
52
|
+
const handle = doc.createElement("div");
|
|
53
|
+
const frame = doc.createElement("iframe");
|
|
54
|
+
const sheet = shown === "sheet";
|
|
55
|
+
const still = media("(prefers-reduced-motion:reduce)");
|
|
56
|
+
const opener = doc.activeElement;
|
|
57
|
+
const y = scrollY;
|
|
58
|
+
const bodyStyle = body.style.cssText;
|
|
59
|
+
const restoration = history.scrollRestoration;
|
|
60
|
+
const inert = [...body.children].filter(
|
|
61
|
+
(el) => !el.inert
|
|
62
|
+
);
|
|
63
|
+
const abort = new AbortController();
|
|
64
|
+
const vv = window.visualViewport;
|
|
65
|
+
let ready = false;
|
|
66
|
+
let paying = false;
|
|
67
|
+
let height = 640;
|
|
68
|
+
let settle = () => {
|
|
69
|
+
};
|
|
70
|
+
root.setAttribute("data-zappie-checkout", shown);
|
|
71
|
+
root.setAttribute("role", "dialog");
|
|
72
|
+
root.setAttribute("aria-modal", "true");
|
|
73
|
+
root.setAttribute("aria-label", "Checkout");
|
|
74
|
+
root.style.cssText = "position:fixed;inset:0;z-index:2147483647;background:var(--z-checkout-backdrop,rgb(0 0 0/.5))";
|
|
75
|
+
panel.style.cssText = `position:absolute;overflow:hidden;display:flex;flex-direction:column;background:var(--z-surface,#fff);${shown === "fullscreen" ? "inset:0" : sheet ? "left:0;right:0;border-radius:var(--z-checkout-radius,16px) var(--z-checkout-radius,16px) 0 0" : "inset:0;margin:auto;width:min(480px,100vw - 32px);border-radius:var(--z-checkout-radius,16px)"}`;
|
|
76
|
+
handle.style.cssText = "flex:none;height:24px;touch-action:none;cursor:grab;background:radial-gradient(closest-side,var(--z-border,#ccc) 99%,#0000) 50%/40px 5px no-repeat";
|
|
77
|
+
frame.title = "Checkout";
|
|
78
|
+
frame.src = url.href;
|
|
79
|
+
frame.allow = "payment 'src'; publickey-credentials-get 'src'";
|
|
80
|
+
frame.referrerPolicy = "no-referrer";
|
|
81
|
+
frame.style.cssText = "flex:1;width:100%;border:0;display:block";
|
|
82
|
+
if (sheet) {
|
|
83
|
+
panel.append(handle);
|
|
84
|
+
}
|
|
85
|
+
panel.append(frame);
|
|
86
|
+
root.append(panel);
|
|
87
|
+
const layout = () => {
|
|
88
|
+
const view = vv ?? { height: innerHeight, offsetTop: 0 };
|
|
89
|
+
if (sheet) {
|
|
90
|
+
panel.style.height = `${Math.round(view.height * 0.92)}px`;
|
|
91
|
+
panel.style.bottom = `${Math.max(0, innerHeight - view.offsetTop - view.height)}px`;
|
|
92
|
+
} else if (shown === "modal") {
|
|
93
|
+
panel.style.height = `${Math.min(height, view.height - 32)}px`;
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
const post = (type, payload = {}) => frame.contentWindow?.postMessage(
|
|
97
|
+
{ source: "zappie-checkout", v: 1, session: session.id, type, payload },
|
|
98
|
+
url.origin
|
|
99
|
+
);
|
|
100
|
+
const finish = (result, popped) => {
|
|
101
|
+
abort.abort();
|
|
102
|
+
request = void 0;
|
|
103
|
+
removeEventListener("message", onMessage);
|
|
104
|
+
removeEventListener("popstate", onPop);
|
|
105
|
+
removeEventListener("keydown", onKey);
|
|
106
|
+
vv?.removeEventListener("resize", layout);
|
|
107
|
+
vv?.removeEventListener("scroll", layout);
|
|
108
|
+
root.remove();
|
|
109
|
+
inert.forEach((el) => el.inert = false);
|
|
110
|
+
body.style.cssText = bodyStyle;
|
|
111
|
+
const restore = () => {
|
|
112
|
+
scrollTo(0, y);
|
|
113
|
+
setTimeout(() => {
|
|
114
|
+
scrollTo(0, y);
|
|
115
|
+
history.scrollRestoration = restoration;
|
|
116
|
+
});
|
|
117
|
+
};
|
|
118
|
+
restore();
|
|
119
|
+
opener?.focus({ preventScroll: true });
|
|
120
|
+
if (!popped && history.state?.[PARAM] === session.id) {
|
|
121
|
+
history.scrollRestoration = "manual";
|
|
122
|
+
addEventListener("popstate", restore, { once: true });
|
|
123
|
+
history.back();
|
|
124
|
+
}
|
|
125
|
+
settle(result);
|
|
126
|
+
};
|
|
127
|
+
const close = () => ready ? post("close_request") : finish({ status: "cancelled", reason: "user" });
|
|
128
|
+
const onKey = (event) => {
|
|
129
|
+
if (event.key === "Escape") {
|
|
130
|
+
close();
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
const onPop = () => {
|
|
134
|
+
if (!ready) {
|
|
135
|
+
return finish({ status: "cancelled", reason: "user" }, true);
|
|
136
|
+
}
|
|
137
|
+
history.pushState(
|
|
138
|
+
{ ...history.state, [PARAM]: session.id },
|
|
139
|
+
"",
|
|
140
|
+
location.href
|
|
141
|
+
);
|
|
142
|
+
post("close_request");
|
|
143
|
+
};
|
|
144
|
+
const onMessage = (event) => {
|
|
145
|
+
const data = event.data;
|
|
146
|
+
if (event.origin !== url.origin || event.source !== frame.contentWindow || data?.source !== "zappie-checkout" || data.v !== 1 || data.session !== session.id) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const payload = Object(data.payload);
|
|
150
|
+
if (data.type === "ready" || data.type === "step") {
|
|
151
|
+
if (data.type === "ready") {
|
|
152
|
+
ready = true;
|
|
153
|
+
frame.focus();
|
|
154
|
+
if (options.theme) {
|
|
155
|
+
post("theme", options.theme);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
paying = payload.paying === true;
|
|
159
|
+
if (typeof payload.step === "string") {
|
|
160
|
+
options.onStep?.(payload.step, paying);
|
|
161
|
+
}
|
|
162
|
+
} else if (data.type === "resize" && Number.isFinite(payload.height)) {
|
|
163
|
+
height = payload.height;
|
|
164
|
+
layout();
|
|
165
|
+
} else if (data.type === "completed") {
|
|
166
|
+
checkout.awaitCompletion(session.id, { signal: abort.signal, intervalMs: 1500 }).then(
|
|
167
|
+
(done) => {
|
|
168
|
+
if (done.status === "completed") {
|
|
169
|
+
finished.add(session.id);
|
|
170
|
+
finish({ status: "completed", orderId: done.order_id });
|
|
171
|
+
} else {
|
|
172
|
+
finish({ status: "cancelled", reason: done.status });
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
() => {
|
|
176
|
+
}
|
|
177
|
+
);
|
|
178
|
+
} else if (data.type === "cancelled" || data.type === "error") {
|
|
179
|
+
finish({
|
|
180
|
+
status: "cancelled",
|
|
181
|
+
reason: String(payload.reason ?? payload.code ?? "user")
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
let start = 0;
|
|
186
|
+
handle.onpointerdown = (event) => {
|
|
187
|
+
if (paying) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
start = event.clientY;
|
|
191
|
+
handle.setPointerCapture?.(event.pointerId);
|
|
192
|
+
};
|
|
193
|
+
handle.onpointermove = (event) => {
|
|
194
|
+
if (start) {
|
|
195
|
+
panel.style.transform = `translateY(${Math.max(0, event.clientY - start)}px)`;
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
handle.onpointerup = handle.onpointercancel = (event) => {
|
|
199
|
+
const moved = start ? event.clientY - start : 0;
|
|
200
|
+
start = 0;
|
|
201
|
+
panel.style.transform = "";
|
|
202
|
+
if (!paying && moved > panel.offsetHeight / 4) {
|
|
203
|
+
finish({ status: "cancelled", reason: "user" });
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
history.pushState(
|
|
207
|
+
{ ...history.state, [PARAM]: session.id },
|
|
208
|
+
"",
|
|
209
|
+
(() => {
|
|
210
|
+
const here = new URL(location.href);
|
|
211
|
+
here.searchParams.set(PARAM, session.id);
|
|
212
|
+
return here;
|
|
213
|
+
})()
|
|
214
|
+
);
|
|
215
|
+
addEventListener("message", onMessage);
|
|
216
|
+
addEventListener("popstate", onPop);
|
|
217
|
+
addEventListener("keydown", onKey);
|
|
218
|
+
vv?.addEventListener("resize", layout);
|
|
219
|
+
vv?.addEventListener("scroll", layout);
|
|
220
|
+
inert.forEach((el) => el.inert = true);
|
|
221
|
+
history.scrollRestoration = "manual";
|
|
222
|
+
body.style.cssText += `;position:fixed;top:${-y}px;left:0;right:0;overflow:hidden`;
|
|
223
|
+
body.append(root);
|
|
224
|
+
layout();
|
|
225
|
+
if (!still) {
|
|
226
|
+
panel.animate?.(
|
|
227
|
+
sheet ? [{ transform: "translateY(100%)" }, { transform: "none" }] : [{ opacity: 0 }, { opacity: 1 }],
|
|
228
|
+
{ duration: sheet ? 280 : 160, easing: "cubic-bezier(.2,.8,.2,1)" }
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
request = close;
|
|
232
|
+
return new Promise((resolve) => settle = resolve);
|
|
233
|
+
}
|
|
234
|
+
export {
|
|
235
|
+
checkoutPresentation,
|
|
236
|
+
closeCheckout,
|
|
237
|
+
embedCheckout,
|
|
238
|
+
resumeCheckout
|
|
239
|
+
};
|
|
240
|
+
//# sourceMappingURL=embed.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/checkout/embed.ts"],
|
|
4
|
+
"sourcesContent": ["import type { CheckoutClient } from \"./client\";\nimport { ZappieError } from \"./sdk\";\nimport type { OpenCheckoutSession } from \"./types\";\n\n/** How checkout opens. Auto redirects in in-app browsers and framed pages, else a modal on desktop and a sheet on mobile. */\nexport type CheckoutPresentation =\n \"auto\" | \"modal\" | \"sheet\" | \"fullscreen\" | \"redirect\";\n\n/** The checkout look from toCheckoutTheme in @zappie/tokens/checkout. The checkout validates it before use. */\nexport type CheckoutTheme = {\n version: 1;\n tokens: Partial<Record<`--z-${string}`, string>>;\n logo?: { url: string; alt: string };\n fonts?: { family: string; role: \"body\" | \"display\"; weights?: number[] }[];\n copy?: { payButton?: string; successTitle?: string };\n};\n\n/** Presentation, theme and step reporting for an embedded checkout. */\nexport type EmbedOptions = {\n presentation?: CheckoutPresentation;\n theme?: CheckoutTheme;\n /** Called with each checkout step, and whether a payment has started. */\n onStep?: (step: string, paying: boolean) => void;\n};\n\n/** How an embedded checkout ended. A completed result is confirmed by the server first. */\nexport type CheckoutResult =\n | { status: \"completed\"; orderId: string }\n | { status: \"cancelled\"; reason: string }\n | { status: \"redirected\" };\n\nconst PARAM = \"zappie_checkout\";\nconst finished = new Set<string>();\nlet request: (() => void) | undefined;\nconst media = (query: string) => matchMedia(query).matches;\n\n/** Resolve auto to the presentation this browser gets. */\nexport function checkoutPresentation(\n presentation: CheckoutPresentation = \"auto\",\n): Exclude<CheckoutPresentation, \"auto\"> {\n if (presentation !== \"auto\") {\n return presentation;\n }\n if (\n window.top !== window ||\n /FBA[NV]|Instagram|TikTok|musical_ly|Bytedance|Line\\//.test(\n navigator.userAgent,\n )\n ) {\n return \"redirect\";\n }\n return media(\"(min-width:640px) and (pointer:fine),(min-width:1024px)\")\n ? \"modal\"\n : \"sheet\";\n}\n\n/** Ask the open checkout to close. Mid-payment it asks the shopper first. */\nexport function closeCheckout(): void {\n request?.();\n}\n\n/** On load, drop a stale checkout entry from the address and read its session. It never reopens checkout. */\nexport async function resumeCheckout(\n checkout: CheckoutClient,\n): Promise<CheckoutResult | null> {\n const url = new URL(location.href);\n const id = url.searchParams.get(PARAM);\n if (!id) {\n return null;\n }\n url.searchParams.delete(PARAM);\n history.replaceState(history.state, \"\", url);\n const session = await checkout.session(id);\n return session.status === \"completed\" && session.order_id\n ? { status: \"completed\", orderId: session.order_id }\n : null;\n}\n\n/**\n * Open a checkout session over the page and settle when it ends. Only Zappie's checkout, or one on this machine, is framed. Back and Escape ask the checkout to step back or\n * close. A completed result waits for the server to confirm the session. A completed session can't be reopened.\n */\nexport function embedCheckout(\n checkout: CheckoutClient,\n session: OpenCheckoutSession,\n options: EmbedOptions = {},\n): Promise<CheckoutResult> {\n const url = new URL(session.url);\n if (\n request ||\n session.status !== \"open\" ||\n finished.has(session.id) ||\n !(\n /^https:\\/\\/checkout\\.(stg\\.)?zappie\\.ai$/.test(url.origin) ||\n /^http:\\/\\/(localhost|127\\.0\\.0\\.1)(:\\d+)?$/.test(url.origin)\n )\n ) {\n return Promise.reject(\n new ZappieError(\n \"invalid_request\",\n \"This checkout session can't be opened\",\n ),\n );\n }\n const shown = checkoutPresentation(options.presentation);\n if (shown === \"redirect\") {\n location.assign(session.url);\n return Promise.resolve({ status: \"redirected\" });\n }\n url.pathname = `/embed/${encodeURIComponent(session.id)}`;\n const doc = document;\n const body = doc.body;\n const root = doc.createElement(\"div\");\n const panel = doc.createElement(\"div\");\n const handle = doc.createElement(\"div\");\n const frame = doc.createElement(\"iframe\");\n const sheet = shown === \"sheet\";\n const still = media(\"(prefers-reduced-motion:reduce)\");\n const opener = doc.activeElement as HTMLElement | null;\n const y = scrollY;\n const bodyStyle = body.style.cssText;\n const restoration = history.scrollRestoration;\n const inert = [...body.children].filter(\n (el): el is HTMLElement => !(el as HTMLElement).inert,\n );\n const abort = new AbortController();\n const vv = window.visualViewport;\n let ready = false;\n let paying = false;\n let height = 640;\n let settle: (result: CheckoutResult) => void = () => {};\n\n root.setAttribute(\"data-zappie-checkout\", shown);\n root.setAttribute(\"role\", \"dialog\");\n root.setAttribute(\"aria-modal\", \"true\");\n root.setAttribute(\"aria-label\", \"Checkout\");\n root.style.cssText =\n \"position:fixed;inset:0;z-index:2147483647;background:var(--z-checkout-backdrop,rgb(0 0 0/.5))\";\n panel.style.cssText = `position:absolute;overflow:hidden;display:flex;flex-direction:column;background:var(--z-surface,#fff);${\n shown === \"fullscreen\"\n ? \"inset:0\"\n : sheet\n ? \"left:0;right:0;border-radius:var(--z-checkout-radius,16px) var(--z-checkout-radius,16px) 0 0\"\n : \"inset:0;margin:auto;width:min(480px,100vw - 32px);border-radius:var(--z-checkout-radius,16px)\"\n }`;\n handle.style.cssText =\n \"flex:none;height:24px;touch-action:none;cursor:grab;background:radial-gradient(closest-side,var(--z-border,#ccc) 99%,#0000) 50%/40px 5px no-repeat\";\n frame.title = \"Checkout\";\n frame.src = url.href;\n frame.allow = \"payment 'src'; publickey-credentials-get 'src'\";\n frame.referrerPolicy = \"no-referrer\";\n frame.style.cssText = \"flex:1;width:100%;border:0;display:block\";\n if (sheet) {\n panel.append(handle);\n }\n panel.append(frame);\n root.append(panel);\n\n const layout = () => {\n const view = vv ?? { height: innerHeight, offsetTop: 0 };\n if (sheet) {\n panel.style.height = `${Math.round(view.height * 0.92)}px`;\n panel.style.bottom = `${Math.max(0, innerHeight - view.offsetTop - view.height)}px`;\n } else if (shown === \"modal\") {\n panel.style.height = `${Math.min(height, view.height - 32)}px`;\n }\n };\n const post = (type: string, payload: object = {}) =>\n frame.contentWindow?.postMessage(\n { source: \"zappie-checkout\", v: 1, session: session.id, type, payload },\n url.origin,\n );\n const finish = (result: CheckoutResult, popped?: boolean) => {\n abort.abort();\n request = undefined;\n removeEventListener(\"message\", onMessage);\n removeEventListener(\"popstate\", onPop);\n removeEventListener(\"keydown\", onKey);\n vv?.removeEventListener(\"resize\", layout);\n vv?.removeEventListener(\"scroll\", layout);\n root.remove();\n inert.forEach((el) => (el.inert = false));\n body.style.cssText = bodyStyle;\n // WebKit scrolls the page again after popstate, so the scroll is set once more a task later.\n const restore = () => {\n scrollTo(0, y);\n setTimeout(() => {\n scrollTo(0, y);\n history.scrollRestoration = restoration;\n });\n };\n restore();\n opener?.focus({ preventScroll: true });\n if (!popped && history.state?.[PARAM] === session.id) {\n history.scrollRestoration = \"manual\";\n addEventListener(\"popstate\", restore, { once: true });\n history.back();\n }\n settle(result);\n };\n const close = () =>\n ready\n ? post(\"close_request\")\n : finish({ status: \"cancelled\", reason: \"user\" });\n const onKey = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") {\n close();\n }\n };\n const onPop = () => {\n if (!ready) {\n return finish({ status: \"cancelled\", reason: \"user\" }, true);\n }\n history.pushState(\n { ...history.state, [PARAM]: session.id },\n \"\",\n location.href,\n );\n post(\"close_request\");\n };\n const onMessage = (event: MessageEvent) => {\n const data = event.data;\n if (\n event.origin !== url.origin ||\n event.source !== frame.contentWindow ||\n data?.source !== \"zappie-checkout\" ||\n data.v !== 1 ||\n data.session !== session.id\n ) {\n return;\n }\n const payload = Object(data.payload);\n if (data.type === \"ready\" || data.type === \"step\") {\n if (data.type === \"ready\") {\n ready = true;\n frame.focus();\n if (options.theme) {\n post(\"theme\", options.theme);\n }\n }\n paying = payload.paying === true;\n if (typeof payload.step === \"string\") {\n options.onStep?.(payload.step, paying);\n }\n } else if (data.type === \"resize\" && Number.isFinite(payload.height)) {\n height = payload.height;\n layout();\n } else if (data.type === \"completed\") {\n checkout\n .awaitCompletion(session.id, { signal: abort.signal, intervalMs: 1500 })\n .then(\n (done) => {\n if (done.status === \"completed\") {\n finished.add(session.id);\n finish({ status: \"completed\", orderId: done.order_id });\n } else {\n finish({ status: \"cancelled\", reason: done.status });\n }\n },\n () => {},\n );\n } else if (data.type === \"cancelled\" || data.type === \"error\") {\n finish({\n status: \"cancelled\",\n reason: String(payload.reason ?? payload.code ?? \"user\"),\n });\n }\n };\n\n let start = 0;\n handle.onpointerdown = (event) => {\n if (paying) {\n return;\n }\n start = event.clientY;\n handle.setPointerCapture?.(event.pointerId);\n };\n handle.onpointermove = (event) => {\n if (start) {\n panel.style.transform = `translateY(${Math.max(0, event.clientY - start)}px)`;\n }\n };\n handle.onpointerup = handle.onpointercancel = (event) => {\n const moved = start ? event.clientY - start : 0;\n start = 0;\n panel.style.transform = \"\";\n if (!paying && moved > panel.offsetHeight / 4) {\n finish({ status: \"cancelled\", reason: \"user\" });\n }\n };\n\n history.pushState(\n { ...history.state, [PARAM]: session.id },\n \"\",\n (() => {\n const here = new URL(location.href);\n here.searchParams.set(PARAM, session.id);\n return here;\n })(),\n );\n addEventListener(\"message\", onMessage);\n addEventListener(\"popstate\", onPop);\n addEventListener(\"keydown\", onKey);\n vv?.addEventListener(\"resize\", layout);\n vv?.addEventListener(\"scroll\", layout);\n inert.forEach((el) => (el.inert = true));\n history.scrollRestoration = \"manual\";\n body.style.cssText += `;position:fixed;top:${-y}px;left:0;right:0;overflow:hidden`;\n body.append(root);\n layout();\n if (!still) {\n panel.animate?.(\n sheet\n ? [{ transform: \"translateY(100%)\" }, { transform: \"none\" }]\n : [{ opacity: 0 }, { opacity: 1 }],\n { duration: sheet ? 280 : 160, easing: \"cubic-bezier(.2,.8,.2,1)\" },\n );\n }\n request = close;\n return new Promise((resolve) => (settle = resolve));\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { WireLine, WireMoney } from '@zappie/sdk';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Address fields you can send with a delivery checkout request.
|
|
5
|
+
*/
|
|
6
|
+
type CheckoutAddress = {
|
|
7
|
+
line1?: string;
|
|
8
|
+
city?: string;
|
|
9
|
+
place_id?: string;
|
|
10
|
+
lat?: number;
|
|
11
|
+
lng?: number;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Pickup or delivery details for a checkout session, including an optional schedule.
|
|
15
|
+
*/
|
|
16
|
+
type CheckoutFulfilment = {
|
|
17
|
+
/** Dine-in orders are not checked out through a session. */
|
|
18
|
+
type: "pickup" | "delivery";
|
|
19
|
+
address?: CheckoutAddress;
|
|
20
|
+
scheduled_for?: string | null;
|
|
21
|
+
delivery_region_id?: string;
|
|
22
|
+
instructions?: string;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Cart lines, branch, fulfilment and return URLs you send to open checkout.
|
|
26
|
+
*/
|
|
27
|
+
type CheckoutRequest = {
|
|
28
|
+
branch_id: string;
|
|
29
|
+
menu_version: string;
|
|
30
|
+
lines: WireLine[];
|
|
31
|
+
fulfilment: CheckoutFulfilment;
|
|
32
|
+
contact_phone?: string;
|
|
33
|
+
promo_code?: string;
|
|
34
|
+
return_url: string;
|
|
35
|
+
cancel_url?: string;
|
|
36
|
+
source?: string;
|
|
37
|
+
campaign?: string;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* A checkout session returned by the service, with its amount, expiry and order status.
|
|
41
|
+
*/
|
|
42
|
+
type CheckoutSession = {
|
|
43
|
+
id: string;
|
|
44
|
+
status: "open" | "completed" | "expired" | "cancelled";
|
|
45
|
+
url?: string;
|
|
46
|
+
amount: WireMoney;
|
|
47
|
+
order_id: string | null;
|
|
48
|
+
return_url: string;
|
|
49
|
+
cancel_url?: string;
|
|
50
|
+
expires_at: string;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* A checkout session with a payment URL you can use to send the shopper to checkout.
|
|
54
|
+
*/
|
|
55
|
+
type OpenCheckoutSession = CheckoutSession & {
|
|
56
|
+
url: string;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* A terminal checkout result: a completed order, an expired session or a cancelled session.
|
|
60
|
+
*/
|
|
61
|
+
type CheckoutCompletion = (CheckoutSession & {
|
|
62
|
+
status: "completed";
|
|
63
|
+
order_id: string;
|
|
64
|
+
}) | (CheckoutSession & {
|
|
65
|
+
status: "expired" | "cancelled";
|
|
66
|
+
});
|
|
67
|
+
/**
|
|
68
|
+
* Optional customer API base URL and fetch implementation for the checkout client.
|
|
69
|
+
*/
|
|
70
|
+
type CheckoutClientOptions = {
|
|
71
|
+
customerBase?: string;
|
|
72
|
+
fetch?: typeof fetch;
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Cancellation signal and polling interval in milliseconds for awaiting checkout completion.
|
|
76
|
+
*/
|
|
77
|
+
type AwaitCompletionOptions = {
|
|
78
|
+
signal?: AbortSignal;
|
|
79
|
+
intervalMs?: number;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Create a client that opens checkout sessions, reads them and polls for completion.
|
|
84
|
+
*
|
|
85
|
+
* @param options - Override the default `/_zappie/api` base or fetch implementation.
|
|
86
|
+
* @returns A checkout client. Retain each idempotency key when retrying the same cart.
|
|
87
|
+
*/
|
|
88
|
+
declare function createCheckout(options?: CheckoutClientOptions): {
|
|
89
|
+
/** The caller retains this key and reuses it when retrying the same cart. */
|
|
90
|
+
open(body: CheckoutRequest, idempotencyKey: string): Promise<OpenCheckoutSession>;
|
|
91
|
+
session(id: string): Promise<CheckoutSession>;
|
|
92
|
+
awaitCompletion(id: string, options?: AwaitCompletionOptions): Promise<CheckoutCompletion>;
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* The session creation, lookup and completion methods returned by createCheckout.
|
|
96
|
+
*/
|
|
97
|
+
type CheckoutClient = ReturnType<typeof createCheckout>;
|
|
98
|
+
|
|
99
|
+
/** How checkout opens. Auto redirects in in-app browsers and framed pages, else a modal on desktop and a sheet on mobile. */
|
|
100
|
+
type CheckoutPresentation = "auto" | "modal" | "sheet" | "fullscreen" | "redirect";
|
|
101
|
+
/** The checkout look from toCheckoutTheme in @zappie/tokens/checkout. The checkout validates it before use. */
|
|
102
|
+
type CheckoutTheme = {
|
|
103
|
+
version: 1;
|
|
104
|
+
tokens: Partial<Record<`--z-${string}`, string>>;
|
|
105
|
+
logo?: {
|
|
106
|
+
url: string;
|
|
107
|
+
alt: string;
|
|
108
|
+
};
|
|
109
|
+
fonts?: {
|
|
110
|
+
family: string;
|
|
111
|
+
role: "body" | "display";
|
|
112
|
+
weights?: number[];
|
|
113
|
+
}[];
|
|
114
|
+
copy?: {
|
|
115
|
+
payButton?: string;
|
|
116
|
+
successTitle?: string;
|
|
117
|
+
};
|
|
118
|
+
};
|
|
119
|
+
/** Presentation, theme and step reporting for an embedded checkout. */
|
|
120
|
+
type EmbedOptions = {
|
|
121
|
+
presentation?: CheckoutPresentation;
|
|
122
|
+
theme?: CheckoutTheme;
|
|
123
|
+
/** Called with each checkout step, and whether a payment has started. */
|
|
124
|
+
onStep?: (step: string, paying: boolean) => void;
|
|
125
|
+
};
|
|
126
|
+
/** How an embedded checkout ended. A completed result is confirmed by the server first. */
|
|
127
|
+
type CheckoutResult = {
|
|
128
|
+
status: "completed";
|
|
129
|
+
orderId: string;
|
|
130
|
+
} | {
|
|
131
|
+
status: "cancelled";
|
|
132
|
+
reason: string;
|
|
133
|
+
} | {
|
|
134
|
+
status: "redirected";
|
|
135
|
+
};
|
|
136
|
+
/** Ask the open checkout to close. Mid-payment it asks the shopper first. */
|
|
137
|
+
declare function closeCheckout(): void;
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Open checkout for a cart and follow it. `open` creates the session, then embeds or redirects it as
|
|
141
|
+
* `presentation` decides, and resolves with how it ended. Retain the idempotency key when retrying a cart.
|
|
142
|
+
*/
|
|
143
|
+
declare function useCheckout(checkout: CheckoutClient): {
|
|
144
|
+
open: (body: CheckoutRequest, key: string, options?: EmbedOptions) => Promise<CheckoutResult>;
|
|
145
|
+
close: typeof closeCheckout;
|
|
146
|
+
status: "open" | "completed" | "cancelled" | "redirected" | "idle" | "opening";
|
|
147
|
+
step: string | undefined;
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
export { useCheckout };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/checkout/react.ts
|
|
4
|
+
import { useCallback, useState } from "react";
|
|
5
|
+
import {
|
|
6
|
+
closeCheckout,
|
|
7
|
+
embedCheckout
|
|
8
|
+
} from "./embed.js";
|
|
9
|
+
function useCheckout(checkout) {
|
|
10
|
+
const [status, setStatus] = useState("idle");
|
|
11
|
+
const [step, setStep] = useState();
|
|
12
|
+
const open = useCallback(
|
|
13
|
+
async (body, key, options = {}) => {
|
|
14
|
+
setStatus("opening");
|
|
15
|
+
try {
|
|
16
|
+
const session = await checkout.open(body, key);
|
|
17
|
+
setStatus("open");
|
|
18
|
+
const result = await embedCheckout(checkout, session, {
|
|
19
|
+
...options,
|
|
20
|
+
onStep: (next, paying) => {
|
|
21
|
+
setStep(next);
|
|
22
|
+
options.onStep?.(next, paying);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
setStatus(result.status);
|
|
26
|
+
return result;
|
|
27
|
+
} catch (failure) {
|
|
28
|
+
setStatus("idle");
|
|
29
|
+
throw failure;
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
[checkout]
|
|
33
|
+
);
|
|
34
|
+
return { open, close: closeCheckout, status, step };
|
|
35
|
+
}
|
|
36
|
+
export {
|
|
37
|
+
useCheckout
|
|
38
|
+
};
|
|
39
|
+
//# sourceMappingURL=react.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/checkout/react.ts"],
|
|
4
|
+
"sourcesContent": ["\"use client\";\nimport { useCallback, useState } from \"react\";\nimport type { CheckoutClient } from \"./client\";\nimport {\n closeCheckout,\n embedCheckout,\n type CheckoutResult,\n type EmbedOptions,\n} from \"./embed\";\nimport type { CheckoutRequest } from \"./types\";\n\n/**\n * Open checkout for a cart and follow it. `open` creates the session, then embeds or redirects it as\n * `presentation` decides, and resolves with how it ended. Retain the idempotency key when retrying a cart.\n */\nexport function useCheckout(checkout: CheckoutClient) {\n const [status, setStatus] = useState<\n \"idle\" | \"opening\" | \"open\" | CheckoutResult[\"status\"]\n >(\"idle\");\n const [step, setStep] = useState<string>();\n const open = useCallback(\n async (body: CheckoutRequest, key: string, options: EmbedOptions = {}) => {\n setStatus(\"opening\");\n try {\n const session = await checkout.open(body, key);\n setStatus(\"open\");\n const result = await embedCheckout(checkout, session, {\n ...options,\n onStep: (next, paying) => {\n setStep(next);\n options.onStep?.(next, paying);\n },\n });\n setStatus(result.status);\n return result;\n } catch (failure) {\n setStatus(\"idle\");\n throw failure;\n }\n },\n [checkout],\n );\n return { open, close: closeCheckout, status, step };\n}\n"],
|
|
5
|
+
"mappings": ";;;AACA,SAAS,aAAa,gBAAgB;AAEtC;AAAA,EACE;AAAA,EACA;AAAA,OAGK;AAOA,SAAS,YAAY,UAA0B;AACpD,QAAM,CAAC,QAAQ,SAAS,IAAI,SAE1B,MAAM;AACR,QAAM,CAAC,MAAM,OAAO,IAAI,SAAiB;AACzC,QAAM,OAAO;AAAA,IACX,OAAO,MAAuB,KAAa,UAAwB,CAAC,MAAM;AACxE,gBAAU,SAAS;AACnB,UAAI;AACF,cAAM,UAAU,MAAM,SAAS,KAAK,MAAM,GAAG;AAC7C,kBAAU,MAAM;AAChB,cAAM,SAAS,MAAM,cAAc,UAAU,SAAS;AAAA,UACpD,GAAG;AAAA,UACH,QAAQ,CAAC,MAAM,WAAW;AACxB,oBAAQ,IAAI;AACZ,oBAAQ,SAAS,MAAM,MAAM;AAAA,UAC/B;AAAA,QACF,CAAC;AACD,kBAAU,OAAO,MAAM;AACvB,eAAO;AAAA,MACT,SAAS,SAAS;AAChB,kBAAU,MAAM;AAChB,cAAM;AAAA,MACR;AAAA,IACF;AAAA,IACA,CAAC,QAAQ;AAAA,EACX;AACA,SAAO,EAAE,MAAM,OAAO,eAAe,QAAQ,KAAK;AACpD;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/debug.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -10,7 +10,7 @@ import { ZappieError, isZappieError } from "./errors.js";
|
|
|
10
10
|
export * from "./models.js";
|
|
11
11
|
import { format, money } from "./money.js";
|
|
12
12
|
import { RestTransport } from "./transport/rest.js";
|
|
13
|
-
var VERSION = true ? "0.1.
|
|
13
|
+
var VERSION = true ? "0.1.19" : "0.0.0-development";
|
|
14
14
|
export {
|
|
15
15
|
API_VERSION,
|
|
16
16
|
RestTransport,
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** The editors in `EDITOR_ORIGINS` allowed to drive region selection: the merchant dashboard and its dev server. */
|
|
2
|
+
declare const STUDIO_ORIGINS: readonly string[];
|
|
3
|
+
/** Merchant-facing names for the Zappie kit's components, so kit files keep their hashes. */
|
|
4
|
+
declare const KIT_REGION_NAMES: Readonly<Record<string, string>>;
|
|
5
|
+
/** A region the merchant selected, as the preview posts it to Studio. */
|
|
6
|
+
type StudioRegion = {
|
|
7
|
+
zappie: "region";
|
|
8
|
+
/** Where the region is rendered, as `app/…:line`. */
|
|
9
|
+
source: string;
|
|
10
|
+
/** The function component that holds it, or an empty string. */
|
|
11
|
+
component: string;
|
|
12
|
+
/** The merchant-facing name. Never a path. */
|
|
13
|
+
name: string;
|
|
14
|
+
/** Up to five distinct `source` values of enclosing regions, nearest first. */
|
|
15
|
+
ancestry: string[];
|
|
16
|
+
/** The region's box in CSS pixels, relative to the viewport. */
|
|
17
|
+
rect: {
|
|
18
|
+
x: number;
|
|
19
|
+
y: number;
|
|
20
|
+
width: number;
|
|
21
|
+
height: number;
|
|
22
|
+
};
|
|
23
|
+
scrollY: number;
|
|
24
|
+
/** The page path and query. */
|
|
25
|
+
path: string;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* The merchant-facing name of a region: its own or an enclosing `data-zappie-region` within the same component, then
|
|
29
|
+
* the kit's name for its component, then the component name made readable, then the element's role.
|
|
30
|
+
*/
|
|
31
|
+
declare function regionName(element: Element): string;
|
|
32
|
+
/**
|
|
33
|
+
* Start the Studio region selector for an editor at `parent`, which must be in `STUDIO_ORIGINS`. It draws nothing until
|
|
34
|
+
* the editor sends `{ zappie: "select", on: true }`, and posts `region` and `select-cancel` through `post`. Returns a
|
|
35
|
+
* function that removes it, or null when `parent` isn't a Studio editor.
|
|
36
|
+
*/
|
|
37
|
+
declare function startStudio(parent: string, post: (message: object) => void): (() => void) | null;
|
|
38
|
+
|
|
39
|
+
export { KIT_REGION_NAMES, STUDIO_ORIGINS, type StudioRegion, regionName, startStudio };
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
// src/studio/index.ts
|
|
2
|
+
import { EDITOR_ORIGINS } from "../template/index.js";
|
|
3
|
+
var STUDIO_ORIGINS = Object.freeze(
|
|
4
|
+
EDITOR_ORIGINS.filter(
|
|
5
|
+
(origin) => /^https:\/\/dashboard\.|\/\/localhost:5181$/.test(origin)
|
|
6
|
+
)
|
|
7
|
+
);
|
|
8
|
+
var SOURCE = "data-zappie-source";
|
|
9
|
+
var COMPONENT = "data-zappie-component";
|
|
10
|
+
var REGION = "data-zappie-region";
|
|
11
|
+
var KIT_REGION_NAMES = {
|
|
12
|
+
Account: "Account",
|
|
13
|
+
AccountPage: "Account",
|
|
14
|
+
AddressPicker: "Delivery address",
|
|
15
|
+
CartButton: "Cart button",
|
|
16
|
+
CartCheckoutLink: "Checkout button",
|
|
17
|
+
CartDrawer: "Cart",
|
|
18
|
+
CartIssueList: "Cart notices",
|
|
19
|
+
CartLine: "Cart item",
|
|
20
|
+
CartTotals: "Cart totals",
|
|
21
|
+
CategoryNav: "Categories",
|
|
22
|
+
CheckoutButton: "Checkout button",
|
|
23
|
+
CheckoutPage: "Checkout",
|
|
24
|
+
CheckoutReturn: "Payment result",
|
|
25
|
+
Consent: "Cookie notice",
|
|
26
|
+
FulfilmentSwitch: "Delivery or pickup",
|
|
27
|
+
ItemCard: "Menu item",
|
|
28
|
+
ItemChoice: "Item option",
|
|
29
|
+
ItemConfiguration: "Item options",
|
|
30
|
+
ItemOptions: "Item options",
|
|
31
|
+
ItemSheet: "Item details",
|
|
32
|
+
Logo: "Logo",
|
|
33
|
+
MenuPage: "Menu",
|
|
34
|
+
Money: "Price",
|
|
35
|
+
OpenStatus: "Opening hours",
|
|
36
|
+
OrderPage: "Order",
|
|
37
|
+
OrderStatus: "Order status",
|
|
38
|
+
Orders: "Orders",
|
|
39
|
+
OrdersPage: "Orders",
|
|
40
|
+
Quantity: "Quantity",
|
|
41
|
+
SchedulePicker: "Order time",
|
|
42
|
+
Search: "Search",
|
|
43
|
+
SearchRow: "Search result",
|
|
44
|
+
SignInButton: "Sign in button",
|
|
45
|
+
StoreFooter: "Shop footer",
|
|
46
|
+
StoreHeader: "Shop header",
|
|
47
|
+
ZappieProvider: "Page"
|
|
48
|
+
};
|
|
49
|
+
var cap = (text, length) => [...text].slice(0, length).join("");
|
|
50
|
+
var read = (element, name) => element.getAttribute(name) ?? "";
|
|
51
|
+
function regionOf(target) {
|
|
52
|
+
const found = target?.closest(`[${SOURCE}]`);
|
|
53
|
+
return found && found !== document.documentElement && found !== document.body ? found : null;
|
|
54
|
+
}
|
|
55
|
+
function regionName(element) {
|
|
56
|
+
const component = read(element, COMPONENT);
|
|
57
|
+
for (let at = element; at && (!at.hasAttribute(COMPONENT) || read(at, COMPONENT) === component); at = at.parentElement) {
|
|
58
|
+
const name2 = at.getAttribute(REGION)?.trim();
|
|
59
|
+
if (name2) {
|
|
60
|
+
return cap(name2, 60);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const words = component.replace(/\B(?=[A-Z])/g, " ").toLowerCase();
|
|
64
|
+
const name = KIT_REGION_NAMES[component] ?? (words && words[0].toUpperCase() + words.slice(1));
|
|
65
|
+
if (name) {
|
|
66
|
+
return cap(name, 60);
|
|
67
|
+
}
|
|
68
|
+
const tag = element.localName;
|
|
69
|
+
return /^(img|picture|svg|video)$/.test(tag) ? "Image" : tag === "button" || element.getAttribute("role") === "button" ? "Button" : /^h[1-6]$/.test(tag) ? "Heading" : tag === "a" ? "Link" : "Section";
|
|
70
|
+
}
|
|
71
|
+
function ancestry(element) {
|
|
72
|
+
const own = read(element, SOURCE);
|
|
73
|
+
const sources = [];
|
|
74
|
+
for (let at = regionOf(element.parentElement); at && sources.length < 5; at = regionOf(at.parentElement)) {
|
|
75
|
+
const source = read(at, SOURCE);
|
|
76
|
+
if (source !== own && !sources.includes(source)) {
|
|
77
|
+
sources.push(source);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return sources;
|
|
81
|
+
}
|
|
82
|
+
var shown = (element) => {
|
|
83
|
+
const box = element.getBoundingClientRect();
|
|
84
|
+
return box.width > 0 && box.height > 0;
|
|
85
|
+
};
|
|
86
|
+
function regionStops() {
|
|
87
|
+
const stops = /* @__PURE__ */ new Set();
|
|
88
|
+
for (const element of document.querySelectorAll(`[${SOURCE}]`)) {
|
|
89
|
+
const stop = element.closest(`[${REGION}]`) ?? element;
|
|
90
|
+
if (regionOf(stop) === stop && shown(stop)) {
|
|
91
|
+
stops.add(stop);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return [...stops];
|
|
95
|
+
}
|
|
96
|
+
var CSS = ".b{position:fixed;left:0;top:0;box-sizing:border-box;border:2px solid #101828;border-radius:2px;box-shadow:0 0 0 2px #fff,inset 0 0 0 2px #fff}.b[hidden]{display:none}.s{border-width:3px}.l{position:absolute;left:-2px;bottom:100%;margin-bottom:4px;padding:2px 6px;border-radius:4px;background:#101828;color:#fff;box-shadow:0 0 0 1px #fff;font:600 12px/1.5 system-ui,sans-serif;white-space:nowrap}.t .l{bottom:auto;top:100%;margin:4px 0 0}.v{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}";
|
|
97
|
+
var CAPTURED = [
|
|
98
|
+
"pointerdown",
|
|
99
|
+
"pointerup",
|
|
100
|
+
"mousedown",
|
|
101
|
+
"mouseup",
|
|
102
|
+
"click",
|
|
103
|
+
"auxclick",
|
|
104
|
+
"dblclick"
|
|
105
|
+
];
|
|
106
|
+
function startStudio(parent, post) {
|
|
107
|
+
if (!STUDIO_ORIGINS.includes(parent)) {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
const host = document.createElement("div");
|
|
111
|
+
host.style.cssText = "position:fixed;inset:0;pointer-events:none;z-index:2147483647";
|
|
112
|
+
const root = host.attachShadow({ mode: "open" });
|
|
113
|
+
const make = (tag, name) => {
|
|
114
|
+
const element = document.createElement(tag);
|
|
115
|
+
element.className = name;
|
|
116
|
+
return element;
|
|
117
|
+
};
|
|
118
|
+
const style = make("style", "");
|
|
119
|
+
style.textContent = CSS;
|
|
120
|
+
const box = make("div", "b");
|
|
121
|
+
box.hidden = true;
|
|
122
|
+
const label = box.appendChild(make("span", "l"));
|
|
123
|
+
const live = make("div", "v");
|
|
124
|
+
live.setAttribute("aria-live", "polite");
|
|
125
|
+
for (const part of [style, box, live]) {
|
|
126
|
+
root.appendChild(part);
|
|
127
|
+
}
|
|
128
|
+
document.body.appendChild(host);
|
|
129
|
+
let on = false;
|
|
130
|
+
let selected = false;
|
|
131
|
+
let current = null;
|
|
132
|
+
let hovered = null;
|
|
133
|
+
let down = [];
|
|
134
|
+
let pointer = null;
|
|
135
|
+
let frame = 0;
|
|
136
|
+
let drawn = "";
|
|
137
|
+
const highlight = (element, announce = false) => {
|
|
138
|
+
current = element;
|
|
139
|
+
drawn = "";
|
|
140
|
+
if (element) {
|
|
141
|
+
label.textContent = regionName(element);
|
|
142
|
+
if (announce) {
|
|
143
|
+
live.textContent = label.textContent;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
const draw = () => {
|
|
148
|
+
frame = requestAnimationFrame(draw);
|
|
149
|
+
if (pointer && !selected) {
|
|
150
|
+
const element = regionOf(document.elementFromPoint(...pointer));
|
|
151
|
+
pointer = null;
|
|
152
|
+
if (element !== hovered) {
|
|
153
|
+
hovered = element;
|
|
154
|
+
down = [];
|
|
155
|
+
highlight(element);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (current && !current.isConnected) {
|
|
159
|
+
current = null;
|
|
160
|
+
}
|
|
161
|
+
if (!current) {
|
|
162
|
+
box.hidden = true;
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
const { x, y, width, height } = current.getBoundingClientRect();
|
|
166
|
+
const key2 = `${x} ${y} ${width} ${height}`;
|
|
167
|
+
if (key2 === drawn) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
drawn = key2;
|
|
171
|
+
box.hidden = false;
|
|
172
|
+
box.classList.toggle("t", y < 24);
|
|
173
|
+
box.style.transition = matchMedia("(prefers-reduced-motion: reduce)").matches ? "none" : "transform 100ms";
|
|
174
|
+
box.style.transform = `translate(${x}px,${y}px)`;
|
|
175
|
+
box.style.width = `${width}px`;
|
|
176
|
+
box.style.height = `${height}px`;
|
|
177
|
+
};
|
|
178
|
+
const choose = (element) => {
|
|
179
|
+
if (!element) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
selected = true;
|
|
183
|
+
highlight(element);
|
|
184
|
+
box.classList.add("s");
|
|
185
|
+
live.textContent = `Selected ${label.textContent}`;
|
|
186
|
+
const { x, y, width, height } = element.getBoundingClientRect();
|
|
187
|
+
post({
|
|
188
|
+
zappie: "region",
|
|
189
|
+
source: read(element, SOURCE),
|
|
190
|
+
component: cap(read(element, COMPONENT), 80),
|
|
191
|
+
name: regionName(element),
|
|
192
|
+
ancestry: ancestry(element),
|
|
193
|
+
rect: { x, y, width, height },
|
|
194
|
+
scrollY: window.scrollY,
|
|
195
|
+
path: location.pathname + location.search
|
|
196
|
+
});
|
|
197
|
+
};
|
|
198
|
+
const step = (up) => {
|
|
199
|
+
if (!current) {
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
let next;
|
|
203
|
+
if (up) {
|
|
204
|
+
const own = read(current, SOURCE);
|
|
205
|
+
next = regionOf(current.parentElement);
|
|
206
|
+
while (next && read(next, SOURCE) === own) {
|
|
207
|
+
next = regionOf(next.parentElement);
|
|
208
|
+
}
|
|
209
|
+
if (next) {
|
|
210
|
+
down.push(current);
|
|
211
|
+
}
|
|
212
|
+
} else {
|
|
213
|
+
next = down.pop() ?? [...current.querySelectorAll(`[${SOURCE}]`)].find(
|
|
214
|
+
(element) => read(element, SOURCE) !== read(current, SOURCE) && shown(element)
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
if (next) {
|
|
218
|
+
highlight(next, true);
|
|
219
|
+
}
|
|
220
|
+
return Boolean(next);
|
|
221
|
+
};
|
|
222
|
+
const capture = (event) => {
|
|
223
|
+
event.preventDefault();
|
|
224
|
+
event.stopImmediatePropagation();
|
|
225
|
+
if (event.type === "click" && !selected) {
|
|
226
|
+
const target = event.target;
|
|
227
|
+
choose(current?.contains(target) ? current : regionOf(target));
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
const move = (event) => {
|
|
231
|
+
pointer = [event.clientX, event.clientY];
|
|
232
|
+
};
|
|
233
|
+
const wheel = (event) => {
|
|
234
|
+
if (event.altKey && !selected && step(event.deltaY < 0)) {
|
|
235
|
+
event.preventDefault();
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
const key = (event) => {
|
|
239
|
+
let handled = true;
|
|
240
|
+
if (event.key === "Escape") {
|
|
241
|
+
stop();
|
|
242
|
+
post({ zappie: "select-cancel" });
|
|
243
|
+
} else if (selected) {
|
|
244
|
+
handled = false;
|
|
245
|
+
} else if (event.key === "Tab") {
|
|
246
|
+
const stops = regionStops();
|
|
247
|
+
const at = current ? stops.indexOf(current.closest(`[${REGION}]`) ?? current) : -1;
|
|
248
|
+
const next = stops[(at + (event.shiftKey ? at < 0 ? 0 : -1 : 1) + stops.length) % stops.length];
|
|
249
|
+
down = [];
|
|
250
|
+
highlight(next ?? null, true);
|
|
251
|
+
next?.scrollIntoView?.({ block: "nearest" });
|
|
252
|
+
} else if (event.key === "Enter" || event.key === " ") {
|
|
253
|
+
choose(current);
|
|
254
|
+
} else if (event.key === "ArrowUp" || event.key === "ArrowDown") {
|
|
255
|
+
handled = step(event.key === "ArrowUp");
|
|
256
|
+
} else {
|
|
257
|
+
handled = false;
|
|
258
|
+
}
|
|
259
|
+
if (handled) {
|
|
260
|
+
event.preventDefault();
|
|
261
|
+
event.stopImmediatePropagation();
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
const listen = (add) => {
|
|
265
|
+
const method = (add ? window.addEventListener : window.removeEventListener).bind(window);
|
|
266
|
+
for (const type of CAPTURED) {
|
|
267
|
+
method(type, capture, true);
|
|
268
|
+
}
|
|
269
|
+
method("pointermove", move, true);
|
|
270
|
+
method("keydown", key, true);
|
|
271
|
+
method("wheel", wheel, { capture: true, passive: false });
|
|
272
|
+
};
|
|
273
|
+
const stop = () => {
|
|
274
|
+
if (on) {
|
|
275
|
+
on = false;
|
|
276
|
+
listen(false);
|
|
277
|
+
cancelAnimationFrame(frame);
|
|
278
|
+
}
|
|
279
|
+
selected = false;
|
|
280
|
+
hovered = null;
|
|
281
|
+
down = [];
|
|
282
|
+
highlight(null);
|
|
283
|
+
box.hidden = true;
|
|
284
|
+
box.classList.remove("s");
|
|
285
|
+
};
|
|
286
|
+
const start = () => {
|
|
287
|
+
stop();
|
|
288
|
+
on = true;
|
|
289
|
+
listen(true);
|
|
290
|
+
draw();
|
|
291
|
+
};
|
|
292
|
+
const message = (event) => {
|
|
293
|
+
const data = event.data;
|
|
294
|
+
if (event.origin === parent && event.source === window.parent && data?.zappie === "select" && typeof data.on === "boolean") {
|
|
295
|
+
if (data.on) {
|
|
296
|
+
start();
|
|
297
|
+
} else {
|
|
298
|
+
stop();
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
window.addEventListener("message", message);
|
|
303
|
+
return () => {
|
|
304
|
+
stop();
|
|
305
|
+
window.removeEventListener("message", message);
|
|
306
|
+
host.remove();
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
export {
|
|
310
|
+
KIT_REGION_NAMES,
|
|
311
|
+
STUDIO_ORIGINS,
|
|
312
|
+
regionName,
|
|
313
|
+
startStudio
|
|
314
|
+
};
|
package/dist/template/react.js
CHANGED
|
@@ -65,7 +65,26 @@ function TemplateSettingsProvider({
|
|
|
65
65
|
send({ zappie: "ready" });
|
|
66
66
|
route();
|
|
67
67
|
scroll();
|
|
68
|
+
let live = true;
|
|
69
|
+
let studio = null;
|
|
70
|
+
if (mode === "draft") {
|
|
71
|
+
if (import.meta.env.DEV && document.querySelector("[data-zappie-source]")) {
|
|
72
|
+
import("@zappie/sdk/studio").then(
|
|
73
|
+
({ startStudio }) => {
|
|
74
|
+
if (live) {
|
|
75
|
+
studio = startStudio(parent, send);
|
|
76
|
+
send({ zappie: "selectable", on: Boolean(studio) });
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
() => send({ zappie: "selectable", on: false })
|
|
80
|
+
);
|
|
81
|
+
} else {
|
|
82
|
+
send({ zappie: "selectable", on: false });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
68
85
|
return () => {
|
|
86
|
+
live = false;
|
|
87
|
+
studio?.();
|
|
69
88
|
window.removeEventListener("message", message);
|
|
70
89
|
window.removeEventListener("popstate", route);
|
|
71
90
|
window.removeEventListener("scroll", scroll);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/template/react.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\";\nimport { useStore } from \"@zappie/sdk/react\";\nimport {\n editorOrigin,\n settingsFromMessage,\n templateSettings,\n type TemplateMode,\n type TemplateSettingValue,\n} from \"@zappie/sdk/template\";\nimport {\n createContext,\n useContext,\n useEffect,\n useMemo,\n useState,\n type ReactNode,\n} from \"react\";\n\nconst Context = createContext<{\n mode: TemplateMode;\n preview: Record<string, TemplateSettingValue> | null;\n}>({ mode: \"site\", preview: null });\n\n/** Supply the host-controlled template mode and editor bridge to a storefront. */\nexport function TemplateSettingsProvider({\n mode,\n children,\n}: {\n mode: TemplateMode;\n children: ReactNode;\n}) {\n const [preview, setPreview] = useState<Record<\n string,\n TemplateSettingValue\n > | null>(null);\n useEffect(() => {\n setPreview(null);\n if (mode === \"site\") {\n return;\n }\n if (window.parent === window) {\n return;\n }\n // Talk only to an allowed Zappie editor framing this page. Anything else gets silence.\n const parent = editorOrigin(location.ancestorOrigins, document.referrer);\n if (!parent) {\n return;\n }\n const send = (message: object) =>\n window.parent.postMessage(message, parent);\n const route = () =>\n send({ zappie: \"route\", path: location.pathname + location.search });\n const scroll = () =>\n send({\n zappie: \"scroll\",\n y: window.scrollY,\n height: document.documentElement.scrollHeight,\n });\n const message = (event: MessageEvent) => {\n if (event.origin !== parent || event.source !== window.parent) {\n return;\n }\n const values = settingsFromMessage(event.data);\n if (values) {\n setPreview(values);\n }\n };\n const pushState = history.pushState;\n const replaceState = history.replaceState;\n history.pushState = function (...args) {\n pushState.apply(this, args);\n route();\n };\n history.replaceState = function (...args) {\n replaceState.apply(this, args);\n route();\n };\n window.addEventListener(\"message\", message);\n window.addEventListener(\"popstate\", route);\n window.addEventListener(\"scroll\", scroll, { passive: true });\n send({ zappie: \"ready\" });\n route();\n scroll();\n return () => {\n window.removeEventListener(\"message\", message);\n window.removeEventListener(\"popstate\", route);\n window.removeEventListener(\"scroll\", scroll);\n history.pushState = pushState;\n history.replaceState = replaceState;\n };\n }, [mode]);\n const value = useMemo(() => ({ mode, preview }), [mode, preview]);\n return <Context.Provider value={value}>{children}</Context.Provider>;\n}\n\n/** Read effective merchant settings over the defaults declared by a template. */\nexport function useTemplateSettings<T extends Record<string, unknown>>(\n defaults: T,\n): T {\n const store = useStore();\n const { mode, preview } = useContext(Context);\n return templateSettings(\n defaults,\n store.data?.templateSettings,\n preview,\n mode,\n );\n}\n"],
|
|
5
|
-
"mappings": ";;;AACA,SAAS,gBAAgB;AACzB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;
|
|
4
|
+
"sourcesContent": ["\"use client\";\nimport { useStore } from \"@zappie/sdk/react\";\nimport {\n editorOrigin,\n settingsFromMessage,\n templateSettings,\n type TemplateMode,\n type TemplateSettingValue,\n} from \"@zappie/sdk/template\";\nimport {\n createContext,\n useContext,\n useEffect,\n useMemo,\n useState,\n type ReactNode,\n} from \"react\";\n\nconst Context = createContext<{\n mode: TemplateMode;\n preview: Record<string, TemplateSettingValue> | null;\n}>({ mode: \"site\", preview: null });\n\n/** Supply the host-controlled template mode and editor bridge to a storefront. */\nexport function TemplateSettingsProvider({\n mode,\n children,\n}: {\n mode: TemplateMode;\n children: ReactNode;\n}) {\n const [preview, setPreview] = useState<Record<\n string,\n TemplateSettingValue\n > | null>(null);\n useEffect(() => {\n setPreview(null);\n if (mode === \"site\") {\n return;\n }\n if (window.parent === window) {\n return;\n }\n // Talk only to an allowed Zappie editor framing this page. Anything else gets silence.\n const parent = editorOrigin(location.ancestorOrigins, document.referrer);\n if (!parent) {\n return;\n }\n const send = (message: object) =>\n window.parent.postMessage(message, parent);\n const route = () =>\n send({ zappie: \"route\", path: location.pathname + location.search });\n const scroll = () =>\n send({\n zappie: \"scroll\",\n y: window.scrollY,\n height: document.documentElement.scrollHeight,\n });\n const message = (event: MessageEvent) => {\n if (event.origin !== parent || event.source !== window.parent) {\n return;\n }\n const values = settingsFromMessage(event.data);\n if (values) {\n setPreview(values);\n }\n };\n const pushState = history.pushState;\n const replaceState = history.replaceState;\n history.pushState = function (...args) {\n pushState.apply(this, args);\n route();\n };\n history.replaceState = function (...args) {\n replaceState.apply(this, args);\n route();\n };\n window.addEventListener(\"message\", message);\n window.addEventListener(\"popstate\", route);\n window.addEventListener(\"scroll\", scroll, { passive: true });\n send({ zappie: \"ready\" });\n route();\n scroll();\n let live = true;\n let studio: (() => void) | null = null;\n if (mode === \"draft\") {\n // Only the Studio dev server marks sources. A build drops this branch, so no published page loads the selector.\n if (\n (import.meta as unknown as { env: { DEV: boolean } }).env.DEV &&\n document.querySelector(\"[data-zappie-source]\")\n ) {\n import(\"@zappie/sdk/studio\").then(\n ({ startStudio }) => {\n if (live) {\n studio = startStudio(parent, send);\n send({ zappie: \"selectable\", on: Boolean(studio) });\n }\n },\n () => send({ zappie: \"selectable\", on: false }),\n );\n } else {\n send({ zappie: \"selectable\", on: false });\n }\n }\n return () => {\n live = false;\n studio?.();\n window.removeEventListener(\"message\", message);\n window.removeEventListener(\"popstate\", route);\n window.removeEventListener(\"scroll\", scroll);\n history.pushState = pushState;\n history.replaceState = replaceState;\n };\n }, [mode]);\n const value = useMemo(() => ({ mode, preview }), [mode, preview]);\n return <Context.Provider value={value}>{children}</Context.Provider>;\n}\n\n/** Read effective merchant settings over the defaults declared by a template. */\nexport function useTemplateSettings<T extends Record<string, unknown>>(\n defaults: T,\n): T {\n const store = useStore();\n const { mode, preview } = useContext(Context);\n return templateSettings(\n defaults,\n store.data?.templateSettings,\n preview,\n mode,\n );\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["message"]
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zappie/sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.19",
|
|
4
4
|
"description": "Typed, framework-agnostic client for Zappie menus, carts, checkout, auth and events.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -49,6 +49,14 @@
|
|
|
49
49
|
"types": "./dist/checkout/index.d.ts",
|
|
50
50
|
"import": "./dist/checkout/index.js"
|
|
51
51
|
},
|
|
52
|
+
"./checkout/embed": {
|
|
53
|
+
"types": "./dist/checkout/embed.d.ts",
|
|
54
|
+
"import": "./dist/checkout/embed.js"
|
|
55
|
+
},
|
|
56
|
+
"./checkout/react": {
|
|
57
|
+
"types": "./dist/checkout/react.d.ts",
|
|
58
|
+
"import": "./dist/checkout/react.js"
|
|
59
|
+
},
|
|
52
60
|
"./checkout-defaults": {
|
|
53
61
|
"types": "./dist/checkout-defaults/index.d.ts",
|
|
54
62
|
"import": "./dist/checkout-defaults/index.js"
|
|
@@ -81,6 +89,10 @@
|
|
|
81
89
|
"types": "./dist/template/react.d.ts",
|
|
82
90
|
"import": "./dist/template/react.js"
|
|
83
91
|
},
|
|
92
|
+
"./studio": {
|
|
93
|
+
"types": "./dist/studio/index.d.ts",
|
|
94
|
+
"import": "./dist/studio/index.js"
|
|
95
|
+
},
|
|
84
96
|
"./persist/catalogue": {
|
|
85
97
|
"types": "./dist/persist/catalogue.d.ts",
|
|
86
98
|
"import": "./dist/persist/catalogue.js"
|
|
@@ -121,7 +133,7 @@
|
|
|
121
133
|
"url": "https://github.com/zappie/storefront-packages/issues"
|
|
122
134
|
},
|
|
123
135
|
"scripts": {
|
|
124
|
-
"build": "tsup && tsup --config tsup.checkout.config.ts && tsup --config tsup.checkout-defaults.config.ts && tsup --config tsup.customer.config.ts && tsup --config tsup.places.config.ts && tsup --config tsup.url.config.ts && tsup --config tsup.redirect.config.ts && tsup --config tsup.visits.config.ts && tsup --config tsup.pricing.config.ts && tsup --config tsup.site-config.config.ts && tsup --config tsup.template.config.ts && node ../../scripts/release-packages/build-modules.mjs",
|
|
136
|
+
"build": "tsup && tsup --config tsup.checkout.config.ts && tsup --config tsup.checkout-defaults.config.ts && tsup --config tsup.customer.config.ts && tsup --config tsup.places.config.ts && tsup --config tsup.url.config.ts && tsup --config tsup.redirect.config.ts && tsup --config tsup.visits.config.ts && tsup --config tsup.pricing.config.ts && tsup --config tsup.site-config.config.ts && tsup --config tsup.template.config.ts && node ../../scripts/release-packages/build-modules.mjs --unmapped=studio/index.js",
|
|
125
137
|
"test": "vitest run --root ../.. packages/sdk/test",
|
|
126
138
|
"size": "node ../../scripts/release-packages/size-sdk.mjs && size-limit"
|
|
127
139
|
}
|