@cmssy/react 0.1.9 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/client.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { B as BlockSchema, a as BlockMeta, C as CmssyPageData, b as BlockDefinition, c as CmssyFormDefinition, d as CmssyLayoutGroup } from './registry-e3O4s_bN.js';
2
+ import { B as BlockSchema, a as BlockMeta, C as CmssyPageData, b as BlockDefinition, c as CmssyFormDefinition, d as CmssyLayoutGroup, e as CmssyCart, f as CmssyOrder, g as CmssyProduct } from './commerce-queries-DV7PZlKS.js';
3
+ export { h as CmssyCartDiscount, i as CmssyCartItem, j as CmssyCartItemSnapshot, k as CmssyProductVariant } from './commerce-queries-DV7PZlKS.js';
3
4
  import { ReactNode } from 'react';
4
5
 
5
6
  interface EditBridgeConfig {
@@ -104,4 +105,40 @@ interface CmssyAuthProviderProps {
104
105
  declare function CmssyAuthProvider({ children, basePath, initialUser, }: CmssyAuthProviderProps): react_jsx_runtime.JSX.Element;
105
106
  declare function useCmssyUser(): CmssyAuthState;
106
107
 
107
- export { type CmssyAuthActionResult, CmssyAuthProvider, type CmssyAuthProviderProps, type CmssyAuthState, type CmssyAuthUser, CmssyEditableLayout, type CmssyEditableLayoutProps, CmssyEditablePage, type CmssyEditablePageProps, CmssyLazyEditor, type CmssyLazyEditorProps, CmssyLazyLayout, type CmssyLazyLayoutProps, type EditBridgeConfig, type EditBridgeState, type PatchMap, useCmssyUser, useEditBridge };
108
+ interface CmssyAddToCartOptions {
109
+ variantSelections?: Record<string, string>;
110
+ notes?: string;
111
+ }
112
+ interface CmssyCommerceState {
113
+ cart: CmssyCart | null;
114
+ loading: boolean;
115
+ error: string | null;
116
+ addToCart(recordId: string, quantity?: number, options?: CmssyAddToCartOptions): Promise<void>;
117
+ updateItem(itemId: string, quantity: number): Promise<void>;
118
+ removeItem(itemId: string): Promise<void>;
119
+ clearCart(): Promise<void>;
120
+ applyDiscount(code: string): Promise<void>;
121
+ removeDiscount(): Promise<void>;
122
+ checkout(customerEmail: string): Promise<CmssyOrder>;
123
+ refresh(): Promise<void>;
124
+ fetchProduct(modelSlug: string, filter: Record<string, unknown>): Promise<CmssyProduct | null>;
125
+ }
126
+ interface CmssyCommerceProviderProps {
127
+ children: ReactNode;
128
+ basePath?: string;
129
+ }
130
+ declare function CmssyCommerceProvider({ children, basePath, }: CmssyCommerceProviderProps): react_jsx_runtime.JSX.Element;
131
+ declare function useCart(): CmssyCommerceState;
132
+
133
+ declare const productBlock: BlockDefinition;
134
+
135
+ declare const cartBlock: BlockDefinition;
136
+
137
+ declare const checkoutBlock: BlockDefinition;
138
+
139
+ declare function fractionDigits(currency: string): number;
140
+ declare function fromMinorUnits(minor: number, currency: string): number;
141
+ declare function toMinorUnits(amount: number, currency: string): number;
142
+ declare function formatPrice(minor: number, currency: string | null | undefined): string;
143
+
144
+ export { type CmssyAddToCartOptions, type CmssyAuthActionResult, CmssyAuthProvider, type CmssyAuthProviderProps, type CmssyAuthState, type CmssyAuthUser, CmssyCart, CmssyCommerceProvider, type CmssyCommerceProviderProps, type CmssyCommerceState, CmssyEditableLayout, type CmssyEditableLayoutProps, CmssyEditablePage, type CmssyEditablePageProps, CmssyLazyEditor, type CmssyLazyEditorProps, CmssyLazyLayout, type CmssyLazyLayoutProps, CmssyOrder, CmssyProduct, type EditBridgeConfig, type EditBridgeState, type PatchMap, cartBlock, checkoutBlock, formatPrice, fractionDigits, fromMinorUnits, productBlock, toMinorUnits, useCart, useCmssyUser, useEditBridge };
package/dist/client.js CHANGED
@@ -3,6 +3,9 @@ import { createContext, useState, useRef, useEffect, useMemo, useCallback, useCo
3
3
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
4
 
5
5
  // src/registry.ts
6
+ function defineBlock(def) {
7
+ return def;
8
+ }
6
9
  function buildBlockMap(blocks) {
7
10
  const map = /* @__PURE__ */ Object.create(null);
8
11
  for (const block of blocks) map[block.type] = block.component;
@@ -804,11 +807,11 @@ function CmssyAuthProvider({
804
807
  [postAction, commitUser]
805
808
  );
806
809
  const register = useCallback(
807
- async (identity, password, fields) => {
810
+ async (identity, password, fields2) => {
808
811
  const data = await postAction("register", {
809
812
  identity,
810
813
  password,
811
- fields: fields ?? {}
814
+ fields: fields2 ?? {}
812
815
  });
813
816
  return { ok: Boolean(data.ok), message: data.message };
814
817
  },
@@ -839,5 +842,566 @@ function useCmssyUser() {
839
842
  }
840
843
  return ctx;
841
844
  }
845
+ var CmssyCommerceContext = createContext(null);
846
+ function CmssyCommerceProvider({
847
+ children,
848
+ basePath = "/api/cmssy/cart"
849
+ }) {
850
+ const base = useMemo(() => basePath.replace(/\/+$/, ""), [basePath]);
851
+ const generation = useRef(0);
852
+ const [cart, setCart] = useState(null);
853
+ const [loading, setLoading] = useState(true);
854
+ const [error, setError] = useState(null);
855
+ const post = useCallback(
856
+ async (action, body) => {
857
+ const res = await fetch(`${base}/${action}`, {
858
+ method: "POST",
859
+ credentials: "same-origin",
860
+ headers: { "content-type": "application/json" },
861
+ body: JSON.stringify(body)
862
+ });
863
+ const data = await res.json().catch(() => ({}));
864
+ if (!res.ok) {
865
+ throw new Error(
866
+ typeof data.message === "string" ? data.message : "Commerce request failed"
867
+ );
868
+ }
869
+ return data;
870
+ },
871
+ [base]
872
+ );
873
+ const runCart = useCallback(
874
+ async (action, body) => {
875
+ const gen = ++generation.current;
876
+ setError(null);
877
+ try {
878
+ const data = await post(action, body);
879
+ if (gen === generation.current) setCart(data.cart);
880
+ } catch (err) {
881
+ if (gen === generation.current) {
882
+ setError(
883
+ err instanceof Error ? err.message : "Commerce request failed"
884
+ );
885
+ }
886
+ throw err;
887
+ }
888
+ },
889
+ [post]
890
+ );
891
+ const refresh = useCallback(async () => {
892
+ setLoading(true);
893
+ try {
894
+ await runCart("cart", {});
895
+ } catch {
896
+ return;
897
+ } finally {
898
+ setLoading(false);
899
+ }
900
+ }, [runCart]);
901
+ useEffect(() => {
902
+ void refresh();
903
+ }, [refresh]);
904
+ const addToCart = useCallback(
905
+ (recordId, quantity = 1, options) => runCart("add", {
906
+ recordId,
907
+ quantity,
908
+ variantSelections: options?.variantSelections,
909
+ notes: options?.notes
910
+ }),
911
+ [runCart]
912
+ );
913
+ const updateItem = useCallback(
914
+ (itemId, quantity) => runCart("update", { itemId, quantity }),
915
+ [runCart]
916
+ );
917
+ const removeItem = useCallback(
918
+ (itemId) => runCart("remove", { itemId }),
919
+ [runCart]
920
+ );
921
+ const clearCart = useCallback(() => runCart("clear", {}), [runCart]);
922
+ const applyDiscount = useCallback(
923
+ (code) => runCart("apply-discount", { code }),
924
+ [runCart]
925
+ );
926
+ const removeDiscount = useCallback(
927
+ () => runCart("remove-discount", {}),
928
+ [runCart]
929
+ );
930
+ const checkout = useCallback(
931
+ async (customerEmail) => {
932
+ const gen = ++generation.current;
933
+ setError(null);
934
+ try {
935
+ const data = await post("checkout", {
936
+ customerEmail
937
+ });
938
+ if (gen === generation.current) setCart(null);
939
+ return data.order;
940
+ } catch (err) {
941
+ if (gen === generation.current) {
942
+ setError(err instanceof Error ? err.message : "Checkout failed");
943
+ }
944
+ throw err;
945
+ }
946
+ },
947
+ [post]
948
+ );
949
+ const fetchProduct = useCallback(
950
+ async (modelSlug, filter) => {
951
+ const data = await post("product", {
952
+ modelSlug,
953
+ filter
954
+ });
955
+ return data.product;
956
+ },
957
+ [post]
958
+ );
959
+ const value = useMemo(
960
+ () => ({
961
+ cart,
962
+ loading,
963
+ error,
964
+ addToCart,
965
+ updateItem,
966
+ removeItem,
967
+ clearCart,
968
+ applyDiscount,
969
+ removeDiscount,
970
+ checkout,
971
+ refresh,
972
+ fetchProduct
973
+ }),
974
+ [
975
+ cart,
976
+ loading,
977
+ error,
978
+ addToCart,
979
+ updateItem,
980
+ removeItem,
981
+ clearCart,
982
+ applyDiscount,
983
+ removeDiscount,
984
+ checkout,
985
+ refresh,
986
+ fetchProduct
987
+ ]
988
+ );
989
+ return /* @__PURE__ */ jsx(CmssyCommerceContext.Provider, { value, children });
990
+ }
991
+ function useCart() {
992
+ const ctx = useContext(CmssyCommerceContext);
993
+ if (!ctx) {
994
+ throw new Error("useCart must be used within <CmssyCommerceProvider>");
995
+ }
996
+ return ctx;
997
+ }
998
+
999
+ // src/fields.ts
1000
+ function control(type) {
1001
+ return (opts = {}) => ({
1002
+ type,
1003
+ label: opts.label ?? "",
1004
+ ...opts
1005
+ });
1006
+ }
1007
+ var fields = {
1008
+ singleLine: control("singleLine"),
1009
+ multiLine: control("multiLine"),
1010
+ richText: control("richText"),
1011
+ numeric: control("numeric"),
1012
+ date: control("date"),
1013
+ media: control("media"),
1014
+ link: control("link"),
1015
+ select: control("select"),
1016
+ multiselect: control("multiselect"),
1017
+ boolean: control("boolean"),
1018
+ color: control("color"),
1019
+ repeater: control("repeater")
1020
+ };
1021
+
1022
+ // src/commerce/money.ts
1023
+ var ZERO_DECIMAL = /* @__PURE__ */ new Set([
1024
+ "BIF",
1025
+ "CLP",
1026
+ "DJF",
1027
+ "GNF",
1028
+ "JPY",
1029
+ "KMF",
1030
+ "KRW",
1031
+ "MGA",
1032
+ "PYG",
1033
+ "RWF",
1034
+ "UGX",
1035
+ "VND",
1036
+ "VUV",
1037
+ "XAF",
1038
+ "XOF",
1039
+ "XPF"
1040
+ ]);
1041
+ var THREE_DECIMAL = /* @__PURE__ */ new Set([
1042
+ "BHD",
1043
+ "IQD",
1044
+ "JOD",
1045
+ "KWD",
1046
+ "LYD",
1047
+ "OMR",
1048
+ "TND"
1049
+ ]);
1050
+ function fractionDigits(currency) {
1051
+ const code = currency.toUpperCase();
1052
+ if (ZERO_DECIMAL.has(code)) return 0;
1053
+ if (THREE_DECIMAL.has(code)) return 3;
1054
+ return 2;
1055
+ }
1056
+ function fromMinorUnits(minor, currency) {
1057
+ return minor / 10 ** fractionDigits(currency);
1058
+ }
1059
+ function toMinorUnits(amount, currency) {
1060
+ return Math.round(amount * 10 ** fractionDigits(currency));
1061
+ }
1062
+ function formatPrice(minor, currency) {
1063
+ if (!Number.isFinite(minor)) return "";
1064
+ const code = currency ?? "USD";
1065
+ const amount = fromMinorUnits(minor, code);
1066
+ try {
1067
+ return new Intl.NumberFormat(void 0, {
1068
+ style: "currency",
1069
+ currency: code
1070
+ }).format(amount);
1071
+ } catch {
1072
+ return `${amount.toFixed(fractionDigits(code))} ${code}`;
1073
+ }
1074
+ }
1075
+ function deriveAxes(variants) {
1076
+ const axes = /* @__PURE__ */ new Map();
1077
+ for (const variant of variants) {
1078
+ for (const option of variant.selectedOptions) {
1079
+ if (!axes.has(option.name)) axes.set(option.name, /* @__PURE__ */ new Set());
1080
+ axes.get(option.name).add(option.value);
1081
+ }
1082
+ }
1083
+ return [...axes.entries()].map(([name, values]) => ({
1084
+ name,
1085
+ values: [...values]
1086
+ }));
1087
+ }
1088
+ function matchVariant(variants, selections) {
1089
+ return variants.find(
1090
+ (variant) => variant.selectedOptions.every(
1091
+ (opt) => selections[opt.name] === opt.value
1092
+ )
1093
+ ) ?? null;
1094
+ }
1095
+ function ProductComponent({ content }) {
1096
+ const c = content;
1097
+ const { fetchProduct, addToCart } = useCart();
1098
+ const [product, setProduct] = useState(null);
1099
+ const [loading, setLoading] = useState(true);
1100
+ const [selections, setSelections] = useState({});
1101
+ const [adding, setAdding] = useState(false);
1102
+ const [added, setAdded] = useState(false);
1103
+ const [addError, setAddError] = useState(null);
1104
+ const modelSlug = c.modelSlug ?? "products";
1105
+ const slugField = c.slugField ?? "slug";
1106
+ const slug = c.slug ?? "";
1107
+ useEffect(() => {
1108
+ let active = true;
1109
+ setLoading(true);
1110
+ void (async () => {
1111
+ try {
1112
+ const result = slug ? await fetchProduct(modelSlug, { [slugField]: slug }) : null;
1113
+ if (active) setProduct(result);
1114
+ } finally {
1115
+ if (active) setLoading(false);
1116
+ }
1117
+ })();
1118
+ return () => {
1119
+ active = false;
1120
+ };
1121
+ }, [fetchProduct, modelSlug, slugField, slug]);
1122
+ const axes = useMemo(
1123
+ () => product ? deriveAxes(product.variants) : [],
1124
+ [product]
1125
+ );
1126
+ const variant = useMemo(
1127
+ () => product ? matchVariant(product.variants, selections) : null,
1128
+ [product, selections]
1129
+ );
1130
+ if (loading) {
1131
+ return /* @__PURE__ */ jsx("div", { "data-cmssy-product": "loading", children: "Loading\u2026" });
1132
+ }
1133
+ if (!product) {
1134
+ return /* @__PURE__ */ jsx("div", { "data-cmssy-product": "not-found", children: "Product not found" });
1135
+ }
1136
+ const data = product.data;
1137
+ const name = String(data[c.nameField ?? "name"] ?? "");
1138
+ const imageUrl = c.imageField ? data[c.imageField] : null;
1139
+ const hasVariants = product.variants.length > 0;
1140
+ const currency = data.currency ?? "USD";
1141
+ const priceMinor = hasVariants ? variant?.price : toMinorUnits(Number(data[c.priceField ?? "price"] ?? 0), currency);
1142
+ const showPrice = priceMinor != null && Number.isFinite(priceMinor);
1143
+ const allAxesSelected = axes.every((axis) => selections[axis.name]);
1144
+ const outOfStock = variant != null && variant.inventory != null && variant.inventory <= 0;
1145
+ const canAdd = hasVariants ? Boolean(variant) && !outOfStock : true;
1146
+ async function onAdd() {
1147
+ if (!product) return;
1148
+ setAdding(true);
1149
+ setAdded(false);
1150
+ setAddError(null);
1151
+ try {
1152
+ await addToCart(
1153
+ product.id,
1154
+ 1,
1155
+ hasVariants ? { variantSelections: selections } : void 0
1156
+ );
1157
+ setAdded(true);
1158
+ } catch (err) {
1159
+ setAddError(err instanceof Error ? err.message : "Could not add to cart");
1160
+ } finally {
1161
+ setAdding(false);
1162
+ }
1163
+ }
1164
+ return /* @__PURE__ */ jsxs("div", { "data-cmssy-product": product.id, children: [
1165
+ imageUrl ? /* @__PURE__ */ jsx("img", { src: imageUrl, alt: name }) : null,
1166
+ /* @__PURE__ */ jsx("h3", { "data-cmssy-product-name": true, children: name }),
1167
+ showPrice ? /* @__PURE__ */ jsx("p", { "data-cmssy-product-price": true, children: formatPrice(priceMinor, currency) }) : null,
1168
+ axes.map((axis) => /* @__PURE__ */ jsxs("label", { "data-cmssy-variant-axis": axis.name, children: [
1169
+ /* @__PURE__ */ jsx("span", { children: axis.name }),
1170
+ /* @__PURE__ */ jsxs(
1171
+ "select",
1172
+ {
1173
+ value: selections[axis.name] ?? "",
1174
+ onChange: (e) => setSelections((prev) => ({
1175
+ ...prev,
1176
+ [axis.name]: e.target.value
1177
+ })),
1178
+ children: [
1179
+ /* @__PURE__ */ jsx("option", { value: "", children: "\u2014" }),
1180
+ axis.values.map((v) => /* @__PURE__ */ jsx("option", { value: v, children: v }, v))
1181
+ ]
1182
+ }
1183
+ )
1184
+ ] }, axis.name)),
1185
+ /* @__PURE__ */ jsx(
1186
+ "button",
1187
+ {
1188
+ type: "button",
1189
+ onClick: onAdd,
1190
+ disabled: adding || !canAdd || hasVariants && !allAxesSelected,
1191
+ "data-cmssy-add-to-cart": true,
1192
+ children: adding ? "Adding\u2026" : outOfStock ? "Out of stock" : added ? "Added" : "Add to cart"
1193
+ }
1194
+ ),
1195
+ addError ? /* @__PURE__ */ jsx("p", { "data-cmssy-product-error": true, children: addError }) : null
1196
+ ] });
1197
+ }
1198
+ var productBlock = defineBlock({
1199
+ type: "product",
1200
+ label: "Product",
1201
+ category: "Commerce",
1202
+ props: {
1203
+ modelSlug: fields.singleLine({ label: "Model slug" }),
1204
+ slugField: fields.singleLine({ label: "Slug field" }),
1205
+ slug: fields.singleLine({ label: "Product slug" }),
1206
+ nameField: fields.singleLine({ label: "Name field" }),
1207
+ priceField: fields.singleLine({ label: "Price field" }),
1208
+ imageField: fields.singleLine({ label: "Image field" })
1209
+ },
1210
+ component: ProductComponent
1211
+ });
1212
+ function CartComponent() {
1213
+ const {
1214
+ cart,
1215
+ loading,
1216
+ error,
1217
+ updateItem,
1218
+ removeItem,
1219
+ applyDiscount,
1220
+ removeDiscount
1221
+ } = useCart();
1222
+ const [code, setCode] = useState("");
1223
+ const [busy, setBusy] = useState(false);
1224
+ if (loading) {
1225
+ return /* @__PURE__ */ jsx("div", { "data-cmssy-cart": "loading", children: "Loading\u2026" });
1226
+ }
1227
+ if (!cart || cart.items.length === 0) {
1228
+ return /* @__PURE__ */ jsx("div", { "data-cmssy-cart": "empty", children: "Your cart is empty." });
1229
+ }
1230
+ const currency = cart.currency ?? "USD";
1231
+ async function withBusy(fn) {
1232
+ setBusy(true);
1233
+ try {
1234
+ await fn();
1235
+ } catch {
1236
+ setBusy(false);
1237
+ } finally {
1238
+ setBusy(false);
1239
+ }
1240
+ }
1241
+ return /* @__PURE__ */ jsxs("div", { "data-cmssy-cart": cart.id, children: [
1242
+ error ? /* @__PURE__ */ jsx("p", { "data-cmssy-cart-error": true, children: error }) : null,
1243
+ /* @__PURE__ */ jsx("ul", { "data-cmssy-cart-items": true, children: cart.items.map((item) => /* @__PURE__ */ jsxs("li", { "data-cmssy-cart-item": item.id, children: [
1244
+ /* @__PURE__ */ jsx("span", { "data-cmssy-item-name": true, children: item.snapshot.name }),
1245
+ /* @__PURE__ */ jsx(
1246
+ "input",
1247
+ {
1248
+ type: "number",
1249
+ min: "1",
1250
+ value: item.quantity,
1251
+ disabled: busy,
1252
+ onChange: (e) => {
1253
+ const qty = Number(e.target.value);
1254
+ if (Number.isInteger(qty) && qty >= 1) {
1255
+ void withBusy(() => updateItem(item.id, qty));
1256
+ }
1257
+ },
1258
+ "data-cmssy-item-qty": true
1259
+ }
1260
+ ),
1261
+ /* @__PURE__ */ jsx("span", { "data-cmssy-item-price": true, children: formatPrice(item.snapshot.price * item.quantity, currency) }),
1262
+ /* @__PURE__ */ jsx(
1263
+ "button",
1264
+ {
1265
+ type: "button",
1266
+ disabled: busy,
1267
+ onClick: () => void withBusy(() => removeItem(item.id)),
1268
+ "data-cmssy-item-remove": true,
1269
+ children: "Remove"
1270
+ }
1271
+ )
1272
+ ] }, item.id)) }),
1273
+ /* @__PURE__ */ jsx("div", { "data-cmssy-cart-discount": true, children: cart.appliedDiscount ? /* @__PURE__ */ jsxs("p", { children: [
1274
+ /* @__PURE__ */ jsx("span", { children: cart.appliedDiscount.code }),
1275
+ /* @__PURE__ */ jsx(
1276
+ "button",
1277
+ {
1278
+ type: "button",
1279
+ disabled: busy,
1280
+ onClick: () => void withBusy(() => removeDiscount()),
1281
+ children: "Remove"
1282
+ }
1283
+ )
1284
+ ] }) : /* @__PURE__ */ jsxs("p", { children: [
1285
+ /* @__PURE__ */ jsx(
1286
+ "input",
1287
+ {
1288
+ type: "text",
1289
+ value: code,
1290
+ placeholder: "Discount code",
1291
+ onChange: (e) => setCode(e.target.value),
1292
+ disabled: busy
1293
+ }
1294
+ ),
1295
+ /* @__PURE__ */ jsx(
1296
+ "button",
1297
+ {
1298
+ type: "button",
1299
+ disabled: busy || !code.trim(),
1300
+ onClick: () => void withBusy(async () => {
1301
+ await applyDiscount(code.trim());
1302
+ setCode("");
1303
+ }),
1304
+ children: "Apply"
1305
+ }
1306
+ )
1307
+ ] }) }),
1308
+ /* @__PURE__ */ jsxs("dl", { "data-cmssy-cart-totals": true, children: [
1309
+ /* @__PURE__ */ jsxs("div", { children: [
1310
+ /* @__PURE__ */ jsx("dt", { children: "Subtotal" }),
1311
+ /* @__PURE__ */ jsx("dd", { children: formatPrice(cart.subtotal, currency) })
1312
+ ] }),
1313
+ /* @__PURE__ */ jsxs("div", { children: [
1314
+ /* @__PURE__ */ jsx("dt", { children: "Total" }),
1315
+ /* @__PURE__ */ jsx("dd", { "data-cmssy-cart-total": true, children: formatPrice(cart.discountedTotal, currency) })
1316
+ ] })
1317
+ ] })
1318
+ ] });
1319
+ }
1320
+ var cartBlock = defineBlock({
1321
+ type: "cart",
1322
+ label: "Cart",
1323
+ category: "Commerce",
1324
+ props: {},
1325
+ component: CartComponent
1326
+ });
1327
+ var EMAIL_RE = /^[^@\s]+@[^@\s]+\.[^@\s]+$/;
1328
+ function CheckoutComponent({ content }) {
1329
+ const c = content;
1330
+ const { cart, loading, checkout } = useCart();
1331
+ const [email, setEmail] = useState("");
1332
+ const [busy, setBusy] = useState(false);
1333
+ const [error, setError] = useState(null);
1334
+ const [order, setOrder] = useState(null);
1335
+ if (order) {
1336
+ return /* @__PURE__ */ jsxs("div", { "data-cmssy-checkout": "done", children: [
1337
+ /* @__PURE__ */ jsx("p", { children: c.successMessage ?? "Order placed - awaiting payment." }),
1338
+ /* @__PURE__ */ jsx("p", { "data-cmssy-order-id": true, children: order.id }),
1339
+ /* @__PURE__ */ jsx("p", { "data-cmssy-order-total": true, children: formatPrice(order.total, order.currency) })
1340
+ ] });
1341
+ }
1342
+ if (loading) {
1343
+ return /* @__PURE__ */ jsx("div", { "data-cmssy-checkout": "loading", children: "Loading\u2026" });
1344
+ }
1345
+ if (!cart || cart.items.length === 0) {
1346
+ return /* @__PURE__ */ jsx("div", { "data-cmssy-checkout": "empty", children: "Your cart is empty." });
1347
+ }
1348
+ const emailValid = EMAIL_RE.test(email.trim());
1349
+ async function onSubmit() {
1350
+ setBusy(true);
1351
+ setError(null);
1352
+ try {
1353
+ setOrder(await checkout(email.trim()));
1354
+ } catch (err) {
1355
+ setError(err instanceof Error ? err.message : "Checkout failed");
1356
+ } finally {
1357
+ setBusy(false);
1358
+ }
1359
+ }
1360
+ return /* @__PURE__ */ jsxs(
1361
+ "form",
1362
+ {
1363
+ "data-cmssy-checkout": cart.id,
1364
+ onSubmit: (e) => {
1365
+ e.preventDefault();
1366
+ if (emailValid && !busy) void onSubmit();
1367
+ },
1368
+ children: [
1369
+ error ? /* @__PURE__ */ jsx("p", { "data-cmssy-checkout-error": true, children: error }) : null,
1370
+ /* @__PURE__ */ jsxs("label", { children: [
1371
+ /* @__PURE__ */ jsx("span", { children: "Email" }),
1372
+ /* @__PURE__ */ jsx(
1373
+ "input",
1374
+ {
1375
+ type: "email",
1376
+ value: email,
1377
+ onChange: (e) => setEmail(e.target.value),
1378
+ required: true,
1379
+ "data-cmssy-checkout-email": true
1380
+ }
1381
+ )
1382
+ ] }),
1383
+ /* @__PURE__ */ jsx("p", { "data-cmssy-checkout-total": true, children: formatPrice(cart.discountedTotal, cart.currency ?? "USD") }),
1384
+ /* @__PURE__ */ jsx(
1385
+ "button",
1386
+ {
1387
+ type: "submit",
1388
+ disabled: busy || !emailValid,
1389
+ "data-cmssy-checkout-submit": true,
1390
+ children: busy ? "Placing order\u2026" : "Place order"
1391
+ }
1392
+ )
1393
+ ]
1394
+ }
1395
+ );
1396
+ }
1397
+ var checkoutBlock = defineBlock({
1398
+ type: "checkout",
1399
+ label: "Checkout",
1400
+ category: "Commerce",
1401
+ props: {
1402
+ successMessage: fields.singleLine({ label: "Success message" })
1403
+ },
1404
+ component: CheckoutComponent
1405
+ });
842
1406
 
843
- export { CmssyAuthProvider, CmssyEditableLayout, CmssyEditablePage, CmssyLazyEditor, CmssyLazyLayout, useCmssyUser, useEditBridge };
1407
+ export { CmssyAuthProvider, CmssyCommerceProvider, CmssyEditableLayout, CmssyEditablePage, CmssyLazyEditor, CmssyLazyLayout, cartBlock, checkoutBlock, formatPrice, fractionDigits, fromMinorUnits, productBlock, toMinorUnits, useCart, useCmssyUser, useEditBridge };
@@ -312,4 +312,60 @@ declare function blocksToMeta(blocks: BlockDefinition[], defaults?: {
312
312
  category?: string;
313
313
  }): Record<string, BlockMeta>;
314
314
 
315
- export { type AppToEditorMessage as A, type BlockSchema as B, type CmssyPageData as C, type PatchMessage as D, type EditorToAppMessage as E, type FieldDefinition as F, type RawLayoutBlock as G, type ReadyMessage as H, SUBMIT_FORM_MUTATION as I, type SelectMessage as J, type SubmitFormInput as K, blocksToMeta as L, MODEL_DEFINITIONS_QUERY as M, blocksToSchemas as N, buildBlockContext as O, PROTOCOL_VERSION as P, buildBlockMap as Q, type RawBlock as R, SITE_CONFIG_QUERY as S, defineBlock as T, fetchLayouts as U, fetchPage as V, isProtocolCompatible as W, normalizeSlug as X, type BlockMeta as a, type BlockDefinition as b, type CmssyFormDefinition as c, type CmssyLayoutGroup as d, type FetchLike as e, type CmssyClientConfig as f, type BlockMap as g, type CmssyBlockContext as h, type BlockRect as i, type BoundsMessage as j, type ClickMessage as k, type CmssyBranding as l, type CmssyFormField as m, type CmssyFormSettings as n, type CmssyFormSubmitResponse as o, type CmssyLocaleContext as p, type CmssyModelDefinition as q, type CmssyModelRecord as r, type CmssyRecordList as s, type CmssySiteConfig as t, FORM_QUERY as u, type FetchLikeResponse as v, type FetchPageOptions as w, type FieldType as x, MODEL_RECORDS_QUERY as y, type ParentReadyMessage as z };
315
+ interface CmssyCartItemSnapshot {
316
+ name: string;
317
+ price: number;
318
+ currency: string;
319
+ imageUrl: string | null;
320
+ sku: string | null;
321
+ }
322
+ interface CmssyCartItem {
323
+ id: string;
324
+ recordId: string;
325
+ quantity: number;
326
+ variantSelections: Record<string, string> | null;
327
+ snapshot: CmssyCartItemSnapshot;
328
+ currentPrice: number | null;
329
+ priceMismatch: boolean;
330
+ }
331
+ interface CmssyCartDiscount {
332
+ code: string;
333
+ type: string;
334
+ value: number;
335
+ computedAmount: number;
336
+ }
337
+ interface CmssyCart {
338
+ id: string;
339
+ status: string;
340
+ items: CmssyCartItem[];
341
+ itemCount: number;
342
+ subtotal: number;
343
+ currency: string | null;
344
+ appliedDiscount: CmssyCartDiscount | null;
345
+ discountedTotal: number;
346
+ }
347
+ interface CmssyProductVariant {
348
+ id: string;
349
+ sku: string | null;
350
+ price: number;
351
+ inventory: number | null;
352
+ selectedOptions: Array<{
353
+ name: string;
354
+ value: string;
355
+ }>;
356
+ }
357
+ interface CmssyProduct {
358
+ id: string;
359
+ data: Record<string, unknown>;
360
+ variants: CmssyProductVariant[];
361
+ }
362
+ interface CmssyOrder {
363
+ id: string;
364
+ status: string;
365
+ subtotal: number;
366
+ total: number;
367
+ currency: string;
368
+ customerEmail: string;
369
+ }
370
+
371
+ export { fetchLayouts as $, type AppToEditorMessage as A, type BlockSchema as B, type CmssyPageData as C, type CmssySiteConfig as D, type EditorToAppMessage as E, type FieldDefinition as F, FORM_QUERY as G, type FetchLikeResponse as H, type FetchPageOptions as I, type FieldType as J, MODEL_RECORDS_QUERY as K, type ParentReadyMessage as L, MODEL_DEFINITIONS_QUERY as M, type PatchMessage as N, type RawLayoutBlock as O, PROTOCOL_VERSION as P, type ReadyMessage as Q, type RawBlock as R, SITE_CONFIG_QUERY as S, SUBMIT_FORM_MUTATION as T, type SelectMessage as U, type SubmitFormInput as V, blocksToMeta as W, blocksToSchemas as X, buildBlockContext as Y, buildBlockMap as Z, defineBlock as _, type BlockMeta as a, fetchPage as a0, isProtocolCompatible as a1, normalizeSlug as a2, type BlockDefinition as b, type CmssyFormDefinition as c, type CmssyLayoutGroup as d, type CmssyCart as e, type CmssyOrder as f, type CmssyProduct as g, type CmssyCartDiscount as h, type CmssyCartItem as i, type CmssyCartItemSnapshot as j, type CmssyProductVariant as k, type FetchLike as l, type CmssyClientConfig as m, type BlockMap as n, type CmssyBlockContext as o, type BlockRect as p, type BoundsMessage as q, type ClickMessage as r, type CmssyBranding as s, type CmssyFormField as t, type CmssyFormSettings as u, type CmssyFormSubmitResponse as v, type CmssyLocaleContext as w, type CmssyModelDefinition as x, type CmssyModelRecord as y, type CmssyRecordList as z };