@sazito/checkout 0.1.0 → 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.
- package/README.md +34 -0
- package/dist/chunks/{labels-ChywPk2i.js → labels-ByA_yGKl.js} +131 -53
- package/dist/chunks/labels-ByA_yGKl.js.map +1 -0
- package/dist/chunks/{labels-BlMZkOsV.cjs → labels-DJ_RnkCQ.cjs} +132 -52
- package/dist/chunks/labels-DJ_RnkCQ.cjs.map +1 -0
- package/dist/chunks/{use-checkout-Cvp_K3UW.cjs → use-checkout-CQBSONj1.cjs} +2 -2
- package/dist/chunks/{use-checkout-Cvp_K3UW.cjs.map → use-checkout-CQBSONj1.cjs.map} +1 -1
- package/dist/chunks/{use-checkout-E0O8HsSs.js → use-checkout-gAjnCzae.js} +2 -2
- package/dist/chunks/{use-checkout-E0O8HsSs.js.map → use-checkout-gAjnCzae.js.map} +1 -1
- package/dist/core/index.cjs +3 -1
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +9 -4
- package/dist/core/index.d.ts +9 -4
- package/dist/core/index.js +1 -1
- package/dist/next/index.cjs +19 -9
- package/dist/next/index.cjs.map +1 -1
- package/dist/next/index.d.cts +1 -0
- package/dist/next/index.d.ts +1 -0
- package/dist/next/index.js +21 -11
- package/dist/next/index.js.map +1 -1
- package/dist/react/index.cjs +2 -2
- package/dist/react/index.d.cts +1 -0
- package/dist/react/index.d.ts +1 -0
- package/dist/react/index.js +2 -2
- package/dist/styles.css +123 -8
- package/package.json +12 -6
- package/dist/chunks/labels-BlMZkOsV.cjs.map +0 -1
- package/dist/chunks/labels-ChywPk2i.js.map +0 -1
package/dist/next/index.d.cts
CHANGED
package/dist/next/index.d.ts
CHANGED
package/dist/next/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
|
|
3
|
-
import { isValidElement, cloneElement, useState, useRef, useEffect, useCallback } from 'react';
|
|
4
|
-
import { u as useCheckout, b as useSazitoClient, C as CheckoutProvider } from '../chunks/use-checkout-
|
|
5
|
-
export { S as SazitoProvider } from '../chunks/use-checkout-
|
|
6
|
-
import { m as formatPercent,
|
|
3
|
+
import { isValidElement, cloneElement, useMemo, useState, useRef, useEffect, useCallback } from 'react';
|
|
4
|
+
import { u as useCheckout, b as useSazitoClient, C as CheckoutProvider } from '../chunks/use-checkout-gAjnCzae.js';
|
|
5
|
+
export { S as SazitoProvider } from '../chunks/use-checkout-gAjnCzae.js';
|
|
6
|
+
import { m as formatPercent, E as sortCartItemsNewestFirst, H as toPersianDigits, r as isDigitalServiceGroup, t as isValidIranianMobile, z as normalizeIranianPhone, A as paymentMethodInfo, l as formatNumber } from '../chunks/labels-ByA_yGKl.js';
|
|
7
7
|
import '@sazito/client-sdk';
|
|
8
8
|
|
|
9
9
|
/**
|
|
@@ -139,6 +139,7 @@ function ClipboardIcon() {
|
|
|
139
139
|
|
|
140
140
|
function OrderSummary() {
|
|
141
141
|
const { state, summary, money, t } = useCheckout();
|
|
142
|
+
const updating = state.flags.updatingCart;
|
|
142
143
|
const percentSign = state.locale === 'fa' ? '٪' : '%';
|
|
143
144
|
const lineLabels = {
|
|
144
145
|
subtotal: t.subtotal,
|
|
@@ -147,14 +148,14 @@ function OrderSummary() {
|
|
|
147
148
|
credit: t.credit,
|
|
148
149
|
vat: t.vat
|
|
149
150
|
};
|
|
150
|
-
return (jsxs("aside", { className:
|
|
151
|
+
return (jsxs("aside", { className: `szc-summary${updating ? ' szc-summary--loading' : ''}`, "aria-busy": updating, children: [jsxs("div", { className: "szc-summary__total-head", children: [jsx("span", { className: "szc-summary__total-label", children: t.totalAmount }), jsx("span", { className: "szc-summary__total-value", children: money(summary.total) })] }), jsx("dl", { className: "szc-summary__lines", children: summary.lines.map((line) => (jsxs("div", { className: `szc-summary__line${line.key === 'discount' ? ' szc-summary__line--discount' : ''}`, children: [jsxs("dt", { children: [lineLabels[line.key], (line.percent ?? 0) > 0
|
|
151
152
|
? ` (${formatPercent(line.percent, state.locale)}${percentSign})`
|
|
152
|
-
: ''] }), jsx("dd", { className: line.negative ? 'szc-summary__line--neg' : undefined, children: line.free ? (jsx("span", { className: "szc-summary__free", children: t.free })) : (`${line.negative ? '−' : ''}${money(line.amount)}`) })] }, line.key))) }), jsxs("div", { className: "szc-summary__grand", children: [jsx("span", { children: t.total }), jsx("span", { children: money(summary.total) })] })] }));
|
|
153
|
+
: ''] }), jsx("dd", { className: line.negative ? 'szc-summary__line--neg' : undefined, children: line.free ? (jsx("span", { className: "szc-summary__free", children: t.free })) : (`${line.negative ? '−' : ''}${money(line.amount)}`) })] }, line.key))) }), jsxs("div", { className: "szc-summary__grand", children: [jsx("span", { children: t.total }), jsx("span", { children: money(summary.total) })] }), updating ? (jsxs("div", { className: "szc-summary__loading", role: "status", "aria-live": "polite", children: [jsx(Spinner, { className: "szc-summary__spinner" }), jsx("span", { className: "szc-visually-hidden", children: t.loading })] })) : null] }));
|
|
153
154
|
}
|
|
154
155
|
|
|
155
156
|
const PERSIAN_DIGITS = '۰۱۲۳۴۵۶۷۸۹';
|
|
156
157
|
const ARABIC_DIGITS = '٠١٢٣٤٥٦٧٨٩';
|
|
157
|
-
const QUANTITY_DEBOUNCE_MS =
|
|
158
|
+
const QUANTITY_DEBOUNCE_MS = 500;
|
|
158
159
|
function toLatinDigits(value) {
|
|
159
160
|
return value
|
|
160
161
|
.replace(/[۰-۹]/g, (digit) => String(PERSIAN_DIGITS.indexOf(digit)))
|
|
@@ -188,6 +189,7 @@ function CartStep({ continueShoppingUrl, renderEmpty, }) {
|
|
|
188
189
|
const { state, actions, money, t } = useCheckout();
|
|
189
190
|
const cart = state.cart;
|
|
190
191
|
const busy = state.flags.updatingCart;
|
|
192
|
+
const newestCartItems = useMemo(() => sortCartItemsNewestFirst(cart?.items ?? []), [cart?.items]);
|
|
191
193
|
const invoiceItemsByVariant = new Map((state.invoice?.items ?? []).map((item) => [item.productVariantId, item]));
|
|
192
194
|
if (!cart || cart.items.length === 0) {
|
|
193
195
|
if (renderEmpty) {
|
|
@@ -195,7 +197,7 @@ function CartStep({ continueShoppingUrl, renderEmpty, }) {
|
|
|
195
197
|
}
|
|
196
198
|
return (jsx("section", { className: "szc-empty", role: "status", "aria-live": "polite", children: jsxs("div", { className: "szc-empty__card", children: [jsx("span", { className: "szc-empty__badge", children: jsx(CartIcon, {}) }), jsxs("div", { className: "szc-empty__copy", children: [jsx("h3", { className: "szc-empty__title", children: t.cartEmpty }), jsx("p", { className: "szc-empty__hint", children: t.cartEmptyHint })] }), continueShoppingUrl ? (jsx(Button, { className: "szc-empty__cta", variant: "primary", onClick: () => { window.location.href = continueShoppingUrl; }, children: t.continueShopping })) : null] }) }));
|
|
197
199
|
}
|
|
198
|
-
return (jsx("section", { className: "szc-step-panel", children: jsx("ul", { className: "szc-cart-list", children:
|
|
200
|
+
return (jsx("section", { className: "szc-step-panel", children: jsx("ul", { className: "szc-cart-list", children: newestCartItems.map((item) => {
|
|
199
201
|
const max = item.product.maxOrderQuantity;
|
|
200
202
|
const min = item.product.minOrderQuantity ?? 1;
|
|
201
203
|
const invoiceItem = invoiceItemsByVariant.get(item.product.variantId);
|
|
@@ -214,6 +216,7 @@ function CartStep({ continueShoppingUrl, renderEmpty, }) {
|
|
|
214
216
|
function QuantityControl({ value, min, max, label, onChange }) {
|
|
215
217
|
const [draft, setDraft] = useState(() => toPersianDigits(String(value)));
|
|
216
218
|
const [pending, setPending] = useState(false);
|
|
219
|
+
const [updating, setUpdating] = useState(false);
|
|
217
220
|
const draftRef = useRef(draft);
|
|
218
221
|
const pendingValueRef = useRef(null);
|
|
219
222
|
const timerRef = useRef(null);
|
|
@@ -248,11 +251,13 @@ function QuantityControl({ value, min, max, label, onChange }) {
|
|
|
248
251
|
return;
|
|
249
252
|
}
|
|
250
253
|
inFlightRef.current = true;
|
|
254
|
+
setUpdating(true);
|
|
251
255
|
try {
|
|
252
256
|
await onChangeRef.current(target);
|
|
253
257
|
}
|
|
254
258
|
finally {
|
|
255
259
|
inFlightRef.current = false;
|
|
260
|
+
setUpdating(false);
|
|
256
261
|
if (pendingValueRef.current === target) {
|
|
257
262
|
pendingValueRef.current = null;
|
|
258
263
|
setPending(false);
|
|
@@ -287,7 +292,7 @@ function QuantityControl({ value, min, max, label, onChange }) {
|
|
|
287
292
|
};
|
|
288
293
|
const parsedDraft = Number(toLatinDigits(draft));
|
|
289
294
|
const displayedValue = Number.isFinite(parsedDraft) ? normalize(parsedDraft) : value;
|
|
290
|
-
return (jsxs("div", { className: `szc-qty${pending ? ' szc-qty--pending' : ''}`, "aria-busy":
|
|
295
|
+
return (jsxs("div", { className: `szc-qty${pending ? ' szc-qty--pending' : ''}`, "aria-busy": updating, children: [jsx("button", { type: "button", className: "szc-qty__btn", "aria-label": `${label} −`, disabled: updating || displayedValue <= min, onMouseDown: (event) => event.preventDefault(), onClick: () => step(-1), children: jsx(MinusIcon, {}) }), 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) => {
|
|
291
296
|
const digits = toLatinDigits(event.target.value).replace(/\D/g, '');
|
|
292
297
|
if (!digits) {
|
|
293
298
|
if (timerRef.current)
|
|
@@ -318,7 +323,7 @@ function QuantityControl({ value, min, max, label, onChange }) {
|
|
|
318
323
|
event.preventDefault();
|
|
319
324
|
step(-1);
|
|
320
325
|
}
|
|
321
|
-
} }), jsx("button", { type: "button", className: "szc-qty__btn", "aria-label": `${label} +`, disabled: max != null && displayedValue >= max, onMouseDown: (event) => event.preventDefault(), onClick: () => step(1), children: jsx(PlusIcon, {}) })] }));
|
|
326
|
+
} }), 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: jsx(PlusIcon, {}) })] }));
|
|
322
327
|
}
|
|
323
328
|
function MinusIcon() {
|
|
324
329
|
return (jsx("svg", { viewBox: "0 0 16 16", width: "14", height: "14", fill: "none", "aria-hidden": "true", children: jsx("path", { d: "M3.5 8h9", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" }) }));
|
|
@@ -365,6 +370,8 @@ function ShippingStep() {
|
|
|
365
370
|
const { addressForm, regions, shippingGroups } = state;
|
|
366
371
|
const [touched, setTouched] = useState({});
|
|
367
372
|
const needsShipping = state.invoice?.needsShipping ?? true;
|
|
373
|
+
const physicalShippingGroups = shippingGroups.filter((group) => !isDigitalServiceGroup(group));
|
|
374
|
+
const physicalItemCount = Math.max(0, (state.invoice?.items.length ?? 0) - digitalItems.length);
|
|
368
375
|
const touch = useCallback((key) => {
|
|
369
376
|
setTouched((prev) => ({ ...prev, [key]: true }));
|
|
370
377
|
}, []);
|
|
@@ -387,7 +394,7 @@ function ShippingStep() {
|
|
|
387
394
|
}, placeholder: t.selectRegion, options: regions.map((r) => ({ value: r.id, label: r.name })) }), jsx(EnhancedSelect, { id: "szc-city", label: t.city, required: true, optionalLabel: t.optional, value: addressForm.cityId ?? '', disabled: !selectedRegion, error: errors.cityId, onChange: (v) => {
|
|
388
395
|
actions.setAddressField('cityId', v ? Number(v) : null);
|
|
389
396
|
touch('cityId');
|
|
390
|
-
}, placeholder: t.selectCity, options: selectedRegion?.cities.map((c) => ({ value: c.id, label: c.name })) ?? [] }), 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') }), 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') })] }), 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') }), 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] }),
|
|
397
|
+
}, placeholder: t.selectCity, options: selectedRegion?.cities.map((c) => ({ value: c.id, label: c.name })) ?? [] }), 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') }), 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') })] }), 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') }), 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 ? (jsxs("section", { className: "szc-digital-products", "aria-labelledby": "szc-digital-title", children: [jsxs("header", { className: "szc-digital-products__head", children: [jsx("span", { className: "szc-digital-products__icon", "aria-hidden": "true", children: jsx(DigitalIcon, {}) }), jsxs("div", { className: "szc-digital-products__heading", children: [jsxs("div", { className: "szc-digital-products__title-row", children: [jsx("h2", { id: "szc-digital-title", className: "szc-digital-products__title", children: t.digitalNoShipping }), jsx("span", { className: "szc-digital-products__count", children: t.productCount(toPersianDigits(String(digitalItems.length))) })] }), jsx("p", { children: t.digitalNoShippingHint })] })] }), jsx("div", { className: "szc-digital-products__list", children: digitalItems.map((item) => (jsxs("article", { className: "szc-digital-product", children: [jsxs("div", { className: "szc-digital-product__media", children: [item.image?.url ? (jsx("img", { src: item.image.url, alt: item.image.alt || item.name })) : (jsx(ProductPlaceholder, {})), jsx("span", { className: "szc-ship-product__quantity", children: toPersianDigits(String(item.quantity)) })] }), jsx("strong", { className: "szc-digital-product__name", children: item.name })] }, item.id))) })] })) : null, physicalShippingGroups.length > 0 ? (jsxs("section", { className: "szc-shipping-methods", "aria-labelledby": "szc-methods-title", children: [jsx(ShippingSectionHeader, { id: "szc-methods-title", title: t.shippingMethods, icon: "shipping" }), jsx("div", { className: "szc-ship-groups", children: physicalShippingGroups.map((group) => (jsx(ShippingGroupCard, { group: group }, group.key))) })] })) : needsShipping && physicalItemCount > 0 ? (jsxs("div", { className: "szc-shipping-pending", role: "note", children: [jsx("span", { className: "szc-shipping-pending__icon", "aria-hidden": "true", children: jsx(TruckIcon, {}) }), jsxs("div", { className: "szc-shipping-pending__copy", children: [jsx("strong", { children: t.shippingMethods }), jsx("span", { children: t.shippingMethodsHint })] })] })) : null] }));
|
|
391
398
|
}
|
|
392
399
|
function ShippingSectionHeader({ id, title, icon }) {
|
|
393
400
|
return (jsxs("header", { className: "szc-shipping-section__head", children: [jsx("span", { className: "szc-shipping-section__icon", "aria-hidden": "true", children: icon === 'contact' ? jsx(ContactIcon, {}) : icon === 'address' ? jsx(AddressIcon, {}) : jsx(TruckIcon, {}) }), jsx("h2", { id: id, className: "szc-shipping-section__title", children: title })] }));
|
|
@@ -398,6 +405,9 @@ function ContactIcon() {
|
|
|
398
405
|
function AddressIcon() {
|
|
399
406
|
return (jsxs("svg", { viewBox: "0 0 24 24", width: "20", height: "20", fill: "none", "aria-hidden": "true", children: [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" }), jsx("circle", { cx: "12", cy: "10", r: "2.25", stroke: "currentColor", strokeWidth: "1.7" })] }));
|
|
400
407
|
}
|
|
408
|
+
function DigitalIcon() {
|
|
409
|
+
return (jsxs("svg", { viewBox: "0 0 24 24", width: "22", height: "22", fill: "none", "aria-hidden": "true", children: [jsx("path", { d: "M12 3v11m0 0 4-4m-4 4-4-4", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round" }), 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" })] }));
|
|
410
|
+
}
|
|
401
411
|
function EnhancedSelect({ id, label, value, placeholder, options, required, optionalLabel, disabled, error, onChange }) {
|
|
402
412
|
return (jsxs("label", { className: "szc-field", htmlFor: id, children: [jsx(FieldLabel, { label: label, required: required, optionalLabel: optionalLabel }), jsxs("div", { className: "szc-select-wrap", children: [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: [jsx("option", { value: "", children: placeholder }), options.map((opt) => (jsx("option", { value: opt.value, children: opt.label }, opt.value)))] }), jsx("span", { className: "szc-select-chevron", "aria-hidden": "true", children: jsx(ChevronIcon, {}) })] }), error ? jsx("span", { className: "szc-field__error", children: error }) : null] }));
|
|
403
413
|
}
|