@numueg/theme-cli 0.6.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js
CHANGED
|
@@ -3828,6 +3828,14 @@ export default function ProductDetails({ settings }: SectionProps) {
|
|
|
3828
3828
|
const productName = product.name;
|
|
3829
3829
|
const productId = product.id;
|
|
3830
3830
|
|
|
3831
|
+
// Cap the quantity stepper to the selected variant's stock (the backend
|
|
3832
|
+
// enforces this too). No per-variant stock \u21D2 unbounded.
|
|
3833
|
+
const stockCap =
|
|
3834
|
+
activeVariant && typeof activeVariant.inventory_quantity === "number"
|
|
3835
|
+
? Math.max(0, activeVariant.inventory_quantity)
|
|
3836
|
+
: Infinity;
|
|
3837
|
+
const maxQty = stockCap === Infinity ? Infinity : Math.max(1, stockCap);
|
|
3838
|
+
|
|
3831
3839
|
const purchasable =
|
|
3832
3840
|
product.in_stock &&
|
|
3833
3841
|
(activeVariant?.is_in_stock ?? true) &&
|
|
@@ -3848,7 +3856,7 @@ export default function ProductDetails({ settings }: SectionProps) {
|
|
|
3848
3856
|
.join("\\n");
|
|
3849
3857
|
await updateNote((kept ? kept + "\\n" : "") + tag + pickedSize);
|
|
3850
3858
|
}
|
|
3851
|
-
await addItem(productId, activeVariant?.id, qty);
|
|
3859
|
+
await addItem(productId, activeVariant?.id, Math.min(qty, maxQty));
|
|
3852
3860
|
} finally {
|
|
3853
3861
|
setPending(false);
|
|
3854
3862
|
}
|
|
@@ -3929,11 +3937,11 @@ export default function ProductDetails({ settings }: SectionProps) {
|
|
|
3929
3937
|
<div className="flex items-center gap-3 mb-5">
|
|
3930
3938
|
<span className="text-sm font-semibold">{t("Quantity", "\u0627\u0644\u0643\u0645\u064A\u0629")}</span>
|
|
3931
3939
|
<div className="inline-flex items-center border rounded-md">
|
|
3932
|
-
<button type="button" className="px-3 py-2" onClick={() => setQty((q) => Math.max(1, q - 1))}>
|
|
3940
|
+
<button type="button" className="px-3 py-2 disabled:opacity-40" disabled={qty <= 1} onClick={() => setQty((q) => Math.max(1, q - 1))}>
|
|
3933
3941
|
\u2212
|
|
3934
3942
|
</button>
|
|
3935
|
-
<span className="px-3">{qty}</span>
|
|
3936
|
-
<button type="button" className="px-3 py-2" onClick={() => setQty((q) => q + 1)}>
|
|
3943
|
+
<span className="px-3">{Math.min(qty, maxQty)}</span>
|
|
3944
|
+
<button type="button" className="px-3 py-2 disabled:opacity-40" disabled={qty >= maxQty} onClick={() => setQty((q) => Math.min(maxQty, q + 1))}>
|
|
3937
3945
|
+
|
|
3938
3946
|
</button>
|
|
3939
3947
|
</div>
|
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState } from "react";
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
2
|
import {
|
|
3
3
|
useProductOptional,
|
|
4
4
|
useVariantSelection,
|
|
@@ -98,6 +98,11 @@ export default function ProductDetails({ id, settings }: EmpSectionProps) {
|
|
|
98
98
|
|
|
99
99
|
const images = product.images ?? [];
|
|
100
100
|
const mainImage = images[activeImg] ?? images[0];
|
|
101
|
+
// Stock to surface in the badge/button: the selected variant's when one is
|
|
102
|
+
// resolved (so "In stock" never sits next to a dead button), else product.
|
|
103
|
+
const displayInStock = activeVariant
|
|
104
|
+
? (activeVariant.is_in_stock ?? activeVariant.in_stock ?? product.in_stock)
|
|
105
|
+
: product.in_stock;
|
|
101
106
|
const purchasable =
|
|
102
107
|
product.in_stock &&
|
|
103
108
|
(activeVariant?.is_in_stock ?? true) &&
|
|
@@ -107,6 +112,20 @@ export default function ProductDetails({ id, settings }: EmpSectionProps) {
|
|
|
107
112
|
const productName = product.name;
|
|
108
113
|
const selectedVariantId = activeVariant?.id ?? pickedId;
|
|
109
114
|
|
|
115
|
+
// Cap the quantity stepper to the selected variant's available stock so the
|
|
116
|
+
// buyer can't choose more than exists (the backend enforces this too; this
|
|
117
|
+
// keeps the UI honest). No per-variant stock ⇒ unbounded.
|
|
118
|
+
const stockCap =
|
|
119
|
+
activeVariant && typeof activeVariant.inventory_quantity === "number"
|
|
120
|
+
? Math.max(0, activeVariant.inventory_quantity)
|
|
121
|
+
: Infinity;
|
|
122
|
+
const maxQty = stockCap === Infinity ? Infinity : Math.max(1, stockCap);
|
|
123
|
+
useEffect(() => {
|
|
124
|
+
setQty((q) => Math.min(q, maxQty));
|
|
125
|
+
}, [maxQty]);
|
|
126
|
+
const lowStockLeft =
|
|
127
|
+
stockCap !== Infinity && stockCap > 0 && stockCap <= 5 ? stockCap : null;
|
|
128
|
+
|
|
110
129
|
const href = `/products/${product.slug}`;
|
|
111
130
|
const shareUrl = shop?.formatUrl ? shop.formatUrl(href) : href;
|
|
112
131
|
const enc = encodeURIComponent(shareUrl);
|
|
@@ -133,7 +152,7 @@ export default function ProductDetails({ id, settings }: EmpSectionProps) {
|
|
|
133
152
|
const next = `${kept ? `${kept}\n` : ""}${tag}${pickedSize}`;
|
|
134
153
|
await updateNote(next);
|
|
135
154
|
}
|
|
136
|
-
await addItem(productId, selectedVariantId, qty);
|
|
155
|
+
await addItem(productId, selectedVariantId, Math.min(qty, maxQty));
|
|
137
156
|
openCart();
|
|
138
157
|
} finally {
|
|
139
158
|
setPending(false);
|
|
@@ -220,8 +239,8 @@ export default function ProductDetails({ id, settings }: EmpSectionProps) {
|
|
|
220
239
|
) : null}
|
|
221
240
|
{formatMoney(activePrice, currency)}
|
|
222
241
|
</p>
|
|
223
|
-
<span className={`nt-pdp__stock${
|
|
224
|
-
{
|
|
242
|
+
<span className={`nt-pdp__stock${displayInStock ? " is-in" : " is-out"}`}>
|
|
243
|
+
{displayInStock ? t("In stock", "متوفر") : t("Out of stock", "نفذ المخزون")}
|
|
225
244
|
<span className="nt-pdp__dot" aria-hidden />
|
|
226
245
|
</span>
|
|
227
246
|
</div>
|
|
@@ -341,14 +360,19 @@ export default function ProductDetails({ id, settings }: EmpSectionProps) {
|
|
|
341
360
|
<div className="nt-pdp__qtyblock">
|
|
342
361
|
<span className="nt-pdp__opt-label">{t("Quantity", "الكمية")}</span>
|
|
343
362
|
<div className="nt-qty nt-pdp__qty" aria-label={t("Quantity", "الكمية")}>
|
|
344
|
-
<button type="button" aria-label={t("Decrease", "تقليل")} onClick={() => setQty((q) => Math.max(1, q - 1))}>
|
|
363
|
+
<button type="button" aria-label={t("Decrease", "تقليل")} disabled={qty <= 1} onClick={() => setQty((q) => Math.max(1, q - 1))}>
|
|
345
364
|
−
|
|
346
365
|
</button>
|
|
347
|
-
<span>{qty}</span>
|
|
348
|
-
<button type="button" aria-label={t("Increase", "زيادة")} onClick={() => setQty((q) => q + 1)}>
|
|
366
|
+
<span>{Math.min(qty, maxQty)}</span>
|
|
367
|
+
<button type="button" aria-label={t("Increase", "زيادة")} disabled={qty >= maxQty} onClick={() => setQty((q) => Math.min(maxQty, q + 1))}>
|
|
349
368
|
+
|
|
350
369
|
</button>
|
|
351
370
|
</div>
|
|
371
|
+
{lowStockLeft != null ? (
|
|
372
|
+
<span className="nt-pdp__hint">
|
|
373
|
+
{t(`Only ${lowStockLeft} left`, `باقي ${lowStockLeft} فقط`)}
|
|
374
|
+
</span>
|
|
375
|
+
) : null}
|
|
352
376
|
</div>
|
|
353
377
|
|
|
354
378
|
{/* CTAs */}
|
|
@@ -361,7 +385,7 @@ export default function ProductDetails({ id, settings }: EmpSectionProps) {
|
|
|
361
385
|
>
|
|
362
386
|
{pending ? (
|
|
363
387
|
"..."
|
|
364
|
-
) : !
|
|
388
|
+
) : !displayInStock ? (
|
|
365
389
|
t("Sold out", "نفذ المخزون")
|
|
366
390
|
) : needsSize && !pickedSize ? (
|
|
367
391
|
t("Select a size", "اختر المقاس")
|