merchi_cart 1.3.2 → 1.3.3

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.
@@ -0,0 +1,129 @@
1
+ function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
2
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
3
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
4
+ function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
5
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : String(i); }
6
+ function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
7
+ function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
8
+ function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
9
+ function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
10
+ function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
11
+ function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
12
+ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
13
+ import React, { useEffect, useMemo, useRef, useState } from 'react';
14
+ import { MerchiProductFormSdkExternals } from './product-form-sdk-externals';
15
+ import { loadFormBundle, publishedFormBundle } from '../utilities/customForm';
16
+ import { LoadingTemplateSm } from './LoadingTemplate';
17
+ import { jsx as _jsx } from "react/jsx-runtime";
18
+ import { Fragment as _Fragment } from "react/jsx-runtime";
19
+ /**
20
+ * Loads a product's published custom form for cart-item edit. On any failure
21
+ * renders `fallback` (the default merchi_product_form) so the cart stays usable.
22
+ */
23
+ export function CustomCartProductForm(_ref) {
24
+ var _publishedFormBundle$, _publishedFormBundle;
25
+ var product = _ref.product,
26
+ initialJob = _ref.initialJob,
27
+ apiUrl = _ref.apiUrl,
28
+ onSave = _ref.onSave,
29
+ fallback = _ref.fallback,
30
+ onActiveChange = _ref.onActiveChange;
31
+ var _useState = useState(null),
32
+ _useState2 = _slicedToArray(_useState, 2),
33
+ Loaded = _useState2[0],
34
+ setLoaded = _useState2[1];
35
+ var _useState3 = useState(false),
36
+ _useState4 = _slicedToArray(_useState3, 2),
37
+ failed = _useState4[0],
38
+ setFailed = _useState4[1];
39
+ var _useState5 = useState(true),
40
+ _useState6 = _slicedToArray(_useState5, 2),
41
+ loading = _useState6[0],
42
+ setLoading = _useState6[1];
43
+ var onSaveRef = useRef(onSave);
44
+ onSaveRef.current = onSave;
45
+ var onActiveChangeRef = useRef(onActiveChange);
46
+ onActiveChangeRef.current = onActiveChange;
47
+ var bundleKey = (_publishedFormBundle$ = (_publishedFormBundle = publishedFormBundle(product)) === null || _publishedFormBundle === void 0 ? void 0 : _publishedFormBundle.url) !== null && _publishedFormBundle$ !== void 0 ? _publishedFormBundle$ : '';
48
+ useEffect(function () {
49
+ var active = true;
50
+ setLoaded(null);
51
+ setFailed(false);
52
+ setLoading(true);
53
+ var bundle = publishedFormBundle(product);
54
+ if (!bundle) {
55
+ setFailed(true);
56
+ setLoading(false);
57
+ return;
58
+ }
59
+ var w = window;
60
+ w.React = React;
61
+ w.MerchiProductFormSdk = MerchiProductFormSdkExternals;
62
+ loadFormBundle(bundle).then(function (Component) {
63
+ if (active) {
64
+ setLoaded(function () {
65
+ return Component;
66
+ });
67
+ setLoading(false);
68
+ }
69
+ })["catch"](function (e) {
70
+ console.error('[merchi_cart] custom form load failed', e);
71
+ if (active) {
72
+ setFailed(true);
73
+ setLoading(false);
74
+ }
75
+ });
76
+ return function () {
77
+ active = false;
78
+ };
79
+ }, [product, bundleKey]);
80
+ var props = useMemo(function () {
81
+ try {
82
+ var runtime = MerchiProductFormSdkExternals.createProductFormRuntime({
83
+ apiUrl: apiUrl || '',
84
+ product: product,
85
+ onAddToCart: function onAddToCart(job) {
86
+ var _onSaveRef$current;
87
+ return (_onSaveRef$current = onSaveRef.current) === null || _onSaveRef$current === void 0 ? void 0 : _onSaveRef$current.call(onSaveRef, job);
88
+ }
89
+ });
90
+ return {
91
+ product: product,
92
+ pricing: runtime.pricing,
93
+ actions: runtime.actions,
94
+ helpers: runtime.helpers
95
+ };
96
+ } catch (e) {
97
+ console.error('[merchi_cart] custom form props build failed', e);
98
+ return null;
99
+ }
100
+ }, [product, apiUrl]);
101
+ var isActive = !failed && !loading && !!Loaded && !!props;
102
+ useEffect(function () {
103
+ var _onActiveChangeRef$cu;
104
+ (_onActiveChangeRef$cu = onActiveChangeRef.current) === null || _onActiveChangeRef$cu === void 0 || _onActiveChangeRef$cu.call(onActiveChangeRef, isActive);
105
+ return function () {
106
+ var _onActiveChangeRef$cu2;
107
+ return (_onActiveChangeRef$cu2 = onActiveChangeRef.current) === null || _onActiveChangeRef$cu2 === void 0 ? void 0 : _onActiveChangeRef$cu2.call(onActiveChangeRef, false);
108
+ };
109
+ }, [isActive]);
110
+ if (loading) return /*#__PURE__*/_jsx(LoadingTemplateSm, {});
111
+ if (failed || !Loaded || !props) return /*#__PURE__*/_jsx(_Fragment, {
112
+ children: fallback
113
+ });
114
+ var Shell = MerchiProductFormSdkExternals.ProductFormShell;
115
+ return /*#__PURE__*/_jsx(_Fragment, {
116
+ children: Shell ? /*#__PURE__*/_jsx(Shell, {
117
+ product: props.product,
118
+ pricing: props.pricing,
119
+ actions: props.actions,
120
+ helpers: props.helpers,
121
+ initialJob: initialJob,
122
+ actionLabels: {
123
+ addToCart: 'Save'
124
+ },
125
+ children: /*#__PURE__*/_jsx(Loaded, _objectSpread({}, props))
126
+ }) : /*#__PURE__*/_jsx(Loaded, _objectSpread({}, props))
127
+ });
128
+ }
129
+ export default CustomCartProductForm;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Full merchi_sdk_product_form runtime for window.MerchiProductFormSdk.
3
+ * Keep in sync with merchi_api/common/js/product_form_gate.cjs SDK_EXPORTS.
4
+ */
5
+ import { SDK_VERSION, serializeJob, nonEmptyGroups, formatCurrency, urlFor, createPricing, createProductFormRuntime, productHasGroups, groupFieldsOf, independentFieldsOf, isOptionQuantityGridProduct, buildProductFormJob, jobToFormState, selectionsToVariations, Section, Stack, Card, Heading, Text, Divider, Field, GroupRows, OptionQuantityGrid, ProductFormProvider, ProductFormShell, useProductForm, OPTION_FIELD_TYPES, theme } from 'merchi_sdk_product_form';
6
+ export var MerchiProductFormSdkExternals = {
7
+ SDK_VERSION: SDK_VERSION,
8
+ serializeJob: serializeJob,
9
+ nonEmptyGroups: nonEmptyGroups,
10
+ formatCurrency: formatCurrency,
11
+ urlFor: urlFor,
12
+ createPricing: createPricing,
13
+ createProductFormRuntime: createProductFormRuntime,
14
+ productHasGroups: productHasGroups,
15
+ groupFieldsOf: groupFieldsOf,
16
+ independentFieldsOf: independentFieldsOf,
17
+ isOptionQuantityGridProduct: isOptionQuantityGridProduct,
18
+ buildProductFormJob: buildProductFormJob,
19
+ jobToFormState: jobToFormState,
20
+ selectionsToVariations: selectionsToVariations,
21
+ Section: Section,
22
+ Stack: Stack,
23
+ Card: Card,
24
+ Heading: Heading,
25
+ Text: Text,
26
+ Divider: Divider,
27
+ Field: Field,
28
+ GroupRows: GroupRows,
29
+ OptionQuantityGrid: OptionQuantityGrid,
30
+ ProductFormProvider: ProductFormProvider,
31
+ ProductFormShell: ProductFormShell,
32
+ useProductForm: useProductForm,
33
+ OPTION_FIELD_TYPES: OPTION_FIELD_TYPES,
34
+ theme: theme
35
+ };
@@ -23,6 +23,8 @@ import { LoadingTemplateSm } from '../components/LoadingTemplate';
23
23
  import { useCartContext } from '../CartProvider';
24
24
  import { makeCartItem } from '../utilities/cart';
25
25
  import { getCartCookieToken } from '../utilities/cookie';
26
+ import { publishedFormBundle } from '../utilities/customForm';
27
+ import CustomCartProductForm from '../components/CustomCartProductForm';
26
28
  import { jsx as _jsx } from "react/jsx-runtime";
27
29
  import { jsxs as _jsxs } from "react/jsx-runtime";
28
30
  var MerchiProductForm = /*#__PURE__*/lazy(function () {
@@ -76,6 +78,10 @@ function PanelEditCartItem(_ref) {
76
78
  _useState2 = _slicedToArray(_useState, 2),
77
79
  loading = _useState2[0],
78
80
  setLoading = _useState2[1];
81
+ var _useState3 = useState(false),
82
+ _useState4 = _slicedToArray(_useState3, 2),
83
+ customFormActive = _useState4[0],
84
+ setCustomFormActive = _useState4[1];
79
85
  var formId = 'edit-cart-item-form';
80
86
 
81
87
  // This action patches the cart item
@@ -149,27 +155,36 @@ function PanelEditCartItem(_ref) {
149
155
  id: undefined
150
156
  });
151
157
  var showForm = activeTabIndex === tabIdItem && !!(cartItem !== null && cartItem !== void 0 && cartItem.id) && !!(cartItem !== null && cartItem !== void 0 && (_cartItem$product = cartItem.product) !== null && _cartItem$product !== void 0 && _cartItem$product.id);
158
+ var hasPublishedCustomForm = !!publishedFormBundle(cartItem === null || cartItem === void 0 ? void 0 : cartItem.product);
159
+ var defaultForm = /*#__PURE__*/_jsx(Suspense, {
160
+ fallback: /*#__PURE__*/_jsx(LoadingTemplateSm, {}),
161
+ children: /*#__PURE__*/_jsx(MerchiProductForm, _objectSpread({
162
+ apiUrl: apiUrl,
163
+ isCartItem: true,
164
+ initJob: _objectSpread({}, cleanCartItem),
165
+ initProduct: cartItem.product,
166
+ onSubmit: onSubmit,
167
+ productFormId: formId,
168
+ hideRequestQuotationButton: true,
169
+ hidePaymentUpfrontButton: true
170
+ }, productFormClassNames))
171
+ });
152
172
  return /*#__PURE__*/_jsxs(CartTabPanel, {
153
173
  tabId: tabIdItem,
154
174
  children: [/*#__PURE__*/_jsx(CartBody, {
155
175
  style: {
156
176
  padding: '2rem'
157
177
  },
158
- children: showForm && /*#__PURE__*/_jsx(Suspense, {
159
- fallback: /*#__PURE__*/_jsx(LoadingTemplateSm, {}),
160
- children: /*#__PURE__*/_jsx(MerchiProductForm, _objectSpread({
161
- apiUrl: apiUrl,
162
- isCartItem: true,
163
- initJob: _objectSpread({}, cleanCartItem),
164
- initProduct: cartItem.product,
165
- onSubmit: onSubmit,
166
- productFormId: formId,
167
- hideRequestQuotationButton: true,
168
- hidePaymentUpfrontButton: true
169
- }, productFormClassNames))
170
- })
178
+ children: showForm && (hasPublishedCustomForm ? /*#__PURE__*/_jsx(CustomCartProductForm, {
179
+ product: cartItem.product,
180
+ initialJob: cleanCartItem,
181
+ apiUrl: apiUrl,
182
+ onSave: onSubmit,
183
+ fallback: defaultForm,
184
+ onActiveChange: setCustomFormActive
185
+ }) : defaultForm)
171
186
  }), /*#__PURE__*/_jsxs(CartFooter, {
172
- children: [/*#__PURE__*/_jsx(ButtonBack, {}), /*#__PURE__*/_jsxs(Button, {
187
+ children: [/*#__PURE__*/_jsx(ButtonBack, {}), !customFormActive && /*#__PURE__*/_jsxs(Button, {
173
188
  className: classNameBtnEditCartItem,
174
189
  disabled: loading,
175
190
  form: formId,
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Helpers for loading a product's published custom form bundle in cart edit.
3
+ * Mirrors the product-embed loader: SRI script tag → window.MERCHI_PRODUCT_FORM_<id>.
4
+ */
5
+
6
+ /** If the product has a published custom form with a compiled bundle, return
7
+ * the info needed to load it; otherwise null (caller falls back). */
8
+ export function publishedFormBundle(product) {
9
+ var form = product === null || product === void 0 ? void 0 : product.productForm;
10
+ if (!form || form.status !== 'published') return null;
11
+ var version = form.activeVersion;
12
+ if (!version || !version.compiledBundleUrl) return null;
13
+ return {
14
+ url: version.compiledBundleUrl,
15
+ integrity: version.bundleSha || undefined,
16
+ globalName: "MERCHI_PRODUCT_FORM_".concat(form.id)
17
+ };
18
+ }
19
+
20
+ /** Dynamically load a compiled form bundle and resolve its default export. */
21
+ export function loadFormBundle(bundle) {
22
+ var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
23
+ var url = bundle.url,
24
+ integrity = bundle.integrity,
25
+ globalName = bundle.globalName;
26
+ var timeoutMs = opts.timeoutMs || 15000;
27
+ var w = window;
28
+ var existing = w[globalName] && w[globalName]["default"];
29
+ if (existing) return Promise.resolve(existing);
30
+ return new Promise(function (resolve, reject) {
31
+ var settled = false;
32
+ var finish = function finish(fn, arg) {
33
+ if (settled) return;
34
+ settled = true;
35
+ clearTimeout(timer);
36
+ fn(arg);
37
+ };
38
+ var timer = setTimeout(function () {
39
+ return finish(reject, new Error("form bundle ".concat(globalName, " timed out")));
40
+ }, timeoutMs);
41
+ var script = document.createElement('script');
42
+ script.src = url;
43
+ if (integrity) script.integrity = integrity;
44
+ script.crossOrigin = 'anonymous';
45
+ script.async = true;
46
+ script.onload = function () {
47
+ var mod = w[globalName];
48
+ if (mod && mod["default"]) finish(resolve, mod["default"]);else {
49
+ finish(reject, new Error("form bundle ".concat(globalName, " did not register a default export")));
50
+ }
51
+ };
52
+ script.onerror = function () {
53
+ return finish(reject, new Error("failed to load form bundle ".concat(globalName)));
54
+ };
55
+ document.head.appendChild(script);
56
+ });
57
+ }
@@ -184,6 +184,10 @@ var productWithImagesEmbed = {
184
184
  linkedFile: {}
185
185
  }
186
186
  },
187
+ // Published custom form pointer for cart-item edit (same shape as product embed).
188
+ productForm: {
189
+ activeVersion: {}
190
+ },
187
191
  taxType: {}
188
192
  };
189
193
  var cartShipmentQuote = {
@@ -0,0 +1,137 @@
1
+ "use strict";
2
+
3
+ function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.CustomCartProductForm = CustomCartProductForm;
8
+ exports["default"] = void 0;
9
+ var _react = _interopRequireWildcard(require("react"));
10
+ var _productFormSdkExternals = require("./product-form-sdk-externals");
11
+ var _customForm = require("../utilities/customForm");
12
+ var _LoadingTemplate = require("./LoadingTemplate");
13
+ var _jsxRuntime = require("react/jsx-runtime");
14
+ function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(e) { return e ? t : r; })(e); }
15
+ function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != _typeof(e) && "function" != typeof e) return { "default": e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n["default"] = e, t && t.set(e, n), n; }
16
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
17
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
18
+ function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
19
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : String(i); }
20
+ function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
21
+ function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
22
+ function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
23
+ function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
24
+ function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
25
+ function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
26
+ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
27
+ /**
28
+ * Loads a product's published custom form for cart-item edit. On any failure
29
+ * renders `fallback` (the default merchi_product_form) so the cart stays usable.
30
+ */
31
+ function CustomCartProductForm(_ref) {
32
+ var _publishedFormBundle$, _publishedFormBundle;
33
+ var product = _ref.product,
34
+ initialJob = _ref.initialJob,
35
+ apiUrl = _ref.apiUrl,
36
+ onSave = _ref.onSave,
37
+ fallback = _ref.fallback,
38
+ onActiveChange = _ref.onActiveChange;
39
+ var _useState = (0, _react.useState)(null),
40
+ _useState2 = _slicedToArray(_useState, 2),
41
+ Loaded = _useState2[0],
42
+ setLoaded = _useState2[1];
43
+ var _useState3 = (0, _react.useState)(false),
44
+ _useState4 = _slicedToArray(_useState3, 2),
45
+ failed = _useState4[0],
46
+ setFailed = _useState4[1];
47
+ var _useState5 = (0, _react.useState)(true),
48
+ _useState6 = _slicedToArray(_useState5, 2),
49
+ loading = _useState6[0],
50
+ setLoading = _useState6[1];
51
+ var onSaveRef = (0, _react.useRef)(onSave);
52
+ onSaveRef.current = onSave;
53
+ var onActiveChangeRef = (0, _react.useRef)(onActiveChange);
54
+ onActiveChangeRef.current = onActiveChange;
55
+ var bundleKey = (_publishedFormBundle$ = (_publishedFormBundle = (0, _customForm.publishedFormBundle)(product)) === null || _publishedFormBundle === void 0 ? void 0 : _publishedFormBundle.url) !== null && _publishedFormBundle$ !== void 0 ? _publishedFormBundle$ : '';
56
+ (0, _react.useEffect)(function () {
57
+ var active = true;
58
+ setLoaded(null);
59
+ setFailed(false);
60
+ setLoading(true);
61
+ var bundle = (0, _customForm.publishedFormBundle)(product);
62
+ if (!bundle) {
63
+ setFailed(true);
64
+ setLoading(false);
65
+ return;
66
+ }
67
+ var w = window;
68
+ w.React = _react["default"];
69
+ w.MerchiProductFormSdk = _productFormSdkExternals.MerchiProductFormSdkExternals;
70
+ (0, _customForm.loadFormBundle)(bundle).then(function (Component) {
71
+ if (active) {
72
+ setLoaded(function () {
73
+ return Component;
74
+ });
75
+ setLoading(false);
76
+ }
77
+ })["catch"](function (e) {
78
+ console.error('[merchi_cart] custom form load failed', e);
79
+ if (active) {
80
+ setFailed(true);
81
+ setLoading(false);
82
+ }
83
+ });
84
+ return function () {
85
+ active = false;
86
+ };
87
+ }, [product, bundleKey]);
88
+ var props = (0, _react.useMemo)(function () {
89
+ try {
90
+ var runtime = _productFormSdkExternals.MerchiProductFormSdkExternals.createProductFormRuntime({
91
+ apiUrl: apiUrl || '',
92
+ product: product,
93
+ onAddToCart: function onAddToCart(job) {
94
+ var _onSaveRef$current;
95
+ return (_onSaveRef$current = onSaveRef.current) === null || _onSaveRef$current === void 0 ? void 0 : _onSaveRef$current.call(onSaveRef, job);
96
+ }
97
+ });
98
+ return {
99
+ product: product,
100
+ pricing: runtime.pricing,
101
+ actions: runtime.actions,
102
+ helpers: runtime.helpers
103
+ };
104
+ } catch (e) {
105
+ console.error('[merchi_cart] custom form props build failed', e);
106
+ return null;
107
+ }
108
+ }, [product, apiUrl]);
109
+ var isActive = !failed && !loading && !!Loaded && !!props;
110
+ (0, _react.useEffect)(function () {
111
+ var _onActiveChangeRef$cu;
112
+ (_onActiveChangeRef$cu = onActiveChangeRef.current) === null || _onActiveChangeRef$cu === void 0 || _onActiveChangeRef$cu.call(onActiveChangeRef, isActive);
113
+ return function () {
114
+ var _onActiveChangeRef$cu2;
115
+ return (_onActiveChangeRef$cu2 = onActiveChangeRef.current) === null || _onActiveChangeRef$cu2 === void 0 ? void 0 : _onActiveChangeRef$cu2.call(onActiveChangeRef, false);
116
+ };
117
+ }, [isActive]);
118
+ if (loading) return /*#__PURE__*/(0, _jsxRuntime.jsx)(_LoadingTemplate.LoadingTemplateSm, {});
119
+ if (failed || !Loaded || !props) return /*#__PURE__*/(0, _jsxRuntime.jsx)(_jsxRuntime.Fragment, {
120
+ children: fallback
121
+ });
122
+ var Shell = _productFormSdkExternals.MerchiProductFormSdkExternals.ProductFormShell;
123
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_jsxRuntime.Fragment, {
124
+ children: Shell ? /*#__PURE__*/(0, _jsxRuntime.jsx)(Shell, {
125
+ product: props.product,
126
+ pricing: props.pricing,
127
+ actions: props.actions,
128
+ helpers: props.helpers,
129
+ initialJob: initialJob,
130
+ actionLabels: {
131
+ addToCart: 'Save'
132
+ },
133
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(Loaded, _objectSpread({}, props))
134
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(Loaded, _objectSpread({}, props))
135
+ });
136
+ }
137
+ var _default = exports["default"] = CustomCartProductForm;
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.MerchiProductFormSdkExternals = void 0;
7
+ var _merchi_sdk_product_form = require("merchi_sdk_product_form");
8
+ /**
9
+ * Full merchi_sdk_product_form runtime for window.MerchiProductFormSdk.
10
+ * Keep in sync with merchi_api/common/js/product_form_gate.cjs SDK_EXPORTS.
11
+ */
12
+
13
+ var MerchiProductFormSdkExternals = exports.MerchiProductFormSdkExternals = {
14
+ SDK_VERSION: _merchi_sdk_product_form.SDK_VERSION,
15
+ serializeJob: _merchi_sdk_product_form.serializeJob,
16
+ nonEmptyGroups: _merchi_sdk_product_form.nonEmptyGroups,
17
+ formatCurrency: _merchi_sdk_product_form.formatCurrency,
18
+ urlFor: _merchi_sdk_product_form.urlFor,
19
+ createPricing: _merchi_sdk_product_form.createPricing,
20
+ createProductFormRuntime: _merchi_sdk_product_form.createProductFormRuntime,
21
+ productHasGroups: _merchi_sdk_product_form.productHasGroups,
22
+ groupFieldsOf: _merchi_sdk_product_form.groupFieldsOf,
23
+ independentFieldsOf: _merchi_sdk_product_form.independentFieldsOf,
24
+ isOptionQuantityGridProduct: _merchi_sdk_product_form.isOptionQuantityGridProduct,
25
+ buildProductFormJob: _merchi_sdk_product_form.buildProductFormJob,
26
+ jobToFormState: _merchi_sdk_product_form.jobToFormState,
27
+ selectionsToVariations: _merchi_sdk_product_form.selectionsToVariations,
28
+ Section: _merchi_sdk_product_form.Section,
29
+ Stack: _merchi_sdk_product_form.Stack,
30
+ Card: _merchi_sdk_product_form.Card,
31
+ Heading: _merchi_sdk_product_form.Heading,
32
+ Text: _merchi_sdk_product_form.Text,
33
+ Divider: _merchi_sdk_product_form.Divider,
34
+ Field: _merchi_sdk_product_form.Field,
35
+ GroupRows: _merchi_sdk_product_form.GroupRows,
36
+ OptionQuantityGrid: _merchi_sdk_product_form.OptionQuantityGrid,
37
+ ProductFormProvider: _merchi_sdk_product_form.ProductFormProvider,
38
+ ProductFormShell: _merchi_sdk_product_form.ProductFormShell,
39
+ useProductForm: _merchi_sdk_product_form.useProductForm,
40
+ OPTION_FIELD_TYPES: _merchi_sdk_product_form.OPTION_FIELD_TYPES,
41
+ theme: _merchi_sdk_product_form.theme
42
+ };
@@ -14,7 +14,10 @@ var _LoadingTemplate = require("../components/LoadingTemplate");
14
14
  var _CartProvider = require("../CartProvider");
15
15
  var _cart = require("../utilities/cart");
16
16
  var _cookie = require("../utilities/cookie");
17
+ var _customForm = require("../utilities/customForm");
18
+ var _CustomCartProductForm = _interopRequireDefault(require("../components/CustomCartProductForm"));
17
19
  var _jsxRuntime = require("react/jsx-runtime");
20
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
18
21
  function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
19
22
  function _regeneratorRuntime() { "use strict"; /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */ _regeneratorRuntime = function _regeneratorRuntime() { return e; }; var t, e = {}, r = Object.prototype, n = r.hasOwnProperty, o = Object.defineProperty || function (t, e, r) { t[e] = r.value; }, i = "function" == typeof Symbol ? Symbol : {}, a = i.iterator || "@@iterator", c = i.asyncIterator || "@@asyncIterator", u = i.toStringTag || "@@toStringTag"; function define(t, e, r) { return Object.defineProperty(t, e, { value: r, enumerable: !0, configurable: !0, writable: !0 }), t[e]; } try { define({}, ""); } catch (t) { define = function define(t, e, r) { return t[e] = r; }; } function wrap(t, e, r, n) { var i = e && e.prototype instanceof Generator ? e : Generator, a = Object.create(i.prototype), c = new Context(n || []); return o(a, "_invoke", { value: makeInvokeMethod(t, r, c) }), a; } function tryCatch(t, e, r) { try { return { type: "normal", arg: t.call(e, r) }; } catch (t) { return { type: "throw", arg: t }; } } e.wrap = wrap; var h = "suspendedStart", l = "suspendedYield", f = "executing", s = "completed", y = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} var p = {}; define(p, a, function () { return this; }); var d = Object.getPrototypeOf, v = d && d(d(values([]))); v && v !== r && n.call(v, a) && (p = v); var g = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(p); function defineIteratorMethods(t) { ["next", "throw", "return"].forEach(function (e) { define(t, e, function (t) { return this._invoke(e, t); }); }); } function AsyncIterator(t, e) { function invoke(r, o, i, a) { var c = tryCatch(t[r], t, o); if ("throw" !== c.type) { var u = c.arg, h = u.value; return h && "object" == _typeof(h) && n.call(h, "__await") ? e.resolve(h.__await).then(function (t) { invoke("next", t, i, a); }, function (t) { invoke("throw", t, i, a); }) : e.resolve(h).then(function (t) { u.value = t, i(u); }, function (t) { return invoke("throw", t, i, a); }); } a(c.arg); } var r; o(this, "_invoke", { value: function value(t, n) { function callInvokeWithMethodAndArg() { return new e(function (e, r) { invoke(t, n, e, r); }); } return r = r ? r.then(callInvokeWithMethodAndArg, callInvokeWithMethodAndArg) : callInvokeWithMethodAndArg(); } }); } function makeInvokeMethod(e, r, n) { var o = h; return function (i, a) { if (o === f) throw new Error("Generator is already running"); if (o === s) { if ("throw" === i) throw a; return { value: t, done: !0 }; } for (n.method = i, n.arg = a;;) { var c = n.delegate; if (c) { var u = maybeInvokeDelegate(c, n); if (u) { if (u === y) continue; return u; } } if ("next" === n.method) n.sent = n._sent = n.arg;else if ("throw" === n.method) { if (o === h) throw o = s, n.arg; n.dispatchException(n.arg); } else "return" === n.method && n.abrupt("return", n.arg); o = f; var p = tryCatch(e, r, n); if ("normal" === p.type) { if (o = n.done ? s : l, p.arg === y) continue; return { value: p.arg, done: n.done }; } "throw" === p.type && (o = s, n.method = "throw", n.arg = p.arg); } }; } function maybeInvokeDelegate(e, r) { var n = r.method, o = e.iterator[n]; if (o === t) return r.delegate = null, "throw" === n && e.iterator["return"] && (r.method = "return", r.arg = t, maybeInvokeDelegate(e, r), "throw" === r.method) || "return" !== n && (r.method = "throw", r.arg = new TypeError("The iterator does not provide a '" + n + "' method")), y; var i = tryCatch(o, e.iterator, r.arg); if ("throw" === i.type) return r.method = "throw", r.arg = i.arg, r.delegate = null, y; var a = i.arg; return a ? a.done ? (r[e.resultName] = a.value, r.next = e.nextLoc, "return" !== r.method && (r.method = "next", r.arg = t), r.delegate = null, y) : a : (r.method = "throw", r.arg = new TypeError("iterator result is not an object"), r.delegate = null, y); } function pushTryEntry(t) { var e = { tryLoc: t[0] }; 1 in t && (e.catchLoc = t[1]), 2 in t && (e.finallyLoc = t[2], e.afterLoc = t[3]), this.tryEntries.push(e); } function resetTryEntry(t) { var e = t.completion || {}; e.type = "normal", delete e.arg, t.completion = e; } function Context(t) { this.tryEntries = [{ tryLoc: "root" }], t.forEach(pushTryEntry, this), this.reset(!0); } function values(e) { if (e || "" === e) { var r = e[a]; if (r) return r.call(e); if ("function" == typeof e.next) return e; if (!isNaN(e.length)) { var o = -1, i = function next() { for (; ++o < e.length;) if (n.call(e, o)) return next.value = e[o], next.done = !1, next; return next.value = t, next.done = !0, next; }; return i.next = i; } } throw new TypeError(_typeof(e) + " is not iterable"); } return GeneratorFunction.prototype = GeneratorFunctionPrototype, o(g, "constructor", { value: GeneratorFunctionPrototype, configurable: !0 }), o(GeneratorFunctionPrototype, "constructor", { value: GeneratorFunction, configurable: !0 }), GeneratorFunction.displayName = define(GeneratorFunctionPrototype, u, "GeneratorFunction"), e.isGeneratorFunction = function (t) { var e = "function" == typeof t && t.constructor; return !!e && (e === GeneratorFunction || "GeneratorFunction" === (e.displayName || e.name)); }, e.mark = function (t) { return Object.setPrototypeOf ? Object.setPrototypeOf(t, GeneratorFunctionPrototype) : (t.__proto__ = GeneratorFunctionPrototype, define(t, u, "GeneratorFunction")), t.prototype = Object.create(g), t; }, e.awrap = function (t) { return { __await: t }; }, defineIteratorMethods(AsyncIterator.prototype), define(AsyncIterator.prototype, c, function () { return this; }), e.AsyncIterator = AsyncIterator, e.async = function (t, r, n, o, i) { void 0 === i && (i = Promise); var a = new AsyncIterator(wrap(t, r, n, o), i); return e.isGeneratorFunction(r) ? a : a.next().then(function (t) { return t.done ? t.value : a.next(); }); }, defineIteratorMethods(g), define(g, u, "Generator"), define(g, a, function () { return this; }), define(g, "toString", function () { return "[object Generator]"; }), e.keys = function (t) { var e = Object(t), r = []; for (var n in e) r.push(n); return r.reverse(), function next() { for (; r.length;) { var t = r.pop(); if (t in e) return next.value = t, next.done = !1, next; } return next.done = !0, next; }; }, e.values = values, Context.prototype = { constructor: Context, reset: function reset(e) { if (this.prev = 0, this.next = 0, this.sent = this._sent = t, this.done = !1, this.delegate = null, this.method = "next", this.arg = t, this.tryEntries.forEach(resetTryEntry), !e) for (var r in this) "t" === r.charAt(0) && n.call(this, r) && !isNaN(+r.slice(1)) && (this[r] = t); }, stop: function stop() { this.done = !0; var t = this.tryEntries[0].completion; if ("throw" === t.type) throw t.arg; return this.rval; }, dispatchException: function dispatchException(e) { if (this.done) throw e; var r = this; function handle(n, o) { return a.type = "throw", a.arg = e, r.next = n, o && (r.method = "next", r.arg = t), !!o; } for (var o = this.tryEntries.length - 1; o >= 0; --o) { var i = this.tryEntries[o], a = i.completion; if ("root" === i.tryLoc) return handle("end"); if (i.tryLoc <= this.prev) { var c = n.call(i, "catchLoc"), u = n.call(i, "finallyLoc"); if (c && u) { if (this.prev < i.catchLoc) return handle(i.catchLoc, !0); if (this.prev < i.finallyLoc) return handle(i.finallyLoc); } else if (c) { if (this.prev < i.catchLoc) return handle(i.catchLoc, !0); } else { if (!u) throw new Error("try statement without catch or finally"); if (this.prev < i.finallyLoc) return handle(i.finallyLoc); } } } }, abrupt: function abrupt(t, e) { for (var r = this.tryEntries.length - 1; r >= 0; --r) { var o = this.tryEntries[r]; if (o.tryLoc <= this.prev && n.call(o, "finallyLoc") && this.prev < o.finallyLoc) { var i = o; break; } } i && ("break" === t || "continue" === t) && i.tryLoc <= e && e <= i.finallyLoc && (i = null); var a = i ? i.completion : {}; return a.type = t, a.arg = e, i ? (this.method = "next", this.next = i.finallyLoc, y) : this.complete(a); }, complete: function complete(t, e) { if ("throw" === t.type) throw t.arg; return "break" === t.type || "continue" === t.type ? this.next = t.arg : "return" === t.type ? (this.rval = this.arg = t.arg, this.method = "return", this.next = "end") : "normal" === t.type && e && (this.next = e), y; }, finish: function finish(t) { for (var e = this.tryEntries.length - 1; e >= 0; --e) { var r = this.tryEntries[e]; if (r.finallyLoc === t) return this.complete(r.completion, r.afterLoc), resetTryEntry(r), y; } }, "catch": function _catch(t) { for (var e = this.tryEntries.length - 1; e >= 0; --e) { var r = this.tryEntries[e]; if (r.tryLoc === t) { var n = r.completion; if ("throw" === n.type) { var o = n.arg; resetTryEntry(r); } return o; } } throw new Error("illegal catch attempt"); }, delegateYield: function delegateYield(e, r, n) { return this.delegate = { iterator: values(e), resultName: r, nextLoc: n }, "next" === this.method && (this.arg = t), y; } }, e; }
20
23
  function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }
@@ -81,6 +84,10 @@ function PanelEditCartItem(_ref) {
81
84
  _useState2 = _slicedToArray(_useState, 2),
82
85
  loading = _useState2[0],
83
86
  setLoading = _useState2[1];
87
+ var _useState3 = (0, _react.useState)(false),
88
+ _useState4 = _slicedToArray(_useState3, 2),
89
+ customFormActive = _useState4[0],
90
+ setCustomFormActive = _useState4[1];
84
91
  var formId = 'edit-cart-item-form';
85
92
 
86
93
  // This action patches the cart item
@@ -154,27 +161,36 @@ function PanelEditCartItem(_ref) {
154
161
  id: undefined
155
162
  });
156
163
  var showForm = activeTabIndex === _tabs.tabIdItem && !!(cartItem !== null && cartItem !== void 0 && cartItem.id) && !!(cartItem !== null && cartItem !== void 0 && (_cartItem$product = cartItem.product) !== null && _cartItem$product !== void 0 && _cartItem$product.id);
164
+ var hasPublishedCustomForm = !!(0, _customForm.publishedFormBundle)(cartItem === null || cartItem === void 0 ? void 0 : cartItem.product);
165
+ var defaultForm = /*#__PURE__*/(0, _jsxRuntime.jsx)(_react.Suspense, {
166
+ fallback: /*#__PURE__*/(0, _jsxRuntime.jsx)(_LoadingTemplate.LoadingTemplateSm, {}),
167
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(MerchiProductForm, _objectSpread({
168
+ apiUrl: apiUrl,
169
+ isCartItem: true,
170
+ initJob: _objectSpread({}, cleanCartItem),
171
+ initProduct: cartItem.product,
172
+ onSubmit: onSubmit,
173
+ productFormId: formId,
174
+ hideRequestQuotationButton: true,
175
+ hidePaymentUpfrontButton: true
176
+ }, productFormClassNames))
177
+ });
157
178
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_components.CartTabPanel, {
158
179
  tabId: _tabs.tabIdItem,
159
180
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_components.CartBody, {
160
181
  style: {
161
182
  padding: '2rem'
162
183
  },
163
- children: showForm && /*#__PURE__*/(0, _jsxRuntime.jsx)(_react.Suspense, {
164
- fallback: /*#__PURE__*/(0, _jsxRuntime.jsx)(_LoadingTemplate.LoadingTemplateSm, {}),
165
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(MerchiProductForm, _objectSpread({
166
- apiUrl: apiUrl,
167
- isCartItem: true,
168
- initJob: _objectSpread({}, cleanCartItem),
169
- initProduct: cartItem.product,
170
- onSubmit: onSubmit,
171
- productFormId: formId,
172
- hideRequestQuotationButton: true,
173
- hidePaymentUpfrontButton: true
174
- }, productFormClassNames))
175
- })
184
+ children: showForm && (hasPublishedCustomForm ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_CustomCartProductForm["default"], {
185
+ product: cartItem.product,
186
+ initialJob: cleanCartItem,
187
+ apiUrl: apiUrl,
188
+ onSave: onSubmit,
189
+ fallback: defaultForm,
190
+ onActiveChange: setCustomFormActive
191
+ }) : defaultForm)
176
192
  }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_components.CartFooter, {
177
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_buttons.ButtonBack, {}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_buttons.Button, {
193
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_buttons.ButtonBack, {}), !customFormActive && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_buttons.Button, {
178
194
  className: classNameBtnEditCartItem,
179
195
  disabled: loading,
180
196
  form: formId,
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.loadFormBundle = loadFormBundle;
7
+ exports.publishedFormBundle = publishedFormBundle;
8
+ /**
9
+ * Helpers for loading a product's published custom form bundle in cart edit.
10
+ * Mirrors the product-embed loader: SRI script tag → window.MERCHI_PRODUCT_FORM_<id>.
11
+ */
12
+
13
+ /** If the product has a published custom form with a compiled bundle, return
14
+ * the info needed to load it; otherwise null (caller falls back). */
15
+ function publishedFormBundle(product) {
16
+ var form = product === null || product === void 0 ? void 0 : product.productForm;
17
+ if (!form || form.status !== 'published') return null;
18
+ var version = form.activeVersion;
19
+ if (!version || !version.compiledBundleUrl) return null;
20
+ return {
21
+ url: version.compiledBundleUrl,
22
+ integrity: version.bundleSha || undefined,
23
+ globalName: "MERCHI_PRODUCT_FORM_".concat(form.id)
24
+ };
25
+ }
26
+
27
+ /** Dynamically load a compiled form bundle and resolve its default export. */
28
+ function loadFormBundle(bundle) {
29
+ var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
30
+ var url = bundle.url,
31
+ integrity = bundle.integrity,
32
+ globalName = bundle.globalName;
33
+ var timeoutMs = opts.timeoutMs || 15000;
34
+ var w = window;
35
+ var existing = w[globalName] && w[globalName]["default"];
36
+ if (existing) return Promise.resolve(existing);
37
+ return new Promise(function (resolve, reject) {
38
+ var settled = false;
39
+ var finish = function finish(fn, arg) {
40
+ if (settled) return;
41
+ settled = true;
42
+ clearTimeout(timer);
43
+ fn(arg);
44
+ };
45
+ var timer = setTimeout(function () {
46
+ return finish(reject, new Error("form bundle ".concat(globalName, " timed out")));
47
+ }, timeoutMs);
48
+ var script = document.createElement('script');
49
+ script.src = url;
50
+ if (integrity) script.integrity = integrity;
51
+ script.crossOrigin = 'anonymous';
52
+ script.async = true;
53
+ script.onload = function () {
54
+ var mod = w[globalName];
55
+ if (mod && mod["default"]) finish(resolve, mod["default"]);else {
56
+ finish(reject, new Error("form bundle ".concat(globalName, " did not register a default export")));
57
+ }
58
+ };
59
+ script.onerror = function () {
60
+ return finish(reject, new Error("failed to load form bundle ".concat(globalName)));
61
+ };
62
+ document.head.appendChild(script);
63
+ });
64
+ }
@@ -195,6 +195,10 @@ var productWithImagesEmbed = {
195
195
  linkedFile: {}
196
196
  }
197
197
  },
198
+ // Published custom form pointer for cart-item edit (same shape as product embed).
199
+ productForm: {
200
+ activeVersion: {}
201
+ },
198
202
  taxType: {}
199
203
  };
200
204
  var cartShipmentQuote = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "merchi_cart",
3
- "version": "1.3.2",
3
+ "version": "1.3.3",
4
4
  "description": "Merchi's cart",
5
5
  "source": "src/index.ts",
6
6
  "main": "lib/index.js",
@@ -91,6 +91,7 @@
91
91
  "js-cookie": "^3.0.5",
92
92
  "lodash": "^4.17.21",
93
93
  "merchi_product_form": "^1.16.0",
94
+ "merchi_sdk_product_form": "^1.0.7",
94
95
  "merchi_sdk_ts": "^1.23.0",
95
96
  "react-country-region-selector": "^3.6.1",
96
97
  "react-geosuggest": "^2.14.1",
@@ -0,0 +1,123 @@
1
+ import React, { useEffect, useMemo, useRef, useState } from 'react';
2
+ import { MerchiProductFormSdkExternals } from './product-form-sdk-externals';
3
+ import { loadFormBundle, publishedFormBundle } from '../utilities/customForm';
4
+ import { LoadingTemplateSm } from './LoadingTemplate';
5
+
6
+ interface Props {
7
+ product: any;
8
+ /** Current cart item used to hydrate quantity / selections. */
9
+ initialJob: any;
10
+ apiUrl?: string;
11
+ onSave: (job: any) => void;
12
+ /** Rendered when there is no published bundle or load/runtime fails. */
13
+ fallback: React.ReactNode;
14
+ /** Notifies the host when the custom form is actively rendering. */
15
+ onActiveChange?: (active: boolean) => void;
16
+ }
17
+
18
+ /**
19
+ * Loads a product's published custom form for cart-item edit. On any failure
20
+ * renders `fallback` (the default merchi_product_form) so the cart stays usable.
21
+ */
22
+ export function CustomCartProductForm({
23
+ product,
24
+ initialJob,
25
+ apiUrl,
26
+ onSave,
27
+ fallback,
28
+ onActiveChange,
29
+ }: Props) {
30
+ const [Loaded, setLoaded] = useState<React.ComponentType<any> | null>(null);
31
+ const [failed, setFailed] = useState(false);
32
+ const [loading, setLoading] = useState(true);
33
+ const onSaveRef = useRef(onSave);
34
+ onSaveRef.current = onSave;
35
+ const onActiveChangeRef = useRef(onActiveChange);
36
+ onActiveChangeRef.current = onActiveChange;
37
+
38
+ const bundleKey = publishedFormBundle(product)?.url ?? '';
39
+
40
+ useEffect(() => {
41
+ let active = true;
42
+ setLoaded(null);
43
+ setFailed(false);
44
+ setLoading(true);
45
+ const bundle = publishedFormBundle(product);
46
+ if (!bundle) {
47
+ setFailed(true);
48
+ setLoading(false);
49
+ return;
50
+ }
51
+ const w = window as any;
52
+ w.React = React;
53
+ w.MerchiProductFormSdk = MerchiProductFormSdkExternals;
54
+ loadFormBundle(bundle)
55
+ .then((Component) => {
56
+ if (active) {
57
+ setLoaded(() => Component);
58
+ setLoading(false);
59
+ }
60
+ })
61
+ .catch((e) => {
62
+ console.error('[merchi_cart] custom form load failed', e);
63
+ if (active) {
64
+ setFailed(true);
65
+ setLoading(false);
66
+ }
67
+ });
68
+ return () => {
69
+ active = false;
70
+ };
71
+ }, [product, bundleKey]);
72
+
73
+ const props = useMemo(() => {
74
+ try {
75
+ const runtime = MerchiProductFormSdkExternals.createProductFormRuntime({
76
+ apiUrl: apiUrl || '',
77
+ product,
78
+ onAddToCart: (job: any) => onSaveRef.current?.(job),
79
+ });
80
+ return {
81
+ product,
82
+ pricing: runtime.pricing,
83
+ actions: runtime.actions,
84
+ helpers: runtime.helpers,
85
+ };
86
+ } catch (e) {
87
+ console.error('[merchi_cart] custom form props build failed', e);
88
+ return null;
89
+ }
90
+ }, [product, apiUrl]);
91
+
92
+ const isActive = !failed && !loading && !!Loaded && !!props;
93
+ useEffect(() => {
94
+ onActiveChangeRef.current?.(isActive);
95
+ return () => onActiveChangeRef.current?.(false);
96
+ }, [isActive]);
97
+
98
+ if (loading) return <LoadingTemplateSm />;
99
+ if (failed || !Loaded || !props) return <>{fallback}</>;
100
+
101
+ const Shell = MerchiProductFormSdkExternals.ProductFormShell;
102
+
103
+ return (
104
+ <>
105
+ {Shell ? (
106
+ <Shell
107
+ product={props.product}
108
+ pricing={props.pricing}
109
+ actions={props.actions}
110
+ helpers={props.helpers}
111
+ initialJob={initialJob}
112
+ actionLabels={{ addToCart: 'Save' }}
113
+ >
114
+ <Loaded {...props} />
115
+ </Shell>
116
+ ) : (
117
+ <Loaded {...props} />
118
+ )}
119
+ </>
120
+ );
121
+ }
122
+
123
+ export default CustomCartProductForm;
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Full merchi_sdk_product_form runtime for window.MerchiProductFormSdk.
3
+ * Keep in sync with merchi_api/common/js/product_form_gate.cjs SDK_EXPORTS.
4
+ */
5
+ import {
6
+ SDK_VERSION,
7
+ serializeJob,
8
+ nonEmptyGroups,
9
+ formatCurrency,
10
+ urlFor,
11
+ createPricing,
12
+ createProductFormRuntime,
13
+ productHasGroups,
14
+ groupFieldsOf,
15
+ independentFieldsOf,
16
+ isOptionQuantityGridProduct,
17
+ buildProductFormJob,
18
+ jobToFormState,
19
+ selectionsToVariations,
20
+ Section,
21
+ Stack,
22
+ Card,
23
+ Heading,
24
+ Text,
25
+ Divider,
26
+ Field,
27
+ GroupRows,
28
+ OptionQuantityGrid,
29
+ ProductFormProvider,
30
+ ProductFormShell,
31
+ useProductForm,
32
+ OPTION_FIELD_TYPES,
33
+ theme,
34
+ } from 'merchi_sdk_product_form';
35
+
36
+ export const MerchiProductFormSdkExternals = {
37
+ SDK_VERSION,
38
+ serializeJob,
39
+ nonEmptyGroups,
40
+ formatCurrency,
41
+ urlFor,
42
+ createPricing,
43
+ createProductFormRuntime,
44
+ productHasGroups,
45
+ groupFieldsOf,
46
+ independentFieldsOf,
47
+ isOptionQuantityGridProduct,
48
+ buildProductFormJob,
49
+ jobToFormState,
50
+ selectionsToVariations,
51
+ Section,
52
+ Stack,
53
+ Card,
54
+ Heading,
55
+ Text,
56
+ Divider,
57
+ Field,
58
+ GroupRows,
59
+ OptionQuantityGrid,
60
+ ProductFormProvider,
61
+ ProductFormShell,
62
+ useProductForm,
63
+ OPTION_FIELD_TYPES,
64
+ theme,
65
+ };
@@ -12,6 +12,8 @@ import { LoadingTemplateSm } from '../components/LoadingTemplate';
12
12
  import { useCartContext } from '../CartProvider';
13
13
  import { makeCartItem } from '../utilities/cart';
14
14
  import { getCartCookieToken } from '../utilities/cookie';
15
+ import { publishedFormBundle } from '../utilities/customForm';
16
+ import CustomCartProductForm from '../components/CustomCartProductForm';
15
17
 
16
18
  const MerchiProductForm = lazy(() => import('merchi_product_form'));
17
19
 
@@ -54,8 +56,9 @@ function PanelEditCartItem({ cart }: Props) {
54
56
  setCartItem,
55
57
  } = useCartContext();
56
58
  const [loading, setLoading] = useState(false);
59
+ const [customFormActive, setCustomFormActive] = useState(false);
57
60
  const formId = 'edit-cart-item-form';
58
-
61
+
59
62
  // This action patches the cart item
60
63
  async function actionCartItemEdit(cartItemJson: any) {
61
64
  setLoading(true);
@@ -68,7 +71,7 @@ function PanelEditCartItem({ cart }: Props) {
68
71
  variations = [],
69
72
  variationsGroups = [],
70
73
  } = cartItemJson;
71
-
74
+
72
75
  const cartItemEnt = makeCartItem({
73
76
  ...cartItem,
74
77
  id: (cartItem as any).id,
@@ -79,19 +82,19 @@ function PanelEditCartItem({ cart }: Props) {
79
82
  variations: variations.map(cleanVariation),
80
83
  variationsGroups: variationsGroups.map(cleanVariationGroups),
81
84
  }, true, cartToken);
82
-
85
+
83
86
  // Save changes to the Cart Item
84
87
  await cartItemEnt.save();
85
-
88
+
86
89
  // Refetch the Cart and all it's relationships
87
90
  await refetchCart();
88
-
91
+
89
92
  // Show success alert
90
93
  alertSuccess('Item updated.');
91
-
94
+
92
95
  // Set active Cart tab
93
96
  setActiveTabIndex(tabIdItems);
94
-
97
+
95
98
  // Clear Cart Item from state
96
99
  setCartItem({});
97
100
  } catch (e: any) {
@@ -110,37 +113,57 @@ function PanelEditCartItem({ cart }: Props) {
110
113
  };
111
114
  const showForm =
112
115
  activeTabIndex === tabIdItem && !!cartItem?.id && !!cartItem?.product?.id;
116
+ const hasPublishedCustomForm = !!publishedFormBundle(cartItem?.product);
117
+
118
+ const defaultForm = (
119
+ <Suspense fallback={<LoadingTemplateSm />}>
120
+ <MerchiProductForm
121
+ apiUrl={apiUrl}
122
+ isCartItem={true}
123
+ initJob={{...cleanCartItem}}
124
+ initProduct={cartItem.product}
125
+ onSubmit={onSubmit}
126
+ productFormId={formId}
127
+ hideRequestQuotationButton={true}
128
+ hidePaymentUpfrontButton={true}
129
+ {...productFormClassNames}
130
+ />
131
+ </Suspense>
132
+ );
113
133
 
114
134
  return (
115
135
  <CartTabPanel tabId={tabIdItem}>
116
136
  <CartBody style={{padding: '2rem'}}>
117
137
  {showForm && (
118
- <Suspense fallback={<LoadingTemplateSm />}>
119
- <MerchiProductForm
138
+ hasPublishedCustomForm ? (
139
+ <CustomCartProductForm
140
+ product={cartItem.product}
141
+ initialJob={cleanCartItem}
120
142
  apiUrl={apiUrl}
121
- isCartItem={true}
122
- initJob={{...cleanCartItem}}
123
- initProduct={cartItem.product}
124
- onSubmit={onSubmit}
125
- productFormId={formId}
126
- hideRequestQuotationButton={true}
127
- hidePaymentUpfrontButton={true}
128
- {...productFormClassNames}
143
+ onSave={onSubmit}
144
+ fallback={defaultForm}
145
+ onActiveChange={setCustomFormActive}
129
146
  />
130
- </Suspense>
147
+ ) : (
148
+ defaultForm
149
+ )
131
150
  )}
132
151
  </CartBody>
133
152
  <CartFooter>
134
153
  <ButtonBack />
135
- <Button
136
- className={classNameBtnEditCartItem}
137
- disabled={loading}
138
- form={formId}
139
- type='submit'
140
- >
141
- {loading && <FontAwesomeIcon icon={faCircleNotch} spin />}
142
- {loading ? ' Loading...' : 'Save'}
143
- </Button>
154
+ {/* Custom forms render their own Save in ProductFormShell; keep the
155
+ HTML-form Save only for the default merchi_product_form path. */}
156
+ {!customFormActive && (
157
+ <Button
158
+ className={classNameBtnEditCartItem}
159
+ disabled={loading}
160
+ form={formId}
161
+ type='submit'
162
+ >
163
+ {loading && <FontAwesomeIcon icon={faCircleNotch} spin />}
164
+ {loading ? ' Loading...' : 'Save'}
165
+ </Button>
166
+ )}
144
167
  </CartFooter>
145
168
  </CartTabPanel>
146
169
  );
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Helpers for loading a product's published custom form bundle in cart edit.
3
+ * Mirrors the product-embed loader: SRI script tag → window.MERCHI_PRODUCT_FORM_<id>.
4
+ */
5
+ import type { ComponentType } from 'react';
6
+
7
+ export interface FormBundleRef {
8
+ url: string;
9
+ integrity?: string;
10
+ globalName: string;
11
+ }
12
+
13
+ /** If the product has a published custom form with a compiled bundle, return
14
+ * the info needed to load it; otherwise null (caller falls back). */
15
+ export function publishedFormBundle(product: any): FormBundleRef | null {
16
+ const form = product?.productForm;
17
+ if (!form || form.status !== 'published') return null;
18
+ const version = form.activeVersion;
19
+ if (!version || !version.compiledBundleUrl) return null;
20
+ return {
21
+ url: version.compiledBundleUrl,
22
+ integrity: version.bundleSha || undefined,
23
+ globalName: `MERCHI_PRODUCT_FORM_${form.id}`,
24
+ };
25
+ }
26
+
27
+ /** Dynamically load a compiled form bundle and resolve its default export. */
28
+ export function loadFormBundle(
29
+ bundle: FormBundleRef,
30
+ opts: { timeoutMs?: number } = {},
31
+ ): Promise<ComponentType<any>> {
32
+ const { url, integrity, globalName } = bundle;
33
+ const timeoutMs = opts.timeoutMs || 15000;
34
+ const w = window as any;
35
+
36
+ const existing = w[globalName] && w[globalName].default;
37
+ if (existing) return Promise.resolve(existing);
38
+
39
+ return new Promise((resolve, reject) => {
40
+ let settled = false;
41
+ const finish = (fn: (arg: any) => void, arg: any) => {
42
+ if (settled) return;
43
+ settled = true;
44
+ clearTimeout(timer);
45
+ fn(arg);
46
+ };
47
+ const timer = setTimeout(
48
+ () => finish(reject, new Error(`form bundle ${globalName} timed out`)),
49
+ timeoutMs,
50
+ );
51
+
52
+ const script = document.createElement('script');
53
+ script.src = url;
54
+ if (integrity) script.integrity = integrity;
55
+ script.crossOrigin = 'anonymous';
56
+ script.async = true;
57
+ script.onload = () => {
58
+ const mod = w[globalName];
59
+ if (mod && mod.default) finish(resolve, mod.default);
60
+ else {
61
+ finish(
62
+ reject,
63
+ new Error(`form bundle ${globalName} did not register a default export`),
64
+ );
65
+ }
66
+ };
67
+ script.onerror = () =>
68
+ finish(reject, new Error(`failed to load form bundle ${globalName}`));
69
+ document.head.appendChild(script);
70
+ });
71
+ }
@@ -173,6 +173,8 @@ const productWithImagesEmbed: any = {
173
173
  groupVariationFields: {options: {linkedFile: {}}},
174
174
  images: {},
175
175
  independentVariationFields: {options: {linkedFile: {}}},
176
+ // Published custom form pointer for cart-item edit (same shape as product embed).
177
+ productForm: {activeVersion: {}},
176
178
  taxType: {},
177
179
  };
178
180