@sazito/checkout 0.1.1 → 0.1.2

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.
@@ -3,8 +3,8 @@
3
3
 
4
4
  var jsxRuntime = require('react/jsx-runtime');
5
5
  var react = require('react');
6
- var useCheckout = require('../chunks/use-checkout-CKFla5or.cjs');
7
- var labels = require('../chunks/labels-CWfNSAYm.cjs');
6
+ var useCheckout = require('../chunks/use-checkout-CQBSONj1.cjs');
7
+ var labels = require('../chunks/labels-DJ_RnkCQ.cjs');
8
8
  require('@sazito/client-sdk');
9
9
 
10
10
  /**
@@ -140,6 +140,7 @@ function ClipboardIcon() {
140
140
 
141
141
  function OrderSummary() {
142
142
  const { state, summary, money, t } = useCheckout.useCheckout();
143
+ const updating = state.flags.updatingCart;
143
144
  const percentSign = state.locale === 'fa' ? '٪' : '%';
144
145
  const lineLabels = {
145
146
  subtotal: t.subtotal,
@@ -148,14 +149,14 @@ function OrderSummary() {
148
149
  credit: t.credit,
149
150
  vat: t.vat
150
151
  };
151
- return (jsxRuntime.jsxs("aside", { className: "szc-summary", children: [jsxRuntime.jsxs("div", { className: "szc-summary__total-head", children: [jsxRuntime.jsx("span", { className: "szc-summary__total-label", children: t.totalAmount }), jsxRuntime.jsx("span", { className: "szc-summary__total-value", children: money(summary.total) })] }), jsxRuntime.jsx("dl", { className: "szc-summary__lines", children: summary.lines.map((line) => (jsxRuntime.jsxs("div", { className: `szc-summary__line${line.key === 'discount' ? ' szc-summary__line--discount' : ''}`, children: [jsxRuntime.jsxs("dt", { children: [lineLabels[line.key], (line.percent ?? 0) > 0
152
+ return (jsxRuntime.jsxs("aside", { className: `szc-summary${updating ? ' szc-summary--loading' : ''}`, "aria-busy": updating, children: [jsxRuntime.jsxs("div", { className: "szc-summary__total-head", children: [jsxRuntime.jsx("span", { className: "szc-summary__total-label", children: t.totalAmount }), jsxRuntime.jsx("span", { className: "szc-summary__total-value", children: money(summary.total) })] }), jsxRuntime.jsx("dl", { className: "szc-summary__lines", children: summary.lines.map((line) => (jsxRuntime.jsxs("div", { className: `szc-summary__line${line.key === 'discount' ? ' szc-summary__line--discount' : ''}`, children: [jsxRuntime.jsxs("dt", { children: [lineLabels[line.key], (line.percent ?? 0) > 0
152
153
  ? ` (${labels.formatPercent(line.percent, state.locale)}${percentSign})`
153
- : ''] }), jsxRuntime.jsx("dd", { className: line.negative ? 'szc-summary__line--neg' : undefined, children: line.free ? (jsxRuntime.jsx("span", { className: "szc-summary__free", children: t.free })) : (`${line.negative ? '−' : ''}${money(line.amount)}`) })] }, line.key))) }), jsxRuntime.jsxs("div", { className: "szc-summary__grand", children: [jsxRuntime.jsx("span", { children: t.total }), jsxRuntime.jsx("span", { children: money(summary.total) })] })] }));
154
+ : ''] }), jsxRuntime.jsx("dd", { className: line.negative ? 'szc-summary__line--neg' : undefined, children: line.free ? (jsxRuntime.jsx("span", { className: "szc-summary__free", children: t.free })) : (`${line.negative ? '−' : ''}${money(line.amount)}`) })] }, line.key))) }), jsxRuntime.jsxs("div", { className: "szc-summary__grand", children: [jsxRuntime.jsx("span", { children: t.total }), jsxRuntime.jsx("span", { children: money(summary.total) })] }), updating ? (jsxRuntime.jsxs("div", { className: "szc-summary__loading", role: "status", "aria-live": "polite", children: [jsxRuntime.jsx(Spinner, { className: "szc-summary__spinner" }), jsxRuntime.jsx("span", { className: "szc-visually-hidden", children: t.loading })] })) : null] }));
154
155
  }
155
156
 
156
157
  const PERSIAN_DIGITS = '۰۱۲۳۴۵۶۷۸۹';
157
158
  const ARABIC_DIGITS = '٠١٢٣٤٥٦٧٨٩';
158
- const QUANTITY_DEBOUNCE_MS = 600;
159
+ const QUANTITY_DEBOUNCE_MS = 500;
159
160
  function toLatinDigits(value) {
160
161
  return value
161
162
  .replace(/[۰-۹]/g, (digit) => String(PERSIAN_DIGITS.indexOf(digit)))
@@ -189,6 +190,7 @@ function CartStep({ continueShoppingUrl, renderEmpty, }) {
189
190
  const { state, actions, money, t } = useCheckout.useCheckout();
190
191
  const cart = state.cart;
191
192
  const busy = state.flags.updatingCart;
193
+ const newestCartItems = react.useMemo(() => labels.sortCartItemsNewestFirst(cart?.items ?? []), [cart?.items]);
192
194
  const invoiceItemsByVariant = new Map((state.invoice?.items ?? []).map((item) => [item.productVariantId, item]));
193
195
  if (!cart || cart.items.length === 0) {
194
196
  if (renderEmpty) {
@@ -196,7 +198,7 @@ function CartStep({ continueShoppingUrl, renderEmpty, }) {
196
198
  }
197
199
  return (jsxRuntime.jsx("section", { className: "szc-empty", role: "status", "aria-live": "polite", children: jsxRuntime.jsxs("div", { className: "szc-empty__card", children: [jsxRuntime.jsx("span", { className: "szc-empty__badge", children: jsxRuntime.jsx(CartIcon, {}) }), jsxRuntime.jsxs("div", { className: "szc-empty__copy", children: [jsxRuntime.jsx("h3", { className: "szc-empty__title", children: t.cartEmpty }), jsxRuntime.jsx("p", { className: "szc-empty__hint", children: t.cartEmptyHint })] }), continueShoppingUrl ? (jsxRuntime.jsx(Button, { className: "szc-empty__cta", variant: "primary", onClick: () => { window.location.href = continueShoppingUrl; }, children: t.continueShopping })) : null] }) }));
198
200
  }
199
- return (jsxRuntime.jsx("section", { className: "szc-step-panel", children: jsxRuntime.jsx("ul", { className: "szc-cart-list", children: cart.items.map((item) => {
201
+ return (jsxRuntime.jsx("section", { className: "szc-step-panel", children: jsxRuntime.jsx("ul", { className: "szc-cart-list", children: newestCartItems.map((item) => {
200
202
  const max = item.product.maxOrderQuantity;
201
203
  const min = item.product.minOrderQuantity ?? 1;
202
204
  const invoiceItem = invoiceItemsByVariant.get(item.product.variantId);
@@ -215,6 +217,7 @@ function CartStep({ continueShoppingUrl, renderEmpty, }) {
215
217
  function QuantityControl({ value, min, max, label, onChange }) {
216
218
  const [draft, setDraft] = react.useState(() => labels.toPersianDigits(String(value)));
217
219
  const [pending, setPending] = react.useState(false);
220
+ const [updating, setUpdating] = react.useState(false);
218
221
  const draftRef = react.useRef(draft);
219
222
  const pendingValueRef = react.useRef(null);
220
223
  const timerRef = react.useRef(null);
@@ -249,11 +252,13 @@ function QuantityControl({ value, min, max, label, onChange }) {
249
252
  return;
250
253
  }
251
254
  inFlightRef.current = true;
255
+ setUpdating(true);
252
256
  try {
253
257
  await onChangeRef.current(target);
254
258
  }
255
259
  finally {
256
260
  inFlightRef.current = false;
261
+ setUpdating(false);
257
262
  if (pendingValueRef.current === target) {
258
263
  pendingValueRef.current = null;
259
264
  setPending(false);
@@ -288,7 +293,7 @@ function QuantityControl({ value, min, max, label, onChange }) {
288
293
  };
289
294
  const parsedDraft = Number(toLatinDigits(draft));
290
295
  const displayedValue = Number.isFinite(parsedDraft) ? normalize(parsedDraft) : value;
291
- return (jsxRuntime.jsxs("div", { className: `szc-qty${pending ? ' szc-qty--pending' : ''}`, "aria-busy": pending, children: [jsxRuntime.jsx("button", { type: "button", className: "szc-qty__btn", "aria-label": `${label} −`, disabled: displayedValue <= min, onMouseDown: (event) => event.preventDefault(), onClick: () => step(-1), children: jsxRuntime.jsx(MinusIcon, {}) }), jsxRuntime.jsx("input", { className: "szc-qty__input", type: "text", inputMode: "numeric", pattern: "[\u06F0-\u06F9\u0660-\u06690-9]*", role: "spinbutton", value: draft, "aria-label": label, "aria-valuemin": min, "aria-valuemax": max, "aria-valuenow": displayedValue, onChange: (event) => {
296
+ return (jsxRuntime.jsxs("div", { className: `szc-qty${pending ? ' szc-qty--pending' : ''}`, "aria-busy": updating, children: [jsxRuntime.jsx("button", { type: "button", className: "szc-qty__btn", "aria-label": `${label} −`, disabled: updating || displayedValue <= min, onMouseDown: (event) => event.preventDefault(), onClick: () => step(-1), children: jsxRuntime.jsx(MinusIcon, {}) }), jsxRuntime.jsx("input", { className: "szc-qty__input", type: "text", inputMode: "numeric", pattern: "[\u06F0-\u06F9\u0660-\u06690-9]*", role: "spinbutton", value: draft, "aria-label": label, "aria-valuemin": min, "aria-valuemax": max, "aria-valuenow": displayedValue, disabled: updating, onChange: (event) => {
292
297
  const digits = toLatinDigits(event.target.value).replace(/\D/g, '');
293
298
  if (!digits) {
294
299
  if (timerRef.current)
@@ -319,7 +324,7 @@ function QuantityControl({ value, min, max, label, onChange }) {
319
324
  event.preventDefault();
320
325
  step(-1);
321
326
  }
322
- } }), jsxRuntime.jsx("button", { type: "button", className: "szc-qty__btn", "aria-label": `${label} +`, disabled: max != null && displayedValue >= max, onMouseDown: (event) => event.preventDefault(), onClick: () => step(1), children: jsxRuntime.jsx(PlusIcon, {}) })] }));
327
+ } }), jsxRuntime.jsx("button", { type: "button", className: "szc-qty__btn", "aria-label": `${label} +`, disabled: updating || (max != null && displayedValue >= max), onMouseDown: (event) => event.preventDefault(), onClick: () => step(1), children: jsxRuntime.jsx(PlusIcon, {}) })] }));
323
328
  }
324
329
  function MinusIcon() {
325
330
  return (jsxRuntime.jsx("svg", { viewBox: "0 0 16 16", width: "14", height: "14", fill: "none", "aria-hidden": "true", children: jsxRuntime.jsx("path", { d: "M3.5 8h9", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" }) }));
@@ -366,6 +371,8 @@ function ShippingStep() {
366
371
  const { addressForm, regions, shippingGroups } = state;
367
372
  const [touched, setTouched] = react.useState({});
368
373
  const needsShipping = state.invoice?.needsShipping ?? true;
374
+ const physicalShippingGroups = shippingGroups.filter((group) => !labels.isDigitalServiceGroup(group));
375
+ const physicalItemCount = Math.max(0, (state.invoice?.items.length ?? 0) - digitalItems.length);
369
376
  const touch = react.useCallback((key) => {
370
377
  setTouched((prev) => ({ ...prev, [key]: true }));
371
378
  }, []);
@@ -388,7 +395,7 @@ function ShippingStep() {
388
395
  }, placeholder: t.selectRegion, options: regions.map((r) => ({ value: r.id, label: r.name })) }), jsxRuntime.jsx(EnhancedSelect, { id: "szc-city", label: t.city, required: true, optionalLabel: t.optional, value: addressForm.cityId ?? '', disabled: !selectedRegion, error: errors.cityId, onChange: (v) => {
389
396
  actions.setAddressField('cityId', v ? Number(v) : null);
390
397
  touch('cityId');
391
- }, placeholder: t.selectCity, options: selectedRegion?.cities.map((c) => ({ value: c.id, label: c.name })) ?? [] }), jsxRuntime.jsx(Field, { id: "szc-postal", label: t.postalCode, required: state.postalCodeMandatory, optionalLabel: t.optional, className: "szc-input--ltr", dir: "ltr", inputMode: "numeric", value: addressForm.postalCode, error: errors.postalCode, onChange: (e) => actions.setAddressField('postalCode', e.target.value), onBlur: () => touch('postalCode') }), jsxRuntime.jsx(Field, { id: "szc-phone", label: t.phoneNumber, optionalLabel: t.optional, className: "szc-input--ltr", dir: "ltr", inputMode: "tel", value: addressForm.phoneNumber, onChange: (e) => actions.setAddressField('phoneNumber', e.target.value), onBlur: () => handleBlurPhone('phoneNumber') })] }), jsxRuntime.jsx(Field, { id: "szc-address", label: t.addressLine, required: true, optionalLabel: t.optional, value: addressForm.address, error: errors.address, onChange: (e) => actions.setAddressField('address', e.target.value), onBlur: () => touch('address') }), jsxRuntime.jsx(Field, { id: "szc-description", label: t.description, optionalLabel: t.optional, value: addressForm.description, onChange: (e) => actions.setAddressField('description', e.target.value), onBlur: () => touch('description') })] })) : null] }), shippingGroups.length > 0 ? (jsxRuntime.jsxs("section", { className: "szc-shipping-methods", "aria-labelledby": "szc-methods-title", children: [jsxRuntime.jsx(ShippingSectionHeader, { id: "szc-methods-title", title: t.shippingMethods, icon: "shipping" }), jsxRuntime.jsx("div", { className: "szc-ship-groups", children: shippingGroups.map((group) => (jsxRuntime.jsx(ShippingGroupCard, { group: group }, group.key))) })] })) : needsShipping ? (jsxRuntime.jsxs("div", { className: "szc-shipping-pending", role: "note", children: [jsxRuntime.jsx("span", { className: "szc-shipping-pending__icon", "aria-hidden": "true", children: jsxRuntime.jsx(TruckIcon, {}) }), jsxRuntime.jsxs("div", { className: "szc-shipping-pending__copy", children: [jsxRuntime.jsx("strong", { children: t.shippingMethods }), jsxRuntime.jsx("span", { children: t.shippingMethodsHint })] })] })) : null, digitalItems.length > 0 ? (jsxRuntime.jsxs("div", { className: "szc-digital-note", children: [jsxRuntime.jsx("span", { className: "szc-digital-note__icon", children: "\u2B07\uFE0E" }), jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("strong", { children: t.digitalNoShipping }), jsxRuntime.jsx("ul", { children: digitalItems.map((item) => (jsxRuntime.jsx("li", { children: item.name }, item.id))) })] })] })) : null] }));
398
+ }, placeholder: t.selectCity, options: selectedRegion?.cities.map((c) => ({ value: c.id, label: c.name })) ?? [] }), jsxRuntime.jsx(Field, { id: "szc-postal", label: t.postalCode, required: state.postalCodeMandatory, optionalLabel: t.optional, className: "szc-input--ltr", dir: "ltr", inputMode: "numeric", value: addressForm.postalCode, error: errors.postalCode, onChange: (e) => actions.setAddressField('postalCode', e.target.value), onBlur: () => touch('postalCode') }), jsxRuntime.jsx(Field, { id: "szc-phone", label: t.phoneNumber, optionalLabel: t.optional, className: "szc-input--ltr", dir: "ltr", inputMode: "tel", value: addressForm.phoneNumber, onChange: (e) => actions.setAddressField('phoneNumber', e.target.value), onBlur: () => handleBlurPhone('phoneNumber') })] }), jsxRuntime.jsx(Field, { id: "szc-address", label: t.addressLine, required: true, optionalLabel: t.optional, value: addressForm.address, error: errors.address, onChange: (e) => actions.setAddressField('address', e.target.value), onBlur: () => touch('address') }), jsxRuntime.jsx(Field, { id: "szc-description", label: t.description, optionalLabel: t.optional, value: addressForm.description, onChange: (e) => actions.setAddressField('description', e.target.value), onBlur: () => touch('description') })] })) : null] }), digitalItems.length > 0 ? (jsxRuntime.jsxs("section", { className: "szc-digital-products", "aria-labelledby": "szc-digital-title", children: [jsxRuntime.jsxs("header", { className: "szc-digital-products__head", children: [jsxRuntime.jsx("span", { className: "szc-digital-products__icon", "aria-hidden": "true", children: jsxRuntime.jsx(DigitalIcon, {}) }), jsxRuntime.jsxs("div", { className: "szc-digital-products__heading", children: [jsxRuntime.jsxs("div", { className: "szc-digital-products__title-row", children: [jsxRuntime.jsx("h2", { id: "szc-digital-title", className: "szc-digital-products__title", children: t.digitalNoShipping }), jsxRuntime.jsx("span", { className: "szc-digital-products__count", children: t.productCount(labels.toPersianDigits(String(digitalItems.length))) })] }), jsxRuntime.jsx("p", { children: t.digitalNoShippingHint })] })] }), jsxRuntime.jsx("div", { className: "szc-digital-products__list", children: digitalItems.map((item) => (jsxRuntime.jsxs("article", { className: "szc-digital-product", children: [jsxRuntime.jsxs("div", { className: "szc-digital-product__media", children: [item.image?.url ? (jsxRuntime.jsx("img", { src: item.image.url, alt: item.image.alt || item.name })) : (jsxRuntime.jsx(ProductPlaceholder, {})), jsxRuntime.jsx("span", { className: "szc-ship-product__quantity", children: labels.toPersianDigits(String(item.quantity)) })] }), jsxRuntime.jsx("strong", { className: "szc-digital-product__name", children: item.name })] }, item.id))) })] })) : null, physicalShippingGroups.length > 0 ? (jsxRuntime.jsxs("section", { className: "szc-shipping-methods", "aria-labelledby": "szc-methods-title", children: [jsxRuntime.jsx(ShippingSectionHeader, { id: "szc-methods-title", title: t.shippingMethods, icon: "shipping" }), jsxRuntime.jsx("div", { className: "szc-ship-groups", children: physicalShippingGroups.map((group) => (jsxRuntime.jsx(ShippingGroupCard, { group: group }, group.key))) })] })) : needsShipping && physicalItemCount > 0 ? (jsxRuntime.jsxs("div", { className: "szc-shipping-pending", role: "note", children: [jsxRuntime.jsx("span", { className: "szc-shipping-pending__icon", "aria-hidden": "true", children: jsxRuntime.jsx(TruckIcon, {}) }), jsxRuntime.jsxs("div", { className: "szc-shipping-pending__copy", children: [jsxRuntime.jsx("strong", { children: t.shippingMethods }), jsxRuntime.jsx("span", { children: t.shippingMethodsHint })] })] })) : null] }));
392
399
  }
393
400
  function ShippingSectionHeader({ id, title, icon }) {
394
401
  return (jsxRuntime.jsxs("header", { className: "szc-shipping-section__head", children: [jsxRuntime.jsx("span", { className: "szc-shipping-section__icon", "aria-hidden": "true", children: icon === 'contact' ? jsxRuntime.jsx(ContactIcon, {}) : icon === 'address' ? jsxRuntime.jsx(AddressIcon, {}) : jsxRuntime.jsx(TruckIcon, {}) }), jsxRuntime.jsx("h2", { id: id, className: "szc-shipping-section__title", children: title })] }));
@@ -399,6 +406,9 @@ function ContactIcon() {
399
406
  function AddressIcon() {
400
407
  return (jsxRuntime.jsxs("svg", { viewBox: "0 0 24 24", width: "20", height: "20", fill: "none", "aria-hidden": "true", children: [jsxRuntime.jsx("path", { d: "M19 10c0 4.7-7 10-7 10S5 14.7 5 10a7 7 0 1 1 14 0Z", stroke: "currentColor", strokeWidth: "1.7", strokeLinejoin: "round" }), jsxRuntime.jsx("circle", { cx: "12", cy: "10", r: "2.25", stroke: "currentColor", strokeWidth: "1.7" })] }));
401
408
  }
409
+ function DigitalIcon() {
410
+ return (jsxRuntime.jsxs("svg", { viewBox: "0 0 24 24", width: "22", height: "22", fill: "none", "aria-hidden": "true", children: [jsxRuntime.jsx("path", { d: "M12 3v11m0 0 4-4m-4 4-4-4", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round" }), jsxRuntime.jsx("path", { d: "M5 16.5v1.75A1.75 1.75 0 0 0 6.75 20h10.5A1.75 1.75 0 0 0 19 18.25V16.5", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round" })] }));
411
+ }
402
412
  function EnhancedSelect({ id, label, value, placeholder, options, required, optionalLabel, disabled, error, onChange }) {
403
413
  return (jsxRuntime.jsxs("label", { className: "szc-field", htmlFor: id, children: [jsxRuntime.jsx(FieldLabel, { label: label, required: required, optionalLabel: optionalLabel }), jsxRuntime.jsxs("div", { className: "szc-select-wrap", children: [jsxRuntime.jsxs("select", { id: id, className: `szc-input szc-select-enhanced${error ? ' szc-input--error' : ''}`, value: value, required: required, disabled: disabled, onChange: (e) => onChange(e.target.value), children: [jsxRuntime.jsx("option", { value: "", children: placeholder }), options.map((opt) => (jsxRuntime.jsx("option", { value: opt.value, children: opt.label }, opt.value)))] }), jsxRuntime.jsx("span", { className: "szc-select-chevron", "aria-hidden": "true", children: jsxRuntime.jsx(ChevronIcon, {}) })] }), error ? jsxRuntime.jsx("span", { className: "szc-field__error", children: error }) : null] }));
404
414
  }