@patientos/website-kit 0.2.6 → 0.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +54 -2
  2. package/dist/booking-block-D-UOAPx3.d.ts +74 -0
  3. package/dist/cart-block-B-ytc9us.d.ts +20 -0
  4. package/dist/cart-button-BCLFittB.d.ts +24 -0
  5. package/dist/cart-storage.d.ts +110 -0
  6. package/dist/cart-storage.js +39 -0
  7. package/dist/checkout-block-BeXwpqYe.d.ts +24 -0
  8. package/dist/chunk-2CZFJOKK.js +79 -0
  9. package/dist/{chunk-ZYX4TXBN.js → chunk-3T7UWKAH.js} +4 -310
  10. package/dist/{chunk-7ZOQ6UKG.js → chunk-4GKSSM5N.js} +578 -3163
  11. package/dist/chunk-4NF7SSIX.js +367 -0
  12. package/dist/chunk-7BUDUYXN.js +80 -0
  13. package/dist/chunk-FZ4WKWIE.js +310 -0
  14. package/dist/{chunk-ZZFBTLR4.js → chunk-HTVKKLWI.js} +20 -5
  15. package/dist/chunk-IOC6QLB7.js +159 -0
  16. package/dist/chunk-S3ZQQOQT.js +2057 -0
  17. package/dist/chunk-UTAMB2SK.js +214 -0
  18. package/dist/chunk-WQSJ46TP.js +19 -0
  19. package/dist/chunk-ZNH6TSYP.js +167 -0
  20. package/dist/index.d.ts +12 -32
  21. package/dist/index.js +88 -26
  22. package/dist/islands-impl/cart-button.d.ts +19 -0
  23. package/dist/islands-impl/cart-button.js +13 -0
  24. package/dist/islands-impl/cart.d.ts +31 -0
  25. package/dist/islands-impl/cart.js +11 -0
  26. package/dist/islands-impl/checkout.d.ts +6 -0
  27. package/dist/islands-impl/checkout.js +13 -0
  28. package/dist/islands-impl/store.d.ts +30 -0
  29. package/dist/islands-impl/store.js +16 -0
  30. package/dist/islands-impl.d.ts +14 -10
  31. package/dist/islands-impl.js +22 -7
  32. package/dist/islands-registry.js +13 -4
  33. package/dist/{portal-account-8uQc_Ncx.d.ts → portal-account-DRJyr3nz.d.ts} +1 -82
  34. package/dist/{portal-account.client-BBe_q9yC.d.ts → portal-account.client-Bke4NsgW.d.ts} +2 -1
  35. package/dist/portal-booking-client.d.ts +3 -2
  36. package/dist/portal-booking-client.js +2 -1
  37. package/dist/portal-client-my3q5GME.d.ts +83 -0
  38. package/dist/store-block-CFxnsfKU.d.ts +37 -0
  39. package/dist/store-catalog-p7TFOpfk.d.ts +78 -0
  40. package/dist/store-catalog.d.ts +3 -0
  41. package/dist/store-catalog.js +11 -0
  42. package/dist/website-kit.css +193 -0
  43. package/package.json +31 -1
  44. package/dist/checkout-block-BdpPIuQ5.d.ts +0 -136
@@ -0,0 +1,310 @@
1
+ // src/portal-client.ts
2
+ var PORTAL_WHOAMI_PATH = "/portal/api/whoami";
3
+ var PORTAL_MAGIC_LINK_PATH = "/portal/api/magic-link";
4
+ var PORTAL_PREFILL_PATH = "/portal/api/prefill";
5
+ var PORTAL_PUBLIC_CLAIM_PATH = "/portal/api/public-claim";
6
+ var PORTAL_APPLICATION_HANDOFF_PATH = "/portal/api/application-run-handoffs";
7
+ var PORTAL_SESSION_EXCHANGE_PATH = "/portal/api/session-exchange";
8
+ var PORTAL_RETURN_PATH = "/portal/return";
9
+ var PORTAL_HANDOFF_FRAGMENT_PREFIX = "#patientos-handoff=";
10
+ var BRIDGE_TIMEOUT_MS = 15e3;
11
+ var BRIDGE_PROBE_TIMEOUT_MS = 8e3;
12
+ var PORTAL_BEARER_STORAGE_PREFIX = "patientos.portal.bearer:";
13
+ var parkedHandoffPayload = null;
14
+ var pendingHandoffTokens = /* @__PURE__ */ new Map();
15
+ var pendingHandoffExchanges = /* @__PURE__ */ new Map();
16
+ var memoryBearers = /* @__PURE__ */ new Map();
17
+ function onLocalhostDevPage() {
18
+ if (typeof window === "undefined") return false;
19
+ return window.location.protocol === "http:" && window.location.hostname === "localhost" && window.location.port !== "";
20
+ }
21
+ function localhostHandoffEnabled(apiOrigin) {
22
+ return apiOrigin !== "" && onLocalhostDevPage();
23
+ }
24
+ function bearerStorage() {
25
+ try {
26
+ return typeof window === "undefined" ? null : window.sessionStorage;
27
+ } catch {
28
+ return null;
29
+ }
30
+ }
31
+ function bearerStorageKey(apiOrigin) {
32
+ return `${PORTAL_BEARER_STORAGE_PREFIX}${apiOrigin}`;
33
+ }
34
+ function readBearer(apiOrigin) {
35
+ const memory = memoryBearers.get(apiOrigin);
36
+ if (memory) return memory;
37
+ try {
38
+ const stored = bearerStorage()?.getItem(bearerStorageKey(apiOrigin)) ?? null;
39
+ if (!stored || stored.length > 4096) {
40
+ clearBearer(apiOrigin);
41
+ return null;
42
+ }
43
+ memoryBearers.set(apiOrigin, stored);
44
+ return stored;
45
+ } catch {
46
+ return null;
47
+ }
48
+ }
49
+ function writeBearer(apiOrigin, token) {
50
+ memoryBearers.set(apiOrigin, token);
51
+ try {
52
+ bearerStorage()?.setItem(bearerStorageKey(apiOrigin), token);
53
+ } catch {
54
+ }
55
+ }
56
+ function clearBearer(apiOrigin) {
57
+ memoryBearers.delete(apiOrigin);
58
+ try {
59
+ bearerStorage()?.removeItem(bearerStorageKey(apiOrigin));
60
+ } catch {
61
+ }
62
+ }
63
+ function decodeBase64UrlJson(raw) {
64
+ const b64 = raw.replace(/-/g, "+").replace(/_/g, "/");
65
+ const binary = atob(b64.padEnd(Math.ceil(b64.length / 4) * 4, "="));
66
+ const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
67
+ return JSON.parse(new TextDecoder().decode(bytes));
68
+ }
69
+ function consumePortalHandoffFragment() {
70
+ if (!onLocalhostDevPage()) return;
71
+ const raw = window.location.hash;
72
+ if (!raw.startsWith(PORTAL_HANDOFF_FRAGMENT_PREFIX)) return;
73
+ let payload = null;
74
+ try {
75
+ const decoded = decodeBase64UrlJson(raw.slice(PORTAL_HANDOFF_FRAGMENT_PREFIX.length));
76
+ const candidate = decoded;
77
+ if (typeof candidate?.token === "string" && candidate.token.length > 0 && candidate.token.length <= 512 && typeof candidate.returnHash === "string") {
78
+ payload = { token: candidate.token, returnHash: candidate.returnHash };
79
+ }
80
+ } catch {
81
+ }
82
+ const restoredHash = payload?.returnHash ? `#${payload.returnHash}` : "";
83
+ window.history.replaceState(
84
+ window.history.state,
85
+ "",
86
+ `${window.location.pathname}${window.location.search}${restoredHash}`
87
+ );
88
+ if (payload) parkedHandoffPayload = payload;
89
+ }
90
+ function adoptParkedHandoff(apiOrigin) {
91
+ const payload = parkedHandoffPayload;
92
+ if (!payload || !localhostHandoffEnabled(apiOrigin)) return;
93
+ parkedHandoffPayload = null;
94
+ clearBearer(apiOrigin);
95
+ pendingHandoffTokens.set(apiOrigin, payload.token);
96
+ }
97
+ async function exchangePortalHandoff(apiOrigin, fetchImpl) {
98
+ const pending = pendingHandoffExchanges.get(apiOrigin);
99
+ if (pending) return pending;
100
+ const oneTimeToken = pendingHandoffTokens.get(apiOrigin);
101
+ if (!oneTimeToken) return readBearer(apiOrigin);
102
+ pendingHandoffTokens.delete(apiOrigin);
103
+ const exchange = (async () => {
104
+ const controller = new AbortController();
105
+ const timer = setTimeout(() => controller.abort(), BRIDGE_PROBE_TIMEOUT_MS);
106
+ try {
107
+ const response = await fetchImpl(`${apiOrigin}${PORTAL_SESSION_EXCHANGE_PATH}`, {
108
+ ...PORTAL_REQUEST_INIT,
109
+ method: "POST",
110
+ headers: { accept: "application/json", "content-type": "application/json" },
111
+ body: JSON.stringify({ token: oneTimeToken }),
112
+ signal: controller.signal
113
+ });
114
+ const result = await readPortalResult(response);
115
+ const bearer = result.ok && typeof result.data?.token === "string" ? result.data.token : null;
116
+ if (!bearer) return null;
117
+ writeBearer(apiOrigin, bearer);
118
+ return bearer;
119
+ } catch {
120
+ pendingHandoffTokens.set(apiOrigin, oneTimeToken);
121
+ return null;
122
+ } finally {
123
+ clearTimeout(timer);
124
+ }
125
+ })();
126
+ pendingHandoffExchanges.set(apiOrigin, exchange);
127
+ try {
128
+ return await exchange;
129
+ } finally {
130
+ if (pendingHandoffExchanges.get(apiOrigin) === exchange) {
131
+ pendingHandoffExchanges.delete(apiOrigin);
132
+ }
133
+ }
134
+ }
135
+ var PORTAL_REQUEST_INIT = {
136
+ credentials: "include",
137
+ cache: "no-store"
138
+ };
139
+ function normalizePortalApiOrigin(raw) {
140
+ let url;
141
+ try {
142
+ url = new URL(raw.trim());
143
+ } catch {
144
+ return null;
145
+ }
146
+ const loopback = url.hostname === "localhost" || url.hostname === "127.0.0.1";
147
+ if (url.protocol !== "https:" && !(url.protocol === "http:" && loopback)) return null;
148
+ if (url.username || url.password || url.search || url.hash) return null;
149
+ if (url.pathname !== "/" && url.pathname !== "") return null;
150
+ return url.origin;
151
+ }
152
+ function encodePortalReturnTarget(url) {
153
+ const bytes = new TextEncoder().encode(url);
154
+ let binary = "";
155
+ for (const byte of bytes) binary += String.fromCharCode(byte);
156
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
157
+ }
158
+ function sameOriginAvailable(portalOrigin) {
159
+ if (typeof window === "undefined") return false;
160
+ if (portalOrigin == null) {
161
+ return true;
162
+ }
163
+ const normalizedOrigin = portalOrigin.trim().replace(/\/+$/, "");
164
+ return normalizedOrigin !== "" && window.location.origin === normalizedOrigin;
165
+ }
166
+ async function readPortalResult(response) {
167
+ if (!response.ok) {
168
+ let error = null;
169
+ let body;
170
+ try {
171
+ const parsed = await response.json();
172
+ if (typeof parsed?.error === "string") error = parsed.error;
173
+ body = parsed;
174
+ } catch {
175
+ }
176
+ return { ok: false, status: response.status, error, body };
177
+ }
178
+ try {
179
+ return { ok: true, data: await response.json() };
180
+ } catch {
181
+ return { ok: false, status: null, error: null };
182
+ }
183
+ }
184
+ function createPortalClient(config = {}) {
185
+ const rawOrigin = config.apiOrigin?.trim() ?? "";
186
+ const normalizedOrigin = rawOrigin ? normalizePortalApiOrigin(rawOrigin) : "";
187
+ if (rawOrigin && !normalizedOrigin) {
188
+ console.error("[portal] ignoring a malformed portal API origin", rawOrigin);
189
+ }
190
+ const apiOrigin = normalizedOrigin ?? "";
191
+ const portalHref = config.portalUrl?.trim() ?? "";
192
+ const available = apiOrigin !== "" || sameOriginAvailable(config.portalOrigin);
193
+ const fetchImpl = config.fetchImpl ?? ((input, init) => globalThis.fetch(input, init));
194
+ const handoffEnabled = localhostHandoffEnabled(apiOrigin);
195
+ consumePortalHandoffFragment();
196
+ adoptParkedHandoff(apiOrigin);
197
+ const url = (path) => apiOrigin && path.startsWith("/") ? apiOrigin + path : path;
198
+ const fetchWithSession = async (input, init) => {
199
+ let belongsToApiOrigin = false;
200
+ if (handoffEnabled) {
201
+ try {
202
+ belongsToApiOrigin = new URL(input, window.location.href).origin === apiOrigin;
203
+ } catch {
204
+ }
205
+ }
206
+ const bearer = belongsToApiOrigin ? await exchangePortalHandoff(apiOrigin, fetchImpl) : null;
207
+ if (!bearer) return fetchImpl(input, init);
208
+ const headers = new Headers(init?.headers);
209
+ headers.set("authorization", `Bearer ${bearer}`);
210
+ return fetchImpl(input, { ...init, headers });
211
+ };
212
+ async function request(path, init, timeoutMs = BRIDGE_TIMEOUT_MS) {
213
+ const controller = new AbortController();
214
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
215
+ try {
216
+ const response = await fetchWithSession(url(path), {
217
+ ...PORTAL_REQUEST_INIT,
218
+ ...init,
219
+ signal: controller.signal
220
+ });
221
+ const result = await readPortalResult(response);
222
+ if (handoffEnabled) {
223
+ const body = result.ok ? result.data : null;
224
+ if (path === "/portal/api/sign-out" && result.ok || !result.ok && result.status === 401 || body?.signedIn === false) {
225
+ clearBearer(apiOrigin);
226
+ }
227
+ }
228
+ return result;
229
+ } catch {
230
+ return { ok: false, status: null, error: null };
231
+ } finally {
232
+ clearTimeout(timer);
233
+ }
234
+ }
235
+ const get = (path) => request(path, { headers: { accept: "application/json" } });
236
+ const send = (path, method, body) => request(path, {
237
+ method,
238
+ headers: { accept: "application/json", "content-type": "application/json" },
239
+ body: JSON.stringify(body ?? {})
240
+ });
241
+ const del = (path) => request(path, { method: "DELETE", headers: { accept: "application/json" } });
242
+ const returnPath = (targetUrl) => {
243
+ if (typeof window === "undefined") return "/";
244
+ let target = targetUrl ?? window.location.href;
245
+ try {
246
+ const parsed2 = new URL(target, window.location.href);
247
+ if (parsed2.origin !== window.location.origin) target = window.location.href;
248
+ else target = parsed2.href;
249
+ } catch {
250
+ target = window.location.href;
251
+ }
252
+ const parsed = new URL(target);
253
+ const path = `${parsed.pathname || "/"}${parsed.search}${parsed.hash}`;
254
+ if (!apiOrigin && !targetUrl) return path;
255
+ return `${PORTAL_RETURN_PATH}?to=${encodePortalReturnTarget(target)}`;
256
+ };
257
+ return {
258
+ available,
259
+ apiOrigin,
260
+ portalHref,
261
+ url,
262
+ fetch: fetchWithSession,
263
+ async whoAmI() {
264
+ if (!available) return { signedIn: false };
265
+ const result = await request(
266
+ PORTAL_WHOAMI_PATH,
267
+ { headers: { accept: "application/json" } },
268
+ BRIDGE_PROBE_TIMEOUT_MS
269
+ );
270
+ return result.ok && result.data?.signedIn === true ? result.data : { signedIn: false };
271
+ },
272
+ async requestMagicLink(email, redirect) {
273
+ if (!available) return false;
274
+ const result = await send(PORTAL_MAGIC_LINK_PATH, "POST", {
275
+ email,
276
+ redirect: redirect ?? returnPath()
277
+ });
278
+ return result.ok && result.data?.ok === true;
279
+ },
280
+ async prefill() {
281
+ if (!available) return null;
282
+ const result = await get(PORTAL_PREFILL_PATH);
283
+ return result.ok ? result.data : null;
284
+ },
285
+ async mintPublicClaim() {
286
+ if (!available) return null;
287
+ const result = await send(PORTAL_PUBLIC_CLAIM_PATH, "POST", {});
288
+ return result.ok ? result.data : null;
289
+ },
290
+ async redeemApplicationRunHandoff(handoffId) {
291
+ if (!available) return null;
292
+ const result = await send(
293
+ `${PORTAL_APPLICATION_HANDOFF_PATH}/${encodeURIComponent(handoffId)}/redeem`,
294
+ "POST",
295
+ {}
296
+ );
297
+ return result.ok ? result.data : null;
298
+ },
299
+ returnPath,
300
+ get,
301
+ send,
302
+ delete: del
303
+ };
304
+ }
305
+
306
+ export {
307
+ normalizePortalApiOrigin,
308
+ encodePortalReturnTarget,
309
+ createPortalClient
310
+ };
@@ -1,15 +1,24 @@
1
1
  import {
2
2
  BookingBlockClient,
3
- CartClient,
4
3
  CertificateFunnelClient,
5
- CheckoutClient,
6
4
  PortalAccountClient,
7
- PortalPanelClient,
8
- StoreClient
9
- } from "./chunk-7ZOQ6UKG.js";
5
+ PortalPanelClient
6
+ } from "./chunk-4GKSSM5N.js";
10
7
  import {
11
8
  mountIslandsWith
12
9
  } from "./chunk-THMT43MV.js";
10
+ import {
11
+ CartClient
12
+ } from "./chunk-2CZFJOKK.js";
13
+ import {
14
+ CartButtonClient
15
+ } from "./chunk-ZNH6TSYP.js";
16
+ import {
17
+ CheckoutClient
18
+ } from "./chunk-S3ZQQOQT.js";
19
+ import {
20
+ StoreClient
21
+ } from "./chunk-UTAMB2SK.js";
13
22
 
14
23
  // src/islands-registry.tsx
15
24
  var ISLANDS = {
@@ -24,6 +33,12 @@ var ISLANDS = {
24
33
  "portal-account": PortalAccountClient,
25
34
  store: StoreClient,
26
35
  cart: CartClient,
36
+ // PAT-944 — the header cart. Admitted because it adds NO new reach: it reads the cart
37
+ // already in this browser (`cart-storage`) and, only once a shopper opens the drawer,
38
+ // posts those same stored lines to the `/api/store/quote` the cart island already
39
+ // calls. It makes no request on mount and renders no count on the server, so a header
40
+ // carrying it stays edge-cacheable.
41
+ "cart-button": CartButtonClient,
27
42
  checkout: CheckoutClient
28
43
  };
29
44
  function mountIslands(root, createRoot) {
@@ -0,0 +1,159 @@
1
+ import {
2
+ formatMoney
3
+ } from "./chunk-7BUDUYXN.js";
4
+ import {
5
+ adjustCartQuantity,
6
+ cartToWireLines,
7
+ removeFromCart
8
+ } from "./chunk-4NF7SSIX.js";
9
+
10
+ // src/cart-lines.tsx
11
+ import * as React from "react";
12
+ import { jsx, jsxs } from "react/jsx-runtime";
13
+ function quoteSourceQuantities(quoteSourceCart) {
14
+ return new Map(
15
+ quoteSourceCart?.lines.filter((line) => line.kind === "retail").map((line) => [line.variantId, line.quantity]) ?? []
16
+ );
17
+ }
18
+ function stepperBaseline(sourceQuantities, line) {
19
+ return (sourceQuantities.get(line.variantId) ?? 0) > line.quantity ? line.quantity : void 0;
20
+ }
21
+ function CartProblemList({
22
+ problems
23
+ }) {
24
+ if (problems.length === 0) return null;
25
+ return /* @__PURE__ */ jsx("ul", { className: "sk-cart__problems", role: "status", children: problems.map((p, i) => /* @__PURE__ */ jsx("li", { children: p.message }, `${p.code}-${p.variantId ?? i}`)) });
26
+ }
27
+ function UnsupportedCartLineList({
28
+ lines,
29
+ onMutate
30
+ }) {
31
+ if (lines.length === 0) return null;
32
+ return /* @__PURE__ */ jsx("ul", { className: "sk-cart__lines", children: lines.map((line) => /* @__PURE__ */ jsxs("li", { className: "sk-cart__line", children: [
33
+ /* @__PURE__ */ jsx("span", { className: "sk-cart__line-title", children: line.kind === "fill" ? "Prescription item" : "Unavailable item" }),
34
+ /* @__PURE__ */ jsx("span", { className: "sk-cart__line-unit", children: "Not supported by retail checkout" }),
35
+ /* @__PURE__ */ jsxs("span", { className: "sk-cart__qty", children: [
36
+ "Quantity ",
37
+ line.quantity
38
+ ] }),
39
+ /* @__PURE__ */ jsx(
40
+ "button",
41
+ {
42
+ type: "button",
43
+ className: "sk-cart__remove",
44
+ onClick: () => void onMutate(removeFromCart(line.variantId, line.kind)),
45
+ children: "Remove"
46
+ }
47
+ )
48
+ ] }, `${line.kind}-${line.variantId}`)) });
49
+ }
50
+ function CartLineList({
51
+ lines,
52
+ sourceQuantities,
53
+ onMutate
54
+ }) {
55
+ return /* @__PURE__ */ jsx("ul", { className: "sk-cart__lines", children: lines.map((line) => /* @__PURE__ */ jsxs("li", { className: "sk-cart__line", children: [
56
+ /* @__PURE__ */ jsx("a", { className: "sk-cart__line-title", href: `/store/${line.productHandle}`, children: line.title }),
57
+ /* @__PURE__ */ jsxs("span", { className: "sk-cart__line-unit", children: [
58
+ formatMoney(line.unitPrice),
59
+ " each"
60
+ ] }),
61
+ /* @__PURE__ */ jsxs("span", { className: "sk-cart__qty", children: [
62
+ /* @__PURE__ */ jsx(
63
+ "button",
64
+ {
65
+ type: "button",
66
+ "aria-label": `Decrease quantity of ${line.title}`,
67
+ onClick: () => void onMutate(
68
+ adjustCartQuantity(
69
+ "retail",
70
+ line.variantId,
71
+ -1,
72
+ stepperBaseline(sourceQuantities, line)
73
+ )
74
+ ),
75
+ children: "\u2212"
76
+ }
77
+ ),
78
+ /* @__PURE__ */ jsx("span", { "aria-live": "polite", children: line.quantity }),
79
+ /* @__PURE__ */ jsx(
80
+ "button",
81
+ {
82
+ type: "button",
83
+ "aria-label": `Increase quantity of ${line.title}`,
84
+ onClick: () => void onMutate(
85
+ adjustCartQuantity(
86
+ "retail",
87
+ line.variantId,
88
+ 1,
89
+ stepperBaseline(sourceQuantities, line)
90
+ )
91
+ ),
92
+ children: "+"
93
+ }
94
+ )
95
+ ] }),
96
+ /* @__PURE__ */ jsx("span", { className: "sk-cart__line-total", children: formatMoney(line.lineTotal) }),
97
+ /* @__PURE__ */ jsx(
98
+ "button",
99
+ {
100
+ type: "button",
101
+ className: "sk-cart__remove",
102
+ onClick: () => void onMutate(removeFromCart(line.variantId, "retail")),
103
+ children: "Remove"
104
+ }
105
+ )
106
+ ] }, line.variantId)) });
107
+ }
108
+ function CartTotals({ quote }) {
109
+ return /* @__PURE__ */ jsxs("dl", { className: "sk-cart__totals", children: [
110
+ /* @__PURE__ */ jsxs("div", { children: [
111
+ /* @__PURE__ */ jsx("dt", { children: "Subtotal" }),
112
+ /* @__PURE__ */ jsx("dd", { children: formatMoney(quote.subtotal) })
113
+ ] }),
114
+ /* @__PURE__ */ jsxs("div", { className: "sk-cart__gst-row", children: [
115
+ /* @__PURE__ */ jsx("dt", { children: "GST included" }),
116
+ /* @__PURE__ */ jsx("dd", { children: formatMoney(quote.taxTotal) })
117
+ ] }),
118
+ /* @__PURE__ */ jsxs("div", { className: "sk-cart__total-row", children: [
119
+ /* @__PURE__ */ jsx("dt", { children: "Total" }),
120
+ /* @__PURE__ */ jsx("dd", { children: formatMoney(quote.total) })
121
+ ] })
122
+ ] });
123
+ }
124
+ function useCartQuote(client, cart, enabled = true) {
125
+ const [state, setState] = React.useState({
126
+ quote: null,
127
+ quoteSourceCart: null,
128
+ failed: false
129
+ });
130
+ React.useEffect(() => {
131
+ if (!enabled || cart.lines.length === 0) {
132
+ setState({ quote: null, quoteSourceCart: null, failed: false });
133
+ return;
134
+ }
135
+ let alive = true;
136
+ setState({ quote: null, quoteSourceCart: null, failed: false });
137
+ void client.post("/api/store/quote", { lines: cartToWireLines(cart) }).then((result) => {
138
+ if (!alive) return;
139
+ if (!result.ok) {
140
+ setState({ quote: null, quoteSourceCart: null, failed: true });
141
+ return;
142
+ }
143
+ setState({ quote: result.data, quoteSourceCart: cart, failed: false });
144
+ });
145
+ return () => {
146
+ alive = false;
147
+ };
148
+ }, [cart, client, enabled]);
149
+ return state;
150
+ }
151
+
152
+ export {
153
+ quoteSourceQuantities,
154
+ CartProblemList,
155
+ UnsupportedCartLineList,
156
+ CartLineList,
157
+ CartTotals,
158
+ useCartQuote
159
+ };