@thebes/cadmea-ecommerce-ui 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 BowenLabs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,91 @@
1
+ # @thebes/cadmea-ecommerce-ui
2
+
3
+ Storefront SolidJS components for
4
+ [`@thebes/cadmea-plugin-ecommerce`](https://www.npmjs.com/package/@thebes/cadmea-plugin-ecommerce) —
5
+ `ProductDetail`, `CartProvider`/`useCart`, `CartDrawer`, `CheckoutForm`.
6
+
7
+ This is a plain **library** — no CMS-config opinion, no Cadmus interface,
8
+ the same "neither axis" categorization
9
+ [EXTENDING.md](https://github.com/bowenlabs/project-thebes/blob/main/EXTENDING.md)
10
+ gives `@thebes/cadmea-design-system`.
11
+
12
+ ```bash
13
+ pnpm add @thebes/cadmea-ecommerce-ui solid-js
14
+ ```
15
+
16
+ ## Why SolidJS here, when the public site's own tier is Alpine.js
17
+
18
+ `DECISIONS.md`'s "Component framework tiering" entry reserves SolidJS for
19
+ `core/`+the Panel and Alpine.js for the public site's own sprinkle-on
20
+ interactivity — but that same entry explicitly carves out framework
21
+ discretion for operator/community **extensions**, since they sit outside
22
+ the `core`/`custom` boundary. This package is exactly that kind of
23
+ extension. SolidJS specifically (not Alpine, not a third framework): cart/
24
+ checkout has real multi-step state, async tokenize callbacks, and
25
+ field-level validation — more than Alpine's `x-data`/`x-show` model
26
+ comfortably expresses — and Solid is already a runtime the Panel depends
27
+ on, so an operator using this on a page doesn't pay for a *second*
28
+ component-framework runtime.
29
+
30
+ Mounting these as Astro islands needs `@astrojs/solid-js` added to your
31
+ project — not implied by anything else in the stack.
32
+
33
+ ## One island, one `<CartProvider>`
34
+
35
+ Astro hydrates each `client:*` directive as its own isolated component
36
+ root — `CartProvider`'s context is **not** shared across separate islands.
37
+ Compose everything that needs to share cart state into one component, and
38
+ mount that as a single island:
39
+
40
+ ```tsx
41
+ // ShopIsland.tsx
42
+ import { CartDrawer, CartProvider, CheckoutForm, ProductDetail } from "@thebes/cadmea-ecommerce-ui";
43
+ import { createSquareCardField } from "@thebes/cadmea-plugin-ecommerce-square/client";
44
+
45
+ export function ShopIsland(props: { product: Product }) {
46
+ return (
47
+ <CartProvider>
48
+ <ProductDetail product={props.product} />
49
+ <CartDrawer
50
+ checkoutSlot={() => (
51
+ <CheckoutForm
52
+ tokenize={/* wire to createSquareCardField/createStripeCardField — see below */}
53
+ mountCardField={(el) => createSquareCardField(el, { applicationId, locationId })}
54
+ />
55
+ )}
56
+ />
57
+ </CartProvider>
58
+ );
59
+ }
60
+ ```
61
+
62
+ ```astro
63
+ ---
64
+ import { ShopIsland } from "../components/ShopIsland";
65
+ ---
66
+ <ShopIsland client:load product={product} />
67
+ ```
68
+
69
+ ## Provider-agnostic checkout
70
+
71
+ `CheckoutForm` takes a `tokenize: () => Promise<string>` prop rather than
72
+ knowing about Square/Stripe itself — wire in whichever provider's
73
+ `/client` helper (`createSquareCardField`/`createStripeCardField`, both
74
+ sharing the exact same `{ tokenize(), destroy() }` shape) you're using.
75
+ Swapping providers means swapping that one prop, not rewriting this
76
+ component.
77
+
78
+ ## Cart state
79
+
80
+ `CartProvider`/`useCart` is `localStorage`-backed, `createSignal`-based
81
+ reactive state — no DB sync needed until checkout, the same pattern found
82
+ independently in two of the three Next.js + Payload projects this
83
+ component's design was generalized from.
84
+
85
+ See `examples/cadmea-smb-template` in the
86
+ [main repo](https://github.com/bowenlabs/project-thebes) for all of this
87
+ wired together end to end, including the backend checkout/webhook Worker.
88
+
89
+ ## License
90
+
91
+ MIT © BowenLabs
@@ -0,0 +1,71 @@
1
+ import { Accessor, JSX } from "solid-js";
2
+
3
+ //#region src/CartDrawer.d.ts
4
+ interface CartDrawerProps {
5
+ /** Rendered inside the drawer below the line items, typically a checkout link/button. */
6
+ checkoutSlot?: () => JSX.Element;
7
+ }
8
+ declare function CartDrawer(props: CartDrawerProps): JSX.Element;
9
+ //#endregion
10
+ //#region src/CartProvider.d.ts
11
+ interface CartLineItem {
12
+ catalogRef: string;
13
+ name: string;
14
+ quantity: number;
15
+ unitPrice: {
16
+ amount: number;
17
+ currency: string;
18
+ };
19
+ }
20
+ interface CartContextValue {
21
+ items: Accessor<CartLineItem[]>;
22
+ isOpen: Accessor<boolean>;
23
+ subtotal: Accessor<number>;
24
+ addItem: (item: Omit<CartLineItem, "quantity">, quantity?: number) => void;
25
+ removeItem: (catalogRef: string) => void;
26
+ updateQuantity: (catalogRef: string, quantity: number) => void;
27
+ clear: () => void;
28
+ open: () => void;
29
+ close: () => void;
30
+ }
31
+ interface CartProviderProps {
32
+ children: JSX.Element;
33
+ }
34
+ declare function CartProvider(props: CartProviderProps): JSX.Element;
35
+ declare function useCart(): CartContextValue;
36
+ //#endregion
37
+ //#region src/CheckoutForm.d.ts
38
+ interface CheckoutFormProps {
39
+ /** Mounts the provider's card field and produces a one-time payment source token — see this package's README for wiring either provider's `/client` helper here. */
40
+ tokenize: () => Promise<string>;
41
+ /** Called once the provider's card field should be mounted — receives the container element to attach to. Most consumers wire this to `createSquareCardField(el, opts)`/`createStripeCardField(el, opts)` directly and ignore the return value here (the `tokenize` prop is what's actually called on submit). */
42
+ mountCardField?: (container: HTMLDivElement) => void;
43
+ /** Default: "/api/checkout". */
44
+ checkoutEndpoint?: string;
45
+ onSuccess?: (order: unknown) => void;
46
+ onError?: (error: Error) => void;
47
+ }
48
+ declare function CheckoutForm(props: CheckoutFormProps): import("solid-js").JSX.Element;
49
+ //#endregion
50
+ //#region src/ProductDetail.d.ts
51
+ interface ProductVariant {
52
+ sku: string;
53
+ catalogRef: string;
54
+ priceCents: number;
55
+ currency: string;
56
+ inventoryCount?: number;
57
+ }
58
+ interface Product {
59
+ id: number;
60
+ name: string;
61
+ description?: string;
62
+ variants: ProductVariant[];
63
+ }
64
+ interface ProductDetailProps {
65
+ product: Product;
66
+ onAddToCart?: (variant: ProductVariant) => void;
67
+ }
68
+ declare function ProductDetail(props: ProductDetailProps): import("solid-js").JSX.Element;
69
+ //#endregion
70
+ export { CartContextValue, CartDrawer, CartDrawerProps, CartLineItem, CartProvider, CartProviderProps, CheckoutForm, CheckoutFormProps, Product, ProductDetail, ProductDetailProps, ProductVariant, useCart };
71
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,339 @@
1
+ import { createComponent, delegateEvents, effect, insert, memo, setAttribute, template, use } from "solid-js/web";
2
+ import { For, Show, createContext, createEffect, createMemo, createSignal, onCleanup, onMount, useContext } from "solid-js";
3
+ //#region src/CartProvider.tsx
4
+ const CartContext = createContext();
5
+ const STORAGE_KEY = "cadmea-ecommerce-cart";
6
+ function loadStoredItems() {
7
+ if (typeof window === "undefined") return [];
8
+ try {
9
+ const raw = window.localStorage.getItem(STORAGE_KEY);
10
+ return raw ? JSON.parse(raw) : [];
11
+ } catch {
12
+ return [];
13
+ }
14
+ }
15
+ function CartProvider(props) {
16
+ const [items, setItems] = createSignal([]);
17
+ const [isOpen, setIsOpen] = createSignal(false);
18
+ onMount(() => setItems(loadStoredItems()));
19
+ function persist(next) {
20
+ setItems(next);
21
+ if (typeof window === "undefined") return;
22
+ window.localStorage.setItem(STORAGE_KEY, JSON.stringify(next));
23
+ }
24
+ const value = {
25
+ items,
26
+ isOpen,
27
+ subtotal: () => items().reduce((sum, item) => sum + item.unitPrice.amount * item.quantity, 0),
28
+ addItem(item, quantity = 1) {
29
+ if (items().find((i) => i.catalogRef === item.catalogRef)) persist(items().map((i) => i.catalogRef === item.catalogRef ? {
30
+ ...i,
31
+ quantity: i.quantity + quantity
32
+ } : i));
33
+ else persist([...items(), {
34
+ ...item,
35
+ quantity
36
+ }]);
37
+ },
38
+ removeItem(catalogRef) {
39
+ persist(items().filter((i) => i.catalogRef !== catalogRef));
40
+ },
41
+ updateQuantity(catalogRef, quantity) {
42
+ if (quantity <= 0) {
43
+ persist(items().filter((i) => i.catalogRef !== catalogRef));
44
+ return;
45
+ }
46
+ persist(items().map((i) => i.catalogRef === catalogRef ? {
47
+ ...i,
48
+ quantity
49
+ } : i));
50
+ },
51
+ clear() {
52
+ persist([]);
53
+ },
54
+ open: () => setIsOpen(true),
55
+ close: () => setIsOpen(false)
56
+ };
57
+ return createComponent(CartContext.Provider, {
58
+ value,
59
+ get children() {
60
+ return props.children;
61
+ }
62
+ });
63
+ }
64
+ function useCart() {
65
+ const context = useContext(CartContext);
66
+ if (!context) throw new Error("useCart() must be called within a <CartProvider>");
67
+ return context;
68
+ }
69
+ //#endregion
70
+ //#region src/CartDrawer.tsx
71
+ var _tmpl$$2 = /*#__PURE__*/ template(`<ul class="mt-4 flex flex-col gap-3">`), _tmpl$2$2 = /*#__PURE__*/ template(`<div class="mt-4 flex items-center justify-between border-t pt-4"><span class=font-semibold>Subtotal</span><span data-testid=cart-subtotal>`), _tmpl$3$1 = /*#__PURE__*/ template(`<div class="cart-drawer fixed inset-0 z-50"data-testid=cart-drawer><button type=button class="fixed inset-0 bg-[var(--color-backdrop)]"aria-label="Close cart"></button><div aria-live=polite aria-atomic=true class=sr-only></div><aside role=dialog aria-modal=true aria-label="Shopping cart"class="fixed right-0 top-0 h-full w-full max-w-sm bg-base-100 p-4 shadow-xl"><div class="flex items-center justify-between"><h2 class="text-lg font-bold">Your cart</h2><button type=button class="btn btn-sm btn-circle"aria-label="Close cart"><i class="ph ph-x"aria-hidden=true>`), _tmpl$4$1 = /*#__PURE__*/ template(`<p class=opacity-70>Your cart is empty.`), _tmpl$5$1 = /*#__PURE__*/ template(`<li class="flex items-center justify-between gap-2"><div><p class=font-medium></p><p class="text-sm opacity-70"> × </p></div><div class="flex items-center gap-1"><button type=button class="btn btn-xs">−</button><span></span><button type=button class="btn btn-xs">+</button><button type=button class="btn btn-xs btn-ghost"><i class="ph ph-trash"aria-hidden=true>`);
72
+ const FOCUSABLE_SELECTOR = "a[href], button:not([disabled]), input:not([disabled]), [tabindex]:not([tabindex=\"-1\"])";
73
+ function formatPrice$1(cents, currency) {
74
+ return new Intl.NumberFormat(void 0, {
75
+ style: "currency",
76
+ currency
77
+ }).format(cents / 100);
78
+ }
79
+ function CartDrawer(props) {
80
+ const cart = useCart();
81
+ let asideRef;
82
+ let closeButtonRef;
83
+ let triggeredBy = null;
84
+ createEffect(() => {
85
+ if (!cart.isOpen()) {
86
+ triggeredBy?.focus();
87
+ return;
88
+ }
89
+ triggeredBy = document.activeElement;
90
+ closeButtonRef?.focus();
91
+ const previousOverflow = document.body.style.overflow;
92
+ document.body.style.overflow = "hidden";
93
+ function onKeyDown(event) {
94
+ if (event.key === "Escape") {
95
+ cart.close();
96
+ return;
97
+ }
98
+ if (event.key !== "Tab" || !asideRef) return;
99
+ const focusable = Array.from(asideRef.querySelectorAll(FOCUSABLE_SELECTOR));
100
+ if (focusable.length === 0) return;
101
+ const first = focusable[0];
102
+ const last = focusable[focusable.length - 1];
103
+ if (event.shiftKey && document.activeElement === first) {
104
+ event.preventDefault();
105
+ last.focus();
106
+ } else if (!event.shiftKey && document.activeElement === last) {
107
+ event.preventDefault();
108
+ first.focus();
109
+ }
110
+ }
111
+ document.addEventListener("keydown", onKeyDown);
112
+ onCleanup(() => {
113
+ document.removeEventListener("keydown", onKeyDown);
114
+ document.body.style.overflow = previousOverflow;
115
+ });
116
+ });
117
+ return createComponent(Show, {
118
+ get when() {
119
+ return cart.isOpen();
120
+ },
121
+ get children() {
122
+ var _el$ = _tmpl$3$1(), _el$2 = _el$.firstChild, _el$3 = _el$2.nextSibling, _el$4 = _el$3.nextSibling, _el$7 = _el$4.firstChild.firstChild.nextSibling;
123
+ _el$2.$$click = () => cart.close();
124
+ insert(_el$3, createComponent(Show, {
125
+ get when() {
126
+ return cart.items().length > 0;
127
+ },
128
+ fallback: "Cart is empty.",
129
+ get children() {
130
+ return `Cart has ${cart.items().length} item${cart.items().length === 1 ? "" : "s"}, subtotal ${formatPrice$1(cart.subtotal(), cart.items()[0]?.unitPrice.currency ?? "USD")}.`;
131
+ }
132
+ }));
133
+ var _ref$ = asideRef;
134
+ typeof _ref$ === "function" ? use(_ref$, _el$4) : asideRef = _el$4;
135
+ _el$7.$$click = () => cart.close();
136
+ var _ref$2 = closeButtonRef;
137
+ typeof _ref$2 === "function" ? use(_ref$2, _el$7) : closeButtonRef = _el$7;
138
+ insert(_el$4, createComponent(Show, {
139
+ get when() {
140
+ return cart.items().length > 0;
141
+ },
142
+ get fallback() {
143
+ return _tmpl$4$1();
144
+ },
145
+ get children() {
146
+ return [
147
+ (() => {
148
+ var _el$8 = _tmpl$$2();
149
+ insert(_el$8, createComponent(For, {
150
+ get each() {
151
+ return cart.items();
152
+ },
153
+ children: (item) => (() => {
154
+ var _el$11 = _tmpl$5$1(), _el$12 = _el$11.firstChild, _el$13 = _el$12.firstChild, _el$14 = _el$13.nextSibling, _el$15 = _el$14.firstChild, _el$18 = _el$12.nextSibling.firstChild, _el$19 = _el$18.nextSibling, _el$20 = _el$19.nextSibling, _el$21 = _el$20.nextSibling;
155
+ insert(_el$13, () => item.name);
156
+ insert(_el$14, () => formatPrice$1(item.unitPrice.amount, item.unitPrice.currency), _el$15);
157
+ insert(_el$14, () => item.quantity, null);
158
+ _el$18.$$click = () => cart.updateQuantity(item.catalogRef, item.quantity - 1);
159
+ insert(_el$19, () => item.quantity);
160
+ _el$20.$$click = () => cart.updateQuantity(item.catalogRef, item.quantity + 1);
161
+ _el$21.$$click = () => cart.removeItem(item.catalogRef);
162
+ effect((_p$) => {
163
+ var _v$ = `Decrease quantity of ${item.name}`, _v$2 = `quantity-${item.catalogRef}`, _v$3 = `Increase quantity of ${item.name}`, _v$4 = `Remove ${item.name}`;
164
+ _v$ !== _p$.e && setAttribute(_el$18, "aria-label", _p$.e = _v$);
165
+ _v$2 !== _p$.t && setAttribute(_el$19, "data-testid", _p$.t = _v$2);
166
+ _v$3 !== _p$.a && setAttribute(_el$20, "aria-label", _p$.a = _v$3);
167
+ _v$4 !== _p$.o && setAttribute(_el$21, "aria-label", _p$.o = _v$4);
168
+ return _p$;
169
+ }, {
170
+ e: void 0,
171
+ t: void 0,
172
+ a: void 0,
173
+ o: void 0
174
+ });
175
+ return _el$11;
176
+ })()
177
+ }));
178
+ return _el$8;
179
+ })(),
180
+ (() => {
181
+ var _el$9 = _tmpl$2$2(), _el$1 = _el$9.firstChild.nextSibling;
182
+ insert(_el$1, () => formatPrice$1(cart.subtotal(), cart.items()[0]?.unitPrice.currency ?? "USD"));
183
+ return _el$9;
184
+ })(),
185
+ memo(() => props.checkoutSlot?.())
186
+ ];
187
+ }
188
+ }), null);
189
+ return _el$;
190
+ }
191
+ });
192
+ }
193
+ delegateEvents(["click"]);
194
+ //#endregion
195
+ //#region src/CheckoutForm.tsx
196
+ var _tmpl$$1 = /*#__PURE__*/ template(`<form class="checkout-form flex flex-col gap-4"><label class=form-control><span class=label-text>Email</span><input type=email required class="input input-bordered"></label><div data-testid=card-field-container></div><button type=submit class="btn btn-primary">`), _tmpl$2$1 = /*#__PURE__*/ template(`<p class=text-error role=alert data-testid=checkout-error>`);
197
+ function CheckoutForm(props) {
198
+ const cart = useCart();
199
+ const [email, setEmail] = createSignal("");
200
+ const [submitting, setSubmitting] = createSignal(false);
201
+ const [error, setError] = createSignal();
202
+ let cardContainer;
203
+ onMount(() => {
204
+ if (cardContainer) props.mountCardField?.(cardContainer);
205
+ });
206
+ async function handleSubmit(event) {
207
+ event.preventDefault();
208
+ setError(void 0);
209
+ setSubmitting(true);
210
+ try {
211
+ const paymentSourceToken = await props.tokenize();
212
+ const response = await fetch(props.checkoutEndpoint ?? "/api/checkout", {
213
+ method: "POST",
214
+ headers: { "Content-Type": "application/json" },
215
+ body: JSON.stringify({
216
+ lineItems: cart.items().map((item) => ({
217
+ catalogRef: item.catalogRef,
218
+ quantity: item.quantity,
219
+ clientUnitPrice: item.unitPrice
220
+ })),
221
+ paymentSourceToken,
222
+ customerEmail: email(),
223
+ idempotencyKey: crypto.randomUUID()
224
+ })
225
+ });
226
+ const body = await response.json();
227
+ if (!response.ok) throw new Error(typeof body?.error === "string" ? body.error : "Checkout failed");
228
+ cart.clear();
229
+ props.onSuccess?.(body.order ?? body);
230
+ } catch (cause) {
231
+ const message = cause instanceof Error ? cause.message : String(cause);
232
+ setError(message);
233
+ props.onError?.(cause instanceof Error ? cause : new Error(message));
234
+ } finally {
235
+ setSubmitting(false);
236
+ }
237
+ }
238
+ return (() => {
239
+ var _el$ = _tmpl$$1(), _el$2 = _el$.firstChild, _el$4 = _el$2.firstChild.nextSibling, _el$5 = _el$2.nextSibling, _el$6 = _el$5.nextSibling;
240
+ _el$.addEventListener("submit", handleSubmit);
241
+ _el$4.$$input = (e) => setEmail(e.currentTarget.value);
242
+ var _ref$ = cardContainer;
243
+ typeof _ref$ === "function" ? use(_ref$, _el$5) : cardContainer = _el$5;
244
+ insert(_el$, (() => {
245
+ var _c$ = memo(() => !!error());
246
+ return () => _c$() && (() => {
247
+ var _el$7 = _tmpl$2$1();
248
+ insert(_el$7, error);
249
+ return _el$7;
250
+ })();
251
+ })(), _el$6);
252
+ insert(_el$6, () => submitting() ? "Processing…" : "Pay now");
253
+ effect(() => _el$6.disabled = submitting());
254
+ effect(() => _el$4.value = email());
255
+ return _el$;
256
+ })();
257
+ }
258
+ delegateEvents(["input"]);
259
+ //#endregion
260
+ //#region src/ProductDetail.tsx
261
+ var _tmpl$ = /*#__PURE__*/ template(`<p class=opacity-80>`), _tmpl$2 = /*#__PURE__*/ template(`<label class="form-control w-full max-w-xs"><span class=label-text>Variant</span><select class="select select-bordered">`), _tmpl$3 = /*#__PURE__*/ template(`<div class=product-detail data-testid=product-detail><h1 class="text-2xl font-bold"></h1><button type=button class="btn btn-primary"><i class="ph ph-shopping-cart-simple"aria-hidden=true></i>Add to cart`), _tmpl$4 = /*#__PURE__*/ template(`<option>`), _tmpl$5 = /*#__PURE__*/ template(`<p class="text-xl font-semibold"data-testid=product-price>`);
262
+ function formatPrice(cents, currency) {
263
+ return new Intl.NumberFormat(void 0, {
264
+ style: "currency",
265
+ currency
266
+ }).format(cents / 100);
267
+ }
268
+ function ProductDetail(props) {
269
+ const cart = useCart();
270
+ const [selectedSku, setSelectedSku] = createSignal(props.product.variants[0]?.sku ?? "");
271
+ const selectedVariant = createMemo(() => props.product.variants.find((v) => v.sku === selectedSku()));
272
+ function handleAddToCart() {
273
+ const variant = selectedVariant();
274
+ if (!variant) return;
275
+ cart.addItem({
276
+ catalogRef: variant.catalogRef,
277
+ name: props.product.name,
278
+ unitPrice: {
279
+ amount: variant.priceCents,
280
+ currency: variant.currency
281
+ }
282
+ });
283
+ props.onAddToCart?.(variant);
284
+ }
285
+ return (() => {
286
+ var _el$ = _tmpl$3(), _el$2 = _el$.firstChild, _el$7 = _el$2.nextSibling;
287
+ insert(_el$2, () => props.product.name);
288
+ insert(_el$, createComponent(Show, {
289
+ get when() {
290
+ return props.product.description;
291
+ },
292
+ get children() {
293
+ var _el$3 = _tmpl$();
294
+ insert(_el$3, () => props.product.description);
295
+ return _el$3;
296
+ }
297
+ }), _el$7);
298
+ insert(_el$, createComponent(Show, {
299
+ get when() {
300
+ return props.product.variants.length > 1;
301
+ },
302
+ get children() {
303
+ var _el$4 = _tmpl$2(), _el$6 = _el$4.firstChild.nextSibling;
304
+ _el$6.addEventListener("change", (e) => setSelectedSku(e.currentTarget.value));
305
+ insert(_el$6, createComponent(For, {
306
+ get each() {
307
+ return props.product.variants;
308
+ },
309
+ children: (variant) => (() => {
310
+ var _el$8 = _tmpl$4();
311
+ insert(_el$8, () => variant.sku);
312
+ effect(() => _el$8.value = variant.sku);
313
+ return _el$8;
314
+ })()
315
+ }));
316
+ effect(() => _el$6.value = selectedSku());
317
+ return _el$4;
318
+ }
319
+ }), _el$7);
320
+ insert(_el$, createComponent(Show, {
321
+ get when() {
322
+ return selectedVariant();
323
+ },
324
+ children: (variant) => (() => {
325
+ var _el$9 = _tmpl$5();
326
+ insert(_el$9, () => formatPrice(variant().priceCents, variant().currency));
327
+ return _el$9;
328
+ })()
329
+ }), _el$7);
330
+ _el$7.$$click = handleAddToCart;
331
+ effect(() => _el$7.disabled = !selectedVariant());
332
+ return _el$;
333
+ })();
334
+ }
335
+ delegateEvents(["click"]);
336
+ //#endregion
337
+ export { CartDrawer, CartProvider, CheckoutForm, ProductDetail, useCart };
338
+
339
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["Accessor","createContext","createSignal","JSX","onMount","useContext","CartLineItem","catalogRef","name","quantity","unitPrice","amount","currency","CartContextValue","items","isOpen","subtotal","addItem","item","Omit","removeItem","updateQuantity","clear","open","close","CartContext","STORAGE_KEY","loadStoredItems","window","raw","localStorage","getItem","JSON","parse","CartProviderProps","children","Element","CartProvider","props","setItems","setIsOpen","persist","next","setItem","stringify","value","reduce","sum","existing","find","i","map","filter","_$createComponent","Provider","useCart","context","Error","createEffect","For","JSX","onCleanup","Show","useCart","FOCUSABLE_SELECTOR","formatPrice","cents","currency","Intl","NumberFormat","undefined","style","format","CartDrawerProps","checkoutSlot","Element","CartDrawer","props","cart","asideRef","HTMLElement","closeButtonRef","HTMLButtonElement","triggeredBy","isOpen","focus","document","activeElement","previousOverflow","body","overflow","onKeyDown","event","KeyboardEvent","key","close","focusable","Array","from","querySelectorAll","length","first","last","shiftKey","preventDefault","addEventListener","removeEventListener","_$createComponent","when","children","_el$","_tmpl$3","_el$2","firstChild","_el$3","nextSibling","_el$4","_el$5","_el$6","_el$7","$$click","_$insert","items","fallback","subtotal","unitPrice","_ref$","_$use","_ref$2","_tmpl$4","_el$8","_tmpl$","each","item","_el$11","_tmpl$5","_el$12","_el$13","_el$14","_el$15","_el$17","_el$18","_el$19","_el$20","_el$21","name","amount","quantity","updateQuantity","catalogRef","removeItem","_$effect","_p$","_v$","_v$2","_v$3","_v$4","e","_$setAttribute","t","a","o","_el$9","_tmpl$2","_el$0","_el$1","_$memo","_$delegateEvents","createSignal","onMount","useCart","CheckoutFormProps","tokenize","Promise","mountCardField","container","HTMLDivElement","checkoutEndpoint","onSuccess","order","onError","error","Error","CheckoutForm","props","cart","email","setEmail","submitting","setSubmitting","setError","cardContainer","handleSubmit","event","SubmitEvent","preventDefault","undefined","paymentSourceToken","response","fetch","method","headers","body","JSON","stringify","lineItems","items","map","item","catalogRef","quantity","clientUnitPrice","unitPrice","customerEmail","idempotencyKey","crypto","randomUUID","json","ok","clear","cause","message","String","_el$","_tmpl$","_el$2","firstChild","_el$3","_el$4","nextSibling","_el$5","_el$6","addEventListener","$$input","e","currentTarget","value","_ref$","_$use","_$insert","_c$","_$memo","_el$7","_tmpl$2","_$effect","disabled","_$delegateEvents","createMemo","createSignal","For","Show","useCart","ProductVariant","sku","catalogRef","priceCents","currency","inventoryCount","Product","id","name","description","variants","ProductDetailProps","product","onAddToCart","variant","formatPrice","cents","Intl","NumberFormat","undefined","style","format","ProductDetail","props","cart","selectedSku","setSelectedSku","selectedVariant","find","v","handleAddToCart","addItem","unitPrice","amount","_el$","_tmpl$3","_el$2","firstChild","_el$7","nextSibling","_$insert","_$createComponent","when","children","_el$3","_tmpl$","length","_el$4","_tmpl$2","_el$5","_el$6","addEventListener","e","currentTarget","value","each","_el$8","_tmpl$4","_$effect","_el$9","_tmpl$5","$$click","disabled","_$delegateEvents"],"sources":["../src/CartProvider.tsx","../src/CartDrawer.tsx","../src/CheckoutForm.tsx","../src/ProductDetail.tsx"],"sourcesContent":["// Copyright (c) 2026 BowenLabs. All rights reserved.\n// MIT licensed. See LICENSE in the repo root.\n//\n// localStorage-backed cart state, the same pattern found independently in\n// two of the three Next.js + Payload reference repos this package's\n// design was generalized from (a `CartContext`-style provider, no DB sync\n// needed until checkout). Pure SolidJS reactivity (createSignal), no\n// React-isms.\n\nimport {\n type Accessor,\n createContext,\n createSignal,\n type JSX,\n onMount,\n useContext,\n} from \"solid-js\";\n\nexport interface CartLineItem {\n catalogRef: string;\n name: string;\n quantity: number;\n unitPrice: { amount: number; currency: string };\n}\n\nexport interface CartContextValue {\n items: Accessor<CartLineItem[]>;\n isOpen: Accessor<boolean>;\n subtotal: Accessor<number>;\n addItem: (item: Omit<CartLineItem, \"quantity\">, quantity?: number) => void;\n removeItem: (catalogRef: string) => void;\n updateQuantity: (catalogRef: string, quantity: number) => void;\n clear: () => void;\n open: () => void;\n close: () => void;\n}\n\nconst CartContext = createContext<CartContextValue>();\n\nconst STORAGE_KEY = \"cadmea-ecommerce-cart\";\n\nfunction loadStoredItems(): CartLineItem[] {\n if (typeof window === \"undefined\") return [];\n try {\n const raw = window.localStorage.getItem(STORAGE_KEY);\n return raw ? (JSON.parse(raw) as CartLineItem[]) : [];\n } catch {\n // A corrupted or inaccessible localStorage entry shouldn't crash the\n // storefront — fall back to an empty cart, same as a first-ever visit.\n return [];\n }\n}\n\nexport interface CartProviderProps {\n children: JSX.Element;\n}\n\nexport function CartProvider(props: CartProviderProps) {\n const [items, setItems] = createSignal<CartLineItem[]>([]);\n const [isOpen, setIsOpen] = createSignal(false);\n\n // Deferred to onMount rather than read at module/signal-init time —\n // keeps this component safe to import in a context where `window` isn't\n // defined yet (e.g. a build-time render pass), even though every real\n // usage is a client-only island.\n onMount(() => setItems(loadStoredItems()));\n\n function persist(next: CartLineItem[]) {\n setItems(next);\n if (typeof window === \"undefined\") return;\n window.localStorage.setItem(STORAGE_KEY, JSON.stringify(next));\n }\n\n const value: CartContextValue = {\n items,\n isOpen,\n subtotal: () =>\n items().reduce(\n (sum, item) => sum + item.unitPrice.amount * item.quantity,\n 0,\n ),\n addItem(item, quantity = 1) {\n const existing = items().find((i) => i.catalogRef === item.catalogRef);\n if (existing) {\n persist(\n items().map((i) =>\n i.catalogRef === item.catalogRef\n ? { ...i, quantity: i.quantity + quantity }\n : i,\n ),\n );\n } else {\n persist([...items(), { ...item, quantity }]);\n }\n },\n removeItem(catalogRef) {\n persist(items().filter((i) => i.catalogRef !== catalogRef));\n },\n updateQuantity(catalogRef, quantity) {\n if (quantity <= 0) {\n persist(items().filter((i) => i.catalogRef !== catalogRef));\n return;\n }\n persist(\n items().map((i) =>\n i.catalogRef === catalogRef ? { ...i, quantity } : i,\n ),\n );\n },\n clear() {\n persist([]);\n },\n open: () => setIsOpen(true),\n close: () => setIsOpen(false),\n };\n\n return (\n <CartContext.Provider value={value}>{props.children}</CartContext.Provider>\n );\n}\n\nexport function useCart(): CartContextValue {\n const context = useContext(CartContext);\n if (!context) {\n throw new Error(\"useCart() must be called within a <CartProvider>\");\n }\n return context;\n}\n","// Copyright (c) 2026 BowenLabs. All rights reserved.\n// MIT licensed. See LICENSE in the repo root.\n\nimport { createEffect, For, type JSX, onCleanup, Show } from \"solid-js\";\nimport { useCart } from \"./CartProvider.js\";\n\nconst FOCUSABLE_SELECTOR =\n 'a[href], button:not([disabled]), input:not([disabled]), [tabindex]:not([tabindex=\"-1\"])';\n\nfunction formatPrice(cents: number, currency: string): string {\n return new Intl.NumberFormat(undefined, {\n style: \"currency\",\n currency,\n }).format(cents / 100);\n}\n\nexport interface CartDrawerProps {\n /** Rendered inside the drawer below the line items, typically a checkout link/button. */\n checkoutSlot?: () => JSX.Element;\n}\n\nexport function CartDrawer(props: CartDrawerProps) {\n const cart = useCart();\n\n let asideRef: HTMLElement | undefined;\n let closeButtonRef: HTMLButtonElement | undefined;\n let triggeredBy: HTMLElement | null = null;\n\n // The drawer is a modal dialog while open: move focus in, cycle Tab\n // within it, close on Escape, restore focus on close, and lock body\n // scroll behind it. Mirrors PanelNav/SearchPalette's focus-trap idiom.\n createEffect(() => {\n if (!cart.isOpen()) {\n triggeredBy?.focus();\n return;\n }\n\n triggeredBy = document.activeElement as HTMLElement | null;\n closeButtonRef?.focus();\n\n const previousOverflow = document.body.style.overflow;\n document.body.style.overflow = \"hidden\";\n\n function onKeyDown(event: KeyboardEvent) {\n if (event.key === \"Escape\") {\n cart.close();\n return;\n }\n if (event.key !== \"Tab\" || !asideRef) return;\n\n const focusable = Array.from(\n asideRef.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR),\n );\n if (focusable.length === 0) return;\n\n const first = focusable[0];\n const last = focusable[focusable.length - 1];\n\n if (event.shiftKey && document.activeElement === first) {\n event.preventDefault();\n last.focus();\n } else if (!event.shiftKey && document.activeElement === last) {\n event.preventDefault();\n first.focus();\n }\n }\n\n document.addEventListener(\"keydown\", onKeyDown);\n onCleanup(() => {\n document.removeEventListener(\"keydown\", onKeyDown);\n document.body.style.overflow = previousOverflow;\n });\n });\n\n return (\n <Show when={cart.isOpen()}>\n <div class=\"cart-drawer fixed inset-0 z-50\" data-testid=\"cart-drawer\">\n <button\n type=\"button\"\n class=\"fixed inset-0 bg-[var(--color-backdrop)]\"\n aria-label=\"Close cart\"\n onClick={() => cart.close()}\n />\n {/* Announce cart contents to assistive tech as items change. */}\n <div aria-live=\"polite\" aria-atomic=\"true\" class=\"sr-only\">\n <Show when={cart.items().length > 0} fallback=\"Cart is empty.\">\n {`Cart has ${cart.items().length} item${cart.items().length === 1 ? \"\" : \"s\"}, subtotal ${formatPrice(\n cart.subtotal(),\n cart.items()[0]?.unitPrice.currency ?? \"USD\",\n )}.`}\n </Show>\n </div>\n <aside\n ref={asideRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=\"Shopping cart\"\n class=\"fixed right-0 top-0 h-full w-full max-w-sm bg-base-100 p-4 shadow-xl\"\n >\n <div class=\"flex items-center justify-between\">\n <h2 class=\"text-lg font-bold\">Your cart</h2>\n <button\n ref={closeButtonRef}\n type=\"button\"\n class=\"btn btn-sm btn-circle\"\n aria-label=\"Close cart\"\n onClick={() => cart.close()}\n >\n <i class=\"ph ph-x\" aria-hidden=\"true\" />\n </button>\n </div>\n\n <Show\n when={cart.items().length > 0}\n fallback={<p class=\"opacity-70\">Your cart is empty.</p>}\n >\n <ul class=\"mt-4 flex flex-col gap-3\">\n <For each={cart.items()}>\n {(item) => (\n <li class=\"flex items-center justify-between gap-2\">\n <div>\n <p class=\"font-medium\">{item.name}</p>\n <p class=\"text-sm opacity-70\">\n {formatPrice(\n item.unitPrice.amount,\n item.unitPrice.currency,\n )}{\" \"}\n × {item.quantity}\n </p>\n </div>\n <div class=\"flex items-center gap-1\">\n <button\n type=\"button\"\n class=\"btn btn-xs\"\n aria-label={`Decrease quantity of ${item.name}`}\n onClick={() =>\n cart.updateQuantity(\n item.catalogRef,\n item.quantity - 1,\n )\n }\n >\n −\n </button>\n <span data-testid={`quantity-${item.catalogRef}`}>\n {item.quantity}\n </span>\n <button\n type=\"button\"\n class=\"btn btn-xs\"\n aria-label={`Increase quantity of ${item.name}`}\n onClick={() =>\n cart.updateQuantity(\n item.catalogRef,\n item.quantity + 1,\n )\n }\n >\n +\n </button>\n <button\n type=\"button\"\n class=\"btn btn-xs btn-ghost\"\n aria-label={`Remove ${item.name}`}\n onClick={() => cart.removeItem(item.catalogRef)}\n >\n <i class=\"ph ph-trash\" aria-hidden=\"true\" />\n </button>\n </div>\n </li>\n )}\n </For>\n </ul>\n\n <div class=\"mt-4 flex items-center justify-between border-t pt-4\">\n <span class=\"font-semibold\">Subtotal</span>\n <span data-testid=\"cart-subtotal\">\n {formatPrice(\n cart.subtotal(),\n cart.items()[0]?.unitPrice.currency ?? \"USD\",\n )}\n </span>\n </div>\n\n {props.checkoutSlot?.()}\n </Show>\n </aside>\n </div>\n </Show>\n );\n}\n","// Copyright (c) 2026 BowenLabs. All rights reserved.\n// MIT licensed. See LICENSE in the repo root.\n//\n// Provider-agnostic — takes a `tokenize` callback rather than knowing\n// about Square/Stripe itself. The consumer wires in whichever provider's\n// `/client` helper (`createSquareCardField`/`createStripeCardField`) it's\n// using; both share the exact `{ tokenize(): Promise<string> }` shape so\n// swapping providers means swapping that one prop, not rewriting this\n// component.\n\nimport { createSignal, onMount } from \"solid-js\";\nimport { useCart } from \"./CartProvider.js\";\n\nexport interface CheckoutFormProps {\n /** Mounts the provider's card field and produces a one-time payment source token — see this package's README for wiring either provider's `/client` helper here. */\n tokenize: () => Promise<string>;\n /** Called once the provider's card field should be mounted — receives the container element to attach to. Most consumers wire this to `createSquareCardField(el, opts)`/`createStripeCardField(el, opts)` directly and ignore the return value here (the `tokenize` prop is what's actually called on submit). */\n mountCardField?: (container: HTMLDivElement) => void;\n /** Default: \"/api/checkout\". */\n checkoutEndpoint?: string;\n onSuccess?: (order: unknown) => void;\n onError?: (error: Error) => void;\n}\n\nexport function CheckoutForm(props: CheckoutFormProps) {\n const cart = useCart();\n const [email, setEmail] = createSignal(\"\");\n const [submitting, setSubmitting] = createSignal(false);\n const [error, setError] = createSignal<string | undefined>();\n let cardContainer: HTMLDivElement | undefined;\n\n onMount(() => {\n if (cardContainer) props.mountCardField?.(cardContainer);\n });\n\n async function handleSubmit(event: SubmitEvent) {\n event.preventDefault();\n setError(undefined);\n setSubmitting(true);\n try {\n const paymentSourceToken = await props.tokenize();\n const response = await fetch(props.checkoutEndpoint ?? \"/api/checkout\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n lineItems: cart.items().map((item) => ({\n catalogRef: item.catalogRef,\n quantity: item.quantity,\n clientUnitPrice: item.unitPrice,\n })),\n paymentSourceToken,\n customerEmail: email(),\n idempotencyKey: crypto.randomUUID(),\n }),\n });\n const body = await response.json();\n if (!response.ok) {\n throw new Error(\n typeof body?.error === \"string\" ? body.error : \"Checkout failed\",\n );\n }\n cart.clear();\n props.onSuccess?.(body.order ?? body);\n } catch (cause) {\n const message = cause instanceof Error ? cause.message : String(cause);\n setError(message);\n props.onError?.(cause instanceof Error ? cause : new Error(message));\n } finally {\n setSubmitting(false);\n }\n }\n\n return (\n <form class=\"checkout-form flex flex-col gap-4\" onSubmit={handleSubmit}>\n <label class=\"form-control\">\n <span class=\"label-text\">Email</span>\n <input\n type=\"email\"\n required\n class=\"input input-bordered\"\n value={email()}\n onInput={(e) => setEmail(e.currentTarget.value)}\n />\n </label>\n\n <div ref={cardContainer} data-testid=\"card-field-container\" />\n\n {error() && (\n <p class=\"text-error\" role=\"alert\" data-testid=\"checkout-error\">\n {error()}\n </p>\n )}\n\n <button type=\"submit\" class=\"btn btn-primary\" disabled={submitting()}>\n {submitting() ? \"Processing…\" : \"Pay now\"}\n </button>\n </form>\n );\n}\n","// Copyright (c) 2026 BowenLabs. All rights reserved.\n// MIT licensed. See LICENSE in the repo root.\n\nimport { createMemo, createSignal, For, Show } from \"solid-js\";\nimport { useCart } from \"./CartProvider.js\";\n\nexport interface ProductVariant {\n sku: string;\n catalogRef: string;\n priceCents: number;\n currency: string;\n inventoryCount?: number;\n}\n\nexport interface Product {\n id: number;\n name: string;\n description?: string;\n variants: ProductVariant[];\n}\n\nexport interface ProductDetailProps {\n product: Product;\n onAddToCart?: (variant: ProductVariant) => void;\n}\n\nfunction formatPrice(cents: number, currency: string): string {\n return new Intl.NumberFormat(undefined, {\n style: \"currency\",\n currency,\n }).format(cents / 100);\n}\n\nexport function ProductDetail(props: ProductDetailProps) {\n const cart = useCart();\n const [selectedSku, setSelectedSku] = createSignal(\n props.product.variants[0]?.sku ?? \"\",\n );\n\n const selectedVariant = createMemo(() =>\n props.product.variants.find((v) => v.sku === selectedSku()),\n );\n\n function handleAddToCart() {\n const variant = selectedVariant();\n if (!variant) return;\n cart.addItem({\n catalogRef: variant.catalogRef,\n name: props.product.name,\n unitPrice: { amount: variant.priceCents, currency: variant.currency },\n });\n props.onAddToCart?.(variant);\n }\n\n return (\n <div class=\"product-detail\" data-testid=\"product-detail\">\n <h1 class=\"text-2xl font-bold\">{props.product.name}</h1>\n <Show when={props.product.description}>\n <p class=\"opacity-80\">{props.product.description}</p>\n </Show>\n\n <Show when={props.product.variants.length > 1}>\n <label class=\"form-control w-full max-w-xs\">\n <span class=\"label-text\">Variant</span>\n <select\n class=\"select select-bordered\"\n value={selectedSku()}\n onChange={(e) => setSelectedSku(e.currentTarget.value)}\n >\n <For each={props.product.variants}>\n {(variant) => <option value={variant.sku}>{variant.sku}</option>}\n </For>\n </select>\n </label>\n </Show>\n\n <Show when={selectedVariant()}>\n {(variant) => (\n <p class=\"text-xl font-semibold\" data-testid=\"product-price\">\n {formatPrice(variant().priceCents, variant().currency)}\n </p>\n )}\n </Show>\n\n <button\n type=\"button\"\n class=\"btn btn-primary\"\n disabled={!selectedVariant()}\n onClick={handleAddToCart}\n >\n <i class=\"ph ph-shopping-cart-simple\" aria-hidden=\"true\" />\n Add to cart\n </button>\n </div>\n );\n}\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@thebes/cadmea-ecommerce-ui",
3
+ "version": "1.0.0",
4
+ "description": "Storefront SolidJS components for @thebes/cadmea-plugin-ecommerce — ProductDetail, cart, and checkout",
5
+ "author": "BowenLabs <hello@bowenlabs.io>",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/bowenlabs/project-thebes",
10
+ "directory": "packages/cadmea-ecommerce-ui"
11
+ },
12
+ "homepage": "https://github.com/bowenlabs/project-thebes/tree/main/packages/cadmea-ecommerce-ui#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/bowenlabs/project-thebes/issues"
15
+ },
16
+ "keywords": [
17
+ "cadmea",
18
+ "cadmus",
19
+ "ecommerce",
20
+ "solidjs",
21
+ "cloudflare"
22
+ ],
23
+ "type": "module",
24
+ "main": "./dist/index.js",
25
+ "module": "./dist/index.js",
26
+ "types": "./dist/index.d.ts",
27
+ "exports": {
28
+ ".": {
29
+ "types": "./dist/index.d.ts",
30
+ "default": "./dist/index.js"
31
+ }
32
+ },
33
+ "peerDependencies": {
34
+ "solid-js": ">=1.9.0",
35
+ "@thebes/cadmus": "^0.2.1"
36
+ },
37
+ "devDependencies": {
38
+ "@solidjs/testing-library": "latest",
39
+ "@testing-library/dom": "latest",
40
+ "@testing-library/jest-dom": "latest",
41
+ "@rolldown/plugin-babel": "^0.2.3",
42
+ "babel-preset-solid": "^1.9.12",
43
+ "@babel/core": "^7.28.0",
44
+ "jsdom": "latest",
45
+ "solid-js": "^1.9.13",
46
+ "typescript": "latest",
47
+ "vite-plugin-solid": "latest",
48
+ "vite-plus": "latest",
49
+ "vitest": "latest",
50
+ "@thebes/cadmus": "^0.2.1"
51
+ },
52
+ "files": [
53
+ "dist",
54
+ "README.md",
55
+ "LICENSE"
56
+ ],
57
+ "scripts": {
58
+ "build": "vp pack",
59
+ "dev": "vp pack --watch",
60
+ "test": "vitest run",
61
+ "test:watch": "vitest"
62
+ }
63
+ }