@cmssy/react 0.1.9 → 0.2.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/dist/client.cjs +571 -2
- package/dist/client.d.cts +38 -2
- package/dist/client.d.ts +38 -2
- package/dist/client.js +564 -3
- package/dist/{registry-e3O4s_bN.d.cts → commerce-queries-DV7PZlKS.d.cts} +57 -1
- package/dist/{registry-e3O4s_bN.d.ts → commerce-queries-DV7PZlKS.d.ts} +57 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/package.json +1 -1
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,
|
|
810
|
+
async (identity, password, fields2) => {
|
|
808
811
|
const data = await postAction("register", {
|
|
809
812
|
identity,
|
|
810
813
|
password,
|
|
811
|
-
fields:
|
|
814
|
+
fields: fields2 ?? {}
|
|
812
815
|
});
|
|
813
816
|
return { ok: Boolean(data.ok), message: data.message };
|
|
814
817
|
},
|
|
@@ -839,5 +842,563 @@ 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 formatPrice(minor, currency) {
|
|
1060
|
+
if (!Number.isFinite(minor)) return "";
|
|
1061
|
+
const code = currency ?? "USD";
|
|
1062
|
+
const amount = fromMinorUnits(minor, code);
|
|
1063
|
+
try {
|
|
1064
|
+
return new Intl.NumberFormat(void 0, {
|
|
1065
|
+
style: "currency",
|
|
1066
|
+
currency: code
|
|
1067
|
+
}).format(amount);
|
|
1068
|
+
} catch {
|
|
1069
|
+
return `${amount.toFixed(fractionDigits(code))} ${code}`;
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
function deriveAxes(variants) {
|
|
1073
|
+
const axes = /* @__PURE__ */ new Map();
|
|
1074
|
+
for (const variant of variants) {
|
|
1075
|
+
for (const option of variant.selectedOptions) {
|
|
1076
|
+
if (!axes.has(option.name)) axes.set(option.name, /* @__PURE__ */ new Set());
|
|
1077
|
+
axes.get(option.name).add(option.value);
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
return [...axes.entries()].map(([name, values]) => ({
|
|
1081
|
+
name,
|
|
1082
|
+
values: [...values]
|
|
1083
|
+
}));
|
|
1084
|
+
}
|
|
1085
|
+
function matchVariant(variants, selections) {
|
|
1086
|
+
return variants.find(
|
|
1087
|
+
(variant) => variant.selectedOptions.every(
|
|
1088
|
+
(opt) => selections[opt.name] === opt.value
|
|
1089
|
+
)
|
|
1090
|
+
) ?? null;
|
|
1091
|
+
}
|
|
1092
|
+
function ProductComponent({ content }) {
|
|
1093
|
+
const c = content;
|
|
1094
|
+
const { fetchProduct, addToCart } = useCart();
|
|
1095
|
+
const [product, setProduct] = useState(null);
|
|
1096
|
+
const [loading, setLoading] = useState(true);
|
|
1097
|
+
const [selections, setSelections] = useState({});
|
|
1098
|
+
const [adding, setAdding] = useState(false);
|
|
1099
|
+
const [added, setAdded] = useState(false);
|
|
1100
|
+
const [addError, setAddError] = useState(null);
|
|
1101
|
+
const modelSlug = c.modelSlug ?? "products";
|
|
1102
|
+
const slugField = c.slugField ?? "slug";
|
|
1103
|
+
const slug = c.slug ?? "";
|
|
1104
|
+
useEffect(() => {
|
|
1105
|
+
let active = true;
|
|
1106
|
+
setLoading(true);
|
|
1107
|
+
void (async () => {
|
|
1108
|
+
try {
|
|
1109
|
+
const result = slug ? await fetchProduct(modelSlug, { [slugField]: slug }) : null;
|
|
1110
|
+
if (active) setProduct(result);
|
|
1111
|
+
} finally {
|
|
1112
|
+
if (active) setLoading(false);
|
|
1113
|
+
}
|
|
1114
|
+
})();
|
|
1115
|
+
return () => {
|
|
1116
|
+
active = false;
|
|
1117
|
+
};
|
|
1118
|
+
}, [fetchProduct, modelSlug, slugField, slug]);
|
|
1119
|
+
const axes = useMemo(
|
|
1120
|
+
() => product ? deriveAxes(product.variants) : [],
|
|
1121
|
+
[product]
|
|
1122
|
+
);
|
|
1123
|
+
const variant = useMemo(
|
|
1124
|
+
() => product ? matchVariant(product.variants, selections) : null,
|
|
1125
|
+
[product, selections]
|
|
1126
|
+
);
|
|
1127
|
+
if (loading) {
|
|
1128
|
+
return /* @__PURE__ */ jsx("div", { "data-cmssy-product": "loading", children: "Loading\u2026" });
|
|
1129
|
+
}
|
|
1130
|
+
if (!product) {
|
|
1131
|
+
return /* @__PURE__ */ jsx("div", { "data-cmssy-product": "not-found", children: "Product not found" });
|
|
1132
|
+
}
|
|
1133
|
+
const data = product.data;
|
|
1134
|
+
const name = String(data[c.nameField ?? "name"] ?? "");
|
|
1135
|
+
const imageUrl = c.imageField ? data[c.imageField] : null;
|
|
1136
|
+
const hasVariants = product.variants.length > 0;
|
|
1137
|
+
const currency = data.currency ?? "USD";
|
|
1138
|
+
const priceMinor = hasVariants ? variant?.price : Number(data[c.priceField ?? "price"] ?? 0);
|
|
1139
|
+
const showPrice = priceMinor != null && Number.isFinite(priceMinor);
|
|
1140
|
+
const allAxesSelected = axes.every((axis) => selections[axis.name]);
|
|
1141
|
+
const outOfStock = variant != null && variant.inventory != null && variant.inventory <= 0;
|
|
1142
|
+
const canAdd = hasVariants ? Boolean(variant) && !outOfStock : true;
|
|
1143
|
+
async function onAdd() {
|
|
1144
|
+
if (!product) return;
|
|
1145
|
+
setAdding(true);
|
|
1146
|
+
setAdded(false);
|
|
1147
|
+
setAddError(null);
|
|
1148
|
+
try {
|
|
1149
|
+
await addToCart(
|
|
1150
|
+
product.id,
|
|
1151
|
+
1,
|
|
1152
|
+
hasVariants ? { variantSelections: selections } : void 0
|
|
1153
|
+
);
|
|
1154
|
+
setAdded(true);
|
|
1155
|
+
} catch (err) {
|
|
1156
|
+
setAddError(err instanceof Error ? err.message : "Could not add to cart");
|
|
1157
|
+
} finally {
|
|
1158
|
+
setAdding(false);
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
return /* @__PURE__ */ jsxs("div", { "data-cmssy-product": product.id, children: [
|
|
1162
|
+
imageUrl ? /* @__PURE__ */ jsx("img", { src: imageUrl, alt: name }) : null,
|
|
1163
|
+
/* @__PURE__ */ jsx("h3", { "data-cmssy-product-name": true, children: name }),
|
|
1164
|
+
showPrice ? /* @__PURE__ */ jsx("p", { "data-cmssy-product-price": true, children: formatPrice(priceMinor, currency) }) : null,
|
|
1165
|
+
axes.map((axis) => /* @__PURE__ */ jsxs("label", { "data-cmssy-variant-axis": axis.name, children: [
|
|
1166
|
+
/* @__PURE__ */ jsx("span", { children: axis.name }),
|
|
1167
|
+
/* @__PURE__ */ jsxs(
|
|
1168
|
+
"select",
|
|
1169
|
+
{
|
|
1170
|
+
value: selections[axis.name] ?? "",
|
|
1171
|
+
onChange: (e) => setSelections((prev) => ({
|
|
1172
|
+
...prev,
|
|
1173
|
+
[axis.name]: e.target.value
|
|
1174
|
+
})),
|
|
1175
|
+
children: [
|
|
1176
|
+
/* @__PURE__ */ jsx("option", { value: "", children: "\u2014" }),
|
|
1177
|
+
axis.values.map((v) => /* @__PURE__ */ jsx("option", { value: v, children: v }, v))
|
|
1178
|
+
]
|
|
1179
|
+
}
|
|
1180
|
+
)
|
|
1181
|
+
] }, axis.name)),
|
|
1182
|
+
/* @__PURE__ */ jsx(
|
|
1183
|
+
"button",
|
|
1184
|
+
{
|
|
1185
|
+
type: "button",
|
|
1186
|
+
onClick: onAdd,
|
|
1187
|
+
disabled: adding || !canAdd || hasVariants && !allAxesSelected,
|
|
1188
|
+
"data-cmssy-add-to-cart": true,
|
|
1189
|
+
children: adding ? "Adding\u2026" : outOfStock ? "Out of stock" : added ? "Added" : "Add to cart"
|
|
1190
|
+
}
|
|
1191
|
+
),
|
|
1192
|
+
addError ? /* @__PURE__ */ jsx("p", { "data-cmssy-product-error": true, children: addError }) : null
|
|
1193
|
+
] });
|
|
1194
|
+
}
|
|
1195
|
+
var productBlock = defineBlock({
|
|
1196
|
+
type: "product",
|
|
1197
|
+
label: "Product",
|
|
1198
|
+
category: "Commerce",
|
|
1199
|
+
props: {
|
|
1200
|
+
modelSlug: fields.singleLine({ label: "Model slug" }),
|
|
1201
|
+
slugField: fields.singleLine({ label: "Slug field" }),
|
|
1202
|
+
slug: fields.singleLine({ label: "Product slug" }),
|
|
1203
|
+
nameField: fields.singleLine({ label: "Name field" }),
|
|
1204
|
+
priceField: fields.singleLine({ label: "Price field" }),
|
|
1205
|
+
imageField: fields.singleLine({ label: "Image field" })
|
|
1206
|
+
},
|
|
1207
|
+
component: ProductComponent
|
|
1208
|
+
});
|
|
1209
|
+
function CartComponent() {
|
|
1210
|
+
const {
|
|
1211
|
+
cart,
|
|
1212
|
+
loading,
|
|
1213
|
+
error,
|
|
1214
|
+
updateItem,
|
|
1215
|
+
removeItem,
|
|
1216
|
+
applyDiscount,
|
|
1217
|
+
removeDiscount
|
|
1218
|
+
} = useCart();
|
|
1219
|
+
const [code, setCode] = useState("");
|
|
1220
|
+
const [busy, setBusy] = useState(false);
|
|
1221
|
+
if (loading) {
|
|
1222
|
+
return /* @__PURE__ */ jsx("div", { "data-cmssy-cart": "loading", children: "Loading\u2026" });
|
|
1223
|
+
}
|
|
1224
|
+
if (!cart || cart.items.length === 0) {
|
|
1225
|
+
return /* @__PURE__ */ jsx("div", { "data-cmssy-cart": "empty", children: "Your cart is empty." });
|
|
1226
|
+
}
|
|
1227
|
+
const currency = cart.currency ?? "USD";
|
|
1228
|
+
async function withBusy(fn) {
|
|
1229
|
+
setBusy(true);
|
|
1230
|
+
try {
|
|
1231
|
+
await fn();
|
|
1232
|
+
} catch {
|
|
1233
|
+
setBusy(false);
|
|
1234
|
+
} finally {
|
|
1235
|
+
setBusy(false);
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
return /* @__PURE__ */ jsxs("div", { "data-cmssy-cart": cart.id, children: [
|
|
1239
|
+
error ? /* @__PURE__ */ jsx("p", { "data-cmssy-cart-error": true, children: error }) : null,
|
|
1240
|
+
/* @__PURE__ */ jsx("ul", { "data-cmssy-cart-items": true, children: cart.items.map((item) => /* @__PURE__ */ jsxs("li", { "data-cmssy-cart-item": item.id, children: [
|
|
1241
|
+
/* @__PURE__ */ jsx("span", { "data-cmssy-item-name": true, children: item.snapshot.name }),
|
|
1242
|
+
/* @__PURE__ */ jsx(
|
|
1243
|
+
"input",
|
|
1244
|
+
{
|
|
1245
|
+
type: "number",
|
|
1246
|
+
min: "1",
|
|
1247
|
+
value: item.quantity,
|
|
1248
|
+
disabled: busy,
|
|
1249
|
+
onChange: (e) => {
|
|
1250
|
+
const qty = Number(e.target.value);
|
|
1251
|
+
if (Number.isInteger(qty) && qty >= 1) {
|
|
1252
|
+
void withBusy(() => updateItem(item.id, qty));
|
|
1253
|
+
}
|
|
1254
|
+
},
|
|
1255
|
+
"data-cmssy-item-qty": true
|
|
1256
|
+
}
|
|
1257
|
+
),
|
|
1258
|
+
/* @__PURE__ */ jsx("span", { "data-cmssy-item-price": true, children: formatPrice(item.snapshot.price * item.quantity, currency) }),
|
|
1259
|
+
/* @__PURE__ */ jsx(
|
|
1260
|
+
"button",
|
|
1261
|
+
{
|
|
1262
|
+
type: "button",
|
|
1263
|
+
disabled: busy,
|
|
1264
|
+
onClick: () => void withBusy(() => removeItem(item.id)),
|
|
1265
|
+
"data-cmssy-item-remove": true,
|
|
1266
|
+
children: "Remove"
|
|
1267
|
+
}
|
|
1268
|
+
)
|
|
1269
|
+
] }, item.id)) }),
|
|
1270
|
+
/* @__PURE__ */ jsx("div", { "data-cmssy-cart-discount": true, children: cart.appliedDiscount ? /* @__PURE__ */ jsxs("p", { children: [
|
|
1271
|
+
/* @__PURE__ */ jsx("span", { children: cart.appliedDiscount.code }),
|
|
1272
|
+
/* @__PURE__ */ jsx(
|
|
1273
|
+
"button",
|
|
1274
|
+
{
|
|
1275
|
+
type: "button",
|
|
1276
|
+
disabled: busy,
|
|
1277
|
+
onClick: () => void withBusy(() => removeDiscount()),
|
|
1278
|
+
children: "Remove"
|
|
1279
|
+
}
|
|
1280
|
+
)
|
|
1281
|
+
] }) : /* @__PURE__ */ jsxs("p", { children: [
|
|
1282
|
+
/* @__PURE__ */ jsx(
|
|
1283
|
+
"input",
|
|
1284
|
+
{
|
|
1285
|
+
type: "text",
|
|
1286
|
+
value: code,
|
|
1287
|
+
placeholder: "Discount code",
|
|
1288
|
+
onChange: (e) => setCode(e.target.value),
|
|
1289
|
+
disabled: busy
|
|
1290
|
+
}
|
|
1291
|
+
),
|
|
1292
|
+
/* @__PURE__ */ jsx(
|
|
1293
|
+
"button",
|
|
1294
|
+
{
|
|
1295
|
+
type: "button",
|
|
1296
|
+
disabled: busy || !code.trim(),
|
|
1297
|
+
onClick: () => void withBusy(async () => {
|
|
1298
|
+
await applyDiscount(code.trim());
|
|
1299
|
+
setCode("");
|
|
1300
|
+
}),
|
|
1301
|
+
children: "Apply"
|
|
1302
|
+
}
|
|
1303
|
+
)
|
|
1304
|
+
] }) }),
|
|
1305
|
+
/* @__PURE__ */ jsxs("dl", { "data-cmssy-cart-totals": true, children: [
|
|
1306
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
1307
|
+
/* @__PURE__ */ jsx("dt", { children: "Subtotal" }),
|
|
1308
|
+
/* @__PURE__ */ jsx("dd", { children: formatPrice(cart.subtotal, currency) })
|
|
1309
|
+
] }),
|
|
1310
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
1311
|
+
/* @__PURE__ */ jsx("dt", { children: "Total" }),
|
|
1312
|
+
/* @__PURE__ */ jsx("dd", { "data-cmssy-cart-total": true, children: formatPrice(cart.discountedTotal, currency) })
|
|
1313
|
+
] })
|
|
1314
|
+
] })
|
|
1315
|
+
] });
|
|
1316
|
+
}
|
|
1317
|
+
var cartBlock = defineBlock({
|
|
1318
|
+
type: "cart",
|
|
1319
|
+
label: "Cart",
|
|
1320
|
+
category: "Commerce",
|
|
1321
|
+
props: {},
|
|
1322
|
+
component: CartComponent
|
|
1323
|
+
});
|
|
1324
|
+
var EMAIL_RE = /^[^@\s]+@[^@\s]+\.[^@\s]+$/;
|
|
1325
|
+
function CheckoutComponent({ content }) {
|
|
1326
|
+
const c = content;
|
|
1327
|
+
const { cart, loading, checkout } = useCart();
|
|
1328
|
+
const [email, setEmail] = useState("");
|
|
1329
|
+
const [busy, setBusy] = useState(false);
|
|
1330
|
+
const [error, setError] = useState(null);
|
|
1331
|
+
const [order, setOrder] = useState(null);
|
|
1332
|
+
if (order) {
|
|
1333
|
+
return /* @__PURE__ */ jsxs("div", { "data-cmssy-checkout": "done", children: [
|
|
1334
|
+
/* @__PURE__ */ jsx("p", { children: c.successMessage ?? "Order placed - awaiting payment." }),
|
|
1335
|
+
/* @__PURE__ */ jsx("p", { "data-cmssy-order-id": true, children: order.id }),
|
|
1336
|
+
/* @__PURE__ */ jsx("p", { "data-cmssy-order-total": true, children: formatPrice(order.total, order.currency) })
|
|
1337
|
+
] });
|
|
1338
|
+
}
|
|
1339
|
+
if (loading) {
|
|
1340
|
+
return /* @__PURE__ */ jsx("div", { "data-cmssy-checkout": "loading", children: "Loading\u2026" });
|
|
1341
|
+
}
|
|
1342
|
+
if (!cart || cart.items.length === 0) {
|
|
1343
|
+
return /* @__PURE__ */ jsx("div", { "data-cmssy-checkout": "empty", children: "Your cart is empty." });
|
|
1344
|
+
}
|
|
1345
|
+
const emailValid = EMAIL_RE.test(email.trim());
|
|
1346
|
+
async function onSubmit() {
|
|
1347
|
+
setBusy(true);
|
|
1348
|
+
setError(null);
|
|
1349
|
+
try {
|
|
1350
|
+
setOrder(await checkout(email.trim()));
|
|
1351
|
+
} catch (err) {
|
|
1352
|
+
setError(err instanceof Error ? err.message : "Checkout failed");
|
|
1353
|
+
} finally {
|
|
1354
|
+
setBusy(false);
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
return /* @__PURE__ */ jsxs(
|
|
1358
|
+
"form",
|
|
1359
|
+
{
|
|
1360
|
+
"data-cmssy-checkout": cart.id,
|
|
1361
|
+
onSubmit: (e) => {
|
|
1362
|
+
e.preventDefault();
|
|
1363
|
+
if (emailValid && !busy) void onSubmit();
|
|
1364
|
+
},
|
|
1365
|
+
children: [
|
|
1366
|
+
error ? /* @__PURE__ */ jsx("p", { "data-cmssy-checkout-error": true, children: error }) : null,
|
|
1367
|
+
/* @__PURE__ */ jsxs("label", { children: [
|
|
1368
|
+
/* @__PURE__ */ jsx("span", { children: "Email" }),
|
|
1369
|
+
/* @__PURE__ */ jsx(
|
|
1370
|
+
"input",
|
|
1371
|
+
{
|
|
1372
|
+
type: "email",
|
|
1373
|
+
value: email,
|
|
1374
|
+
onChange: (e) => setEmail(e.target.value),
|
|
1375
|
+
required: true,
|
|
1376
|
+
"data-cmssy-checkout-email": true
|
|
1377
|
+
}
|
|
1378
|
+
)
|
|
1379
|
+
] }),
|
|
1380
|
+
/* @__PURE__ */ jsx("p", { "data-cmssy-checkout-total": true, children: formatPrice(cart.discountedTotal, cart.currency ?? "USD") }),
|
|
1381
|
+
/* @__PURE__ */ jsx(
|
|
1382
|
+
"button",
|
|
1383
|
+
{
|
|
1384
|
+
type: "submit",
|
|
1385
|
+
disabled: busy || !emailValid,
|
|
1386
|
+
"data-cmssy-checkout-submit": true,
|
|
1387
|
+
children: busy ? "Placing order\u2026" : "Place order"
|
|
1388
|
+
}
|
|
1389
|
+
)
|
|
1390
|
+
]
|
|
1391
|
+
}
|
|
1392
|
+
);
|
|
1393
|
+
}
|
|
1394
|
+
var checkoutBlock = defineBlock({
|
|
1395
|
+
type: "checkout",
|
|
1396
|
+
label: "Checkout",
|
|
1397
|
+
category: "Commerce",
|
|
1398
|
+
props: {
|
|
1399
|
+
successMessage: fields.singleLine({ label: "Success message" })
|
|
1400
|
+
},
|
|
1401
|
+
component: CheckoutComponent
|
|
1402
|
+
});
|
|
842
1403
|
|
|
843
|
-
export { CmssyAuthProvider, CmssyEditableLayout, CmssyEditablePage, CmssyLazyEditor, CmssyLazyLayout, useCmssyUser, useEditBridge };
|
|
1404
|
+
export { CmssyAuthProvider, CmssyCommerceProvider, CmssyEditableLayout, CmssyEditablePage, CmssyLazyEditor, CmssyLazyLayout, cartBlock, checkoutBlock, formatPrice, fractionDigits, fromMinorUnits, productBlock, 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
|
-
|
|
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 };
|