@mohasinac/appkit 4.41.1 → 4.41.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.
@@ -17,6 +17,16 @@ import { apiClient } from "../../../http";
17
17
  const PAGE_SIZE = 50;
18
18
  const FILTER_KEYS = ["active"];
19
19
  const DEFAULT_SORT = "order";
20
+ function extractCarouselRows(response) {
21
+ if (Array.isArray(response))
22
+ return response;
23
+ if (!response || typeof response !== "object")
24
+ return undefined;
25
+ const obj = response;
26
+ if (Array.isArray(obj.data))
27
+ return obj.data;
28
+ return Array.isArray(obj.items) ? obj.items : undefined;
29
+ }
20
30
  export function AdminCarouselView({ children, onBulkDelete, ...props }) {
21
31
  const hasChildren = React.Children.count(children) > 0;
22
32
  const [draggingId, setDraggingId] = useState(null);
@@ -30,7 +40,7 @@ export function AdminCarouselView({ children, onBulkDelete, ...props }) {
30
40
  queryKey: ["admin", "carousel", "listing"],
31
41
  endpoint: `${HOMEPAGE_ENDPOINTS.CAROUSEL}?includeInactive=true`,
32
42
  mapRows: (response) => {
33
- const sourceItems = Array.isArray(response.data) ? response.data : response.items;
43
+ const sourceItems = extractCarouselRows(response);
34
44
  return toRecordArray(sourceItems)
35
45
  .sort((a, b) => (a.order ?? 0) - (b.order ?? 0))
36
46
  .map((item, index) => {
@@ -50,7 +60,13 @@ export function AdminCarouselView({ children, onBulkDelete, ...props }) {
50
60
  };
51
61
  });
52
62
  },
53
- getTotal: (response, mappedRows) => typeof response.total === "number" ? response.total : mappedRows.length,
63
+ getTotal: (response, mappedRows) => {
64
+ if (Array.isArray(response) || !response || typeof response !== "object") {
65
+ return mappedRows.length;
66
+ }
67
+ const total = response.total;
68
+ return typeof total === "number" ? total : mappedRows.length;
69
+ },
54
70
  buildFilters: (filterState) => {
55
71
  const activeRaw = filterState.active;
56
72
  if (activeRaw === "true")
@@ -9,6 +9,24 @@ import { toRecordArray, toRelativeDate, toStringValue, } from "../hooks/useAdmin
9
9
  import { DataListingView } from "./DataListingView";
10
10
  import { AdminCategoryEditorView } from "./AdminCategoryEditorView";
11
11
  import { ADMIN_BULK_ACTIONS, ROW_ACTION_META } from "../../products/constants/action-defs";
12
+ /** Rows, whichever of the three shapes the endpoint returns. */
13
+ function extractCategoryRows(response) {
14
+ if (Array.isArray(response))
15
+ return response;
16
+ if (!response || typeof response !== "object")
17
+ return undefined;
18
+ const obj = response;
19
+ if (Array.isArray(obj.data))
20
+ return obj.data;
21
+ return Array.isArray(obj.items) ? obj.items : undefined;
22
+ }
23
+ /** `total` only exists on the object shapes; a bare array has none. */
24
+ function extractCategoryTotal(response) {
25
+ if (Array.isArray(response) || !response || typeof response !== "object")
26
+ return undefined;
27
+ const total = response.total;
28
+ return typeof total === "number" ? total : undefined;
29
+ }
12
30
  const COLUMNS = [
13
31
  {
14
32
  key: "primary",
@@ -46,7 +64,7 @@ const ADMIN_CATEGORIES_CONFIG = {
46
64
  ],
47
65
  columns: COLUMNS,
48
66
  mapRows: (response) => {
49
- const sourceItems = Array.isArray(response.data) ? response.data : response.items;
67
+ const sourceItems = extractCategoryRows(response);
50
68
  return toRecordArray(sourceItems).map((item, index) => ({
51
69
  id: toStringValue(item.id, `category-${index}`),
52
70
  primary: toStringValue(item.name, "Untitled category"),
@@ -59,7 +77,7 @@ const ADMIN_CATEGORIES_CONFIG = {
59
77
  updatedAt: toRelativeDate(item.updatedAt ?? item.createdAt),
60
78
  }));
61
79
  },
62
- getTotal: (response, mappedRows) => typeof response.total === "number" ? response.total : mappedRows.length,
80
+ getTotal: (response, mappedRows) => extractCategoryTotal(response) ?? mappedRows.length,
63
81
  buildFilters: (state) => {
64
82
  const parts = [];
65
83
  if (state.isActive)
@@ -38,7 +38,7 @@ function FeaturedBundleCard({ bundle, showSavingsBadge = true, onBuyNow, }) {
38
38
  const cover = bundle.display?.coverImage;
39
39
  const href = String(ROUTES.PUBLIC.BUNDLE_DETAIL?.(bundle.slug) ?? "#");
40
40
  const discount = computeBundleDiscount(bundle.bundlePrice, bundle.bundleOriginalTotal);
41
- return (_jsxs(Stack, { gap: "none", surface: "default", rounded: "xl", border: "default", children: [_jsxs(Link, { href: href, className: "group block p-[var(--appkit-space-3)] hover:no-underline", children: [_jsxs(Div, { className: `relative mb-2 aspect-square ${__O.hidden}`, rounded: "lg", surface: "subtle", children: [cover ? (_jsx(MediaImage, { src: cover, alt: bundle.name, size: "card", className: "transition-transform group-hover:scale-105" })) : (_jsx(Row, { textSize: "3xl", className: "h-full w-full", align: "center", justify: "center", children: PLACEHOLDER_EMOJI })), showSavingsBadge && discount && (_jsx(Badge, { variant: "success", className: "absolute left-2 top-2", children: BUNDLE_COPY.detail.discountBadge(discount.percent) }))] }), _jsx(Text, { className: "line-clamp-2", size: "sm", weight: "semibold", children: bundle.name }), _jsxs(Row, { gap: "sm", align: "center", className: "mt-1", wrap: true, children: [_jsx(Text, { size: "sm", weight: "bold", children: _jsx(GatedPrice, { label: "Sign in to see the bundle price", children: bundle.bundlePrice
41
+ return (_jsxs(Stack, { gap: "none", surface: "default", rounded: "xl", border: "default", children: [_jsxs(Link, { href: href, className: "group block p-[var(--appkit-space-3)] hover:no-underline", children: [_jsxs(Div, { className: `relative mb-2 aspect-square ${__O.hidden}`, rounded: "lg", surface: "subtle", children: [cover ? (_jsx(MediaImage, { src: cover, alt: bundle.name, size: "card", className: "transition-transform group-hover:scale-105" })) : (_jsx(Row, { textSize: "3xl", className: "h-full w-full", align: "center", justify: "center", children: PLACEHOLDER_EMOJI })), showSavingsBadge && discount && (_jsx(PricesOnly, { children: _jsx(Badge, { variant: "success", className: "absolute left-2 top-2", children: BUNDLE_COPY.detail.discountBadge(discount.percent) }) }))] }), _jsx(Text, { className: "line-clamp-2", size: "sm", weight: "semibold", children: bundle.name }), _jsxs(Row, { gap: "sm", align: "center", className: "mt-1", wrap: true, children: [_jsx(Text, { size: "sm", weight: "bold", children: _jsx(GatedPrice, { label: "Sign in to see the bundle price", children: bundle.bundlePrice
42
42
  ? formatCurrency(bundle.bundlePrice, "INR")
43
43
  : BUNDLE_COPY.featured.priceFallback }) }), discount && (_jsx(PricesOnly, { children: _jsx(Text, { size: "xs", color: "muted", className: "line-through", children: formatCurrency(discount.originalTotal, "INR") }) })), _jsxs(Text, { size: "xs", color: "muted", children: ["\u00B7 ", BUNDLE_COPY.featured.itemCount(memberCount)] }), stock !== "in_stock" && (_jsx(Badge, { variant: BUNDLE_STOCK_VARIANT[stock], children: BUNDLE_COPY.stockBadge.listVariantOutOfStock }))] })] }), onBuyNow && (_jsx(Div, { border: "subtle", className: `border-t ${__P.p3}`, padding: "t-xs", children: _jsx(BundleBuyNowCta, { bundleSlug: bundle.slug, outOfStock: stock === "out_of_stock", onBuyNow: onBuyNow, compact: true }) }))] }));
44
44
  }
@@ -10,7 +10,7 @@ const CLS_BUNDLE_PILL = "inline-flex items-center rounded-full bg-violet-600 px-
10
10
  const CLS_STOCK_OK = "bg-success-solid text-success-on-solid";
11
11
  // audit-content-alignment-ok: button label text, not page content — buttons are universally center-labeled
12
12
  const CLS_VIEW_BTN = "mt-2 w-full cursor-pointer rounded-md bg-violet-600 py-[var(--appkit-space-1-5)] text-center text-[length:var(--appkit-text-xs)] font-semibold text-white transition-colors hover:bg-violet-700 active:scale-[0.98]";
13
- import { BaseListingCard, Div, GatedPrice, Row, Span, Stack, Text, TextLink } from "../../../ui";
13
+ import { BaseListingCard, Div, GatedPrice, PricesOnly, Row, Span, Stack, Text, TextLink } from "../../../ui";
14
14
  import { MediaImage } from "../../media/MediaImage";
15
15
  import { computeBundleDiscount } from "../../../_internal/shared/features/categories/bundle-pricing";
16
16
  import { BUNDLE_COPY } from "../../../_internal/shared/features/categories/bundle-copy";
@@ -49,7 +49,7 @@ export function MarketplaceBundleCard({ bundle, className = "", variant = "grid"
49
49
  }
50
50
  router.push(String(detailHref));
51
51
  }, [detailHref, onNavigate, router]);
52
- return (_jsxs(BaseListingCard, { isSelected: isSelected, variant: variant, className: className, onMouseDown: !isSelected ? longPress.onMouseDown : undefined, onMouseUp: !isSelected ? longPress.onMouseUp : undefined, onMouseLeave: !isSelected ? longPress.onMouseLeave : undefined, onTouchStart: !isSelected ? longPress.onTouchStart : undefined, onTouchEnd: !isSelected ? longPress.onTouchEnd : undefined, onTouchCancel: !isSelected ? longPress.onTouchCancel : undefined, children: [_jsxs(BaseListingCard.Hero, { aspect: "square", variant: variant, children: [_jsx(TextLink, { href: String(detailHref), className: "absolute inset-0 block", children: showCollage ? (_jsx(Div, { layout: "grid", className: `h-full w-full gap-[0.125rem] bg-[var(--appkit-color-surface-muted)] ${collageTiles.length === 2 ? "grid-cols-2 grid-rows-1" : "grid-cols-2 grid-rows-2"}`, children: collageTiles.map((tile, i) => (_jsxs(Div, { className: "relative overflow-hidden bg-[var(--appkit-color-surface-muted)]", children: [_jsx(MediaImage, { src: tile.imageURL, alt: tile.title ?? `${bundle.name} item ${i + 1}`, size: "card" }), i === collageTiles.length - 1 && overflow > 0 && (_jsxs(Row, { textWeight: "semibold", textSize: "sm", surface: "overlay-md", className: "absolute inset-0 text-white", align: "center", justify: "center", children: ["+", overflow] }))] }, `${tile.productId}-${i}`))) })) : cover ? (_jsx(Div, { className: "absolute inset-0 bg-[var(--appkit-color-surface-muted)]", children: _jsx(MediaImage, { src: cover, alt: bundle.name, size: "card" }) })) : (_jsx(Row, { className: "absolute inset-0 bg-[var(--appkit-color-surface-muted)]", align: "center", justify: "center", children: _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "xs", children: "No image" }) })) }), discount && (_jsx(Span, { size: "xs", weight: "bold", className: "absolute left-2 top-2 bg-success-solid text-success-on-solid", rounded: "full", padding: "pill-xs", children: BUNDLE_COPY.detail.discountBadge(discount.percent) })), _jsxs(Stack, { className: "absolute right-2 top-2", align: "end", gap: "xs", children: [_jsx(Span, { size: "xs", weight: "medium", className: CLS_BUNDLE_PILL, children: mergedLabels.bundleBadge }), _jsx(Span, { layout: "inline-flex", color: "inverse", className: `${stock === "out_of_stock" ? "bg-neutral-500 " : CLS_STOCK_OK}`, rounded: "full", padding: "pill-xs", size: "xs", weight: "medium", children: stock === "out_of_stock" ? mergedLabels.outOfStockBadge : mergedLabels.inStockBadge })] }), stock === "out_of_stock" && (_jsx(Row, { surface: "overlay-xs", className: "absolute inset-0", align: "center", justify: "center", children: _jsx(Span, { color: "inverse", size: "xs", weight: "bold", className: "bg-neutral-900/80 tracking-wider", rounded: "default", padding: "pill-md", transform: "uppercase", children: mergedLabels.outOfStockBadge }) })), onSelect && (_jsx(BaseListingCard.Checkbox, { selected: isSelected, onSelect: (event) => {
52
+ return (_jsxs(BaseListingCard, { isSelected: isSelected, variant: variant, className: className, onMouseDown: !isSelected ? longPress.onMouseDown : undefined, onMouseUp: !isSelected ? longPress.onMouseUp : undefined, onMouseLeave: !isSelected ? longPress.onMouseLeave : undefined, onTouchStart: !isSelected ? longPress.onTouchStart : undefined, onTouchEnd: !isSelected ? longPress.onTouchEnd : undefined, onTouchCancel: !isSelected ? longPress.onTouchCancel : undefined, children: [_jsxs(BaseListingCard.Hero, { aspect: "square", variant: variant, children: [_jsx(TextLink, { href: String(detailHref), className: "absolute inset-0 block", children: showCollage ? (_jsx(Div, { layout: "grid", className: `h-full w-full gap-[0.125rem] bg-[var(--appkit-color-surface-muted)] ${collageTiles.length === 2 ? "grid-cols-2 grid-rows-1" : "grid-cols-2 grid-rows-2"}`, children: collageTiles.map((tile, i) => (_jsxs(Div, { className: "relative overflow-hidden bg-[var(--appkit-color-surface-muted)]", children: [_jsx(MediaImage, { src: tile.imageURL, alt: tile.title ?? `${bundle.name} item ${i + 1}`, size: "card" }), i === collageTiles.length - 1 && overflow > 0 && (_jsxs(Row, { textWeight: "semibold", textSize: "sm", surface: "overlay-md", className: "absolute inset-0 text-white", align: "center", justify: "center", children: ["+", overflow] }))] }, `${tile.productId}-${i}`))) })) : cover ? (_jsx(Div, { className: "absolute inset-0 bg-[var(--appkit-color-surface-muted)]", children: _jsx(MediaImage, { src: cover, alt: bundle.name, size: "card" }) })) : (_jsx(Row, { className: "absolute inset-0 bg-[var(--appkit-color-surface-muted)]", align: "center", justify: "center", children: _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "xs", children: "No image" }) })) }), discount && (_jsx(PricesOnly, { children: _jsx(Span, { size: "xs", weight: "bold", className: "absolute left-2 top-2 bg-success-solid text-success-on-solid", rounded: "full", padding: "pill-xs", children: BUNDLE_COPY.detail.discountBadge(discount.percent) }) })), _jsxs(Stack, { className: "absolute right-2 top-2", align: "end", gap: "xs", children: [_jsx(Span, { size: "xs", weight: "medium", className: CLS_BUNDLE_PILL, children: mergedLabels.bundleBadge }), _jsx(Span, { layout: "inline-flex", color: "inverse", className: `${stock === "out_of_stock" ? "bg-neutral-500 " : CLS_STOCK_OK}`, rounded: "full", padding: "pill-xs", size: "xs", weight: "medium", children: stock === "out_of_stock" ? mergedLabels.outOfStockBadge : mergedLabels.inStockBadge })] }), stock === "out_of_stock" && (_jsx(Row, { surface: "overlay-xs", className: "absolute inset-0", align: "center", justify: "center", children: _jsx(Span, { color: "inverse", size: "xs", weight: "bold", className: "bg-neutral-900/80 tracking-wider", rounded: "default", padding: "pill-md", transform: "uppercase", children: mergedLabels.outOfStockBadge }) })), onSelect && (_jsx(BaseListingCard.Checkbox, { selected: isSelected, onSelect: (event) => {
53
53
  event.stopPropagation();
54
54
  onSelect(bundle.id, !isSelected);
55
55
  }, className: selectable || isSelected
@@ -15,9 +15,9 @@ import { z } from "zod";
15
15
  export declare const couponFormBase: z.ZodObject<{
16
16
  code: z.ZodString;
17
17
  type: z.ZodEnum<["percentage", "fixed", "free_shipping"]>;
18
- value: z.ZodEffects<z.ZodString, string, string>;
18
+ value: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
19
19
  minPurchase: z.ZodEffects<z.ZodString, string, string>;
20
- maxDiscount: z.ZodEffects<z.ZodString, string, string>;
20
+ maxDiscount: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
21
21
  totalLimit: z.ZodEffects<z.ZodString, string, string>;
22
22
  perUserLimit: z.ZodEffects<z.ZodString, string, string>;
23
23
  startDate: z.ZodString;
@@ -26,31 +26,31 @@ export declare const couponFormBase: z.ZodObject<{
26
26
  applicableProducts: z.ZodArray<z.ZodString, "many">;
27
27
  applicableCategories: z.ZodArray<z.ZodString, "many">;
28
28
  }, "strip", z.ZodTypeAny, {
29
- value: string;
30
29
  code: string;
31
30
  type: "fixed" | "percentage" | "free_shipping";
32
31
  isActive: boolean;
33
32
  endDate: string;
34
- maxDiscount: string;
35
33
  minPurchase: string;
36
34
  applicableProducts: string[];
37
35
  applicableCategories: string[];
38
36
  totalLimit: string;
39
37
  perUserLimit: string;
40
38
  startDate: string;
39
+ value?: string | undefined;
40
+ maxDiscount?: string | undefined;
41
41
  }, {
42
- value: string;
43
42
  code: string;
44
43
  type: "fixed" | "percentage" | "free_shipping";
45
44
  isActive: boolean;
46
45
  endDate: string;
47
- maxDiscount: string;
48
46
  minPurchase: string;
49
47
  applicableProducts: string[];
50
48
  applicableCategories: string[];
51
49
  totalLimit: string;
52
50
  perUserLimit: string;
53
51
  startDate: string;
52
+ value?: string | undefined;
53
+ maxDiscount?: string | undefined;
54
54
  }>;
55
55
  /**
56
56
  * The cross-field rules both editors need. Shared for the same reason the
@@ -59,7 +59,7 @@ export declare const couponFormBase: z.ZodObject<{
59
59
  */
60
60
  export declare function refineCouponCommon(v: {
61
61
  type: string;
62
- value: string;
62
+ value?: string;
63
63
  startDate: string;
64
64
  endDate: string;
65
65
  totalLimit: string;
@@ -68,9 +68,9 @@ export declare function refineCouponCommon(v: {
68
68
  export declare const sellerCouponFormSchema: z.ZodEffects<z.ZodObject<{
69
69
  code: z.ZodString;
70
70
  type: z.ZodEnum<["percentage", "fixed", "free_shipping"]>;
71
- value: z.ZodEffects<z.ZodString, string, string>;
71
+ value: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
72
72
  minPurchase: z.ZodEffects<z.ZodString, string, string>;
73
- maxDiscount: z.ZodEffects<z.ZodString, string, string>;
73
+ maxDiscount: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
74
74
  totalLimit: z.ZodEffects<z.ZodString, string, string>;
75
75
  perUserLimit: z.ZodEffects<z.ZodString, string, string>;
76
76
  startDate: z.ZodString;
@@ -79,57 +79,57 @@ export declare const sellerCouponFormSchema: z.ZodEffects<z.ZodObject<{
79
79
  applicableProducts: z.ZodArray<z.ZodString, "many">;
80
80
  applicableCategories: z.ZodArray<z.ZodString, "many">;
81
81
  }, "strip", z.ZodTypeAny, {
82
- value: string;
83
82
  code: string;
84
83
  type: "fixed" | "percentage" | "free_shipping";
85
84
  isActive: boolean;
86
85
  endDate: string;
87
- maxDiscount: string;
88
86
  minPurchase: string;
89
87
  applicableProducts: string[];
90
88
  applicableCategories: string[];
91
89
  totalLimit: string;
92
90
  perUserLimit: string;
93
91
  startDate: string;
92
+ value?: string | undefined;
93
+ maxDiscount?: string | undefined;
94
94
  }, {
95
- value: string;
96
95
  code: string;
97
96
  type: "fixed" | "percentage" | "free_shipping";
98
97
  isActive: boolean;
99
98
  endDate: string;
100
- maxDiscount: string;
101
99
  minPurchase: string;
102
100
  applicableProducts: string[];
103
101
  applicableCategories: string[];
104
102
  totalLimit: string;
105
103
  perUserLimit: string;
106
104
  startDate: string;
105
+ value?: string | undefined;
106
+ maxDiscount?: string | undefined;
107
107
  }>, {
108
- value: string;
109
108
  code: string;
110
109
  type: "fixed" | "percentage" | "free_shipping";
111
110
  isActive: boolean;
112
111
  endDate: string;
113
- maxDiscount: string;
114
112
  minPurchase: string;
115
113
  applicableProducts: string[];
116
114
  applicableCategories: string[];
117
115
  totalLimit: string;
118
116
  perUserLimit: string;
119
117
  startDate: string;
118
+ value?: string | undefined;
119
+ maxDiscount?: string | undefined;
120
120
  }, {
121
- value: string;
122
121
  code: string;
123
122
  type: "fixed" | "percentage" | "free_shipping";
124
123
  isActive: boolean;
125
124
  endDate: string;
126
- maxDiscount: string;
127
125
  minPurchase: string;
128
126
  applicableProducts: string[];
129
127
  applicableCategories: string[];
130
128
  totalLimit: string;
131
129
  perUserLimit: string;
132
130
  startDate: string;
131
+ value?: string | undefined;
132
+ maxDiscount?: string | undefined;
133
133
  }>;
134
134
  export type SellerCouponFormValues = z.infer<typeof sellerCouponFormSchema>;
135
135
  /**
@@ -183,9 +183,9 @@ export declare function couponDraftToPayload(draft: SellerCouponFormValues): Sel
183
183
  */
184
184
  export declare const adminCouponFormSchema: z.ZodEffects<z.ZodObject<{
185
185
  code: z.ZodString;
186
- value: z.ZodEffects<z.ZodString, string, string>;
186
+ value: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
187
187
  minPurchase: z.ZodEffects<z.ZodString, string, string>;
188
- maxDiscount: z.ZodEffects<z.ZodString, string, string>;
188
+ maxDiscount: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
189
189
  totalLimit: z.ZodEffects<z.ZodString, string, string>;
190
190
  perUserLimit: z.ZodEffects<z.ZodString, string, string>;
191
191
  startDate: z.ZodString;
@@ -197,90 +197,90 @@ export declare const adminCouponFormSchema: z.ZodEffects<z.ZodObject<{
197
197
  type: z.ZodEnum<["percentage", "fixed", "free_shipping", "buy_x_get_y"]>;
198
198
  name: z.ZodString;
199
199
  description: z.ZodUnion<[z.ZodOptional<z.ZodString>, z.ZodLiteral<"">]>;
200
- buyQuantity: z.ZodEffects<z.ZodString, string, string>;
201
- getQuantity: z.ZodEffects<z.ZodString, string, string>;
200
+ buyQuantity: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
201
+ getQuantity: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
202
202
  currentUsage: z.ZodOptional<z.ZodNumber>;
203
203
  firstTimeUserOnly: z.ZodBoolean;
204
204
  appliesToAuctions: z.ZodBoolean;
205
205
  }, "strip", z.ZodTypeAny, {
206
- value: string;
207
206
  code: string;
208
207
  type: "fixed" | "percentage" | "free_shipping" | "buy_x_get_y";
209
208
  name: string;
210
209
  isActive: boolean;
211
210
  endDate: string;
212
211
  firstTimeUserOnly: boolean;
213
- maxDiscount: string;
214
212
  minPurchase: string;
215
- buyQuantity: string;
216
- getQuantity: string;
217
213
  applicableProducts: string[];
218
214
  applicableCategories: string[];
219
215
  totalLimit: string;
220
216
  perUserLimit: string;
221
217
  startDate: string;
222
218
  appliesToAuctions: boolean;
219
+ value?: string | undefined;
223
220
  description?: string | undefined;
224
221
  currentUsage?: number | undefined;
222
+ maxDiscount?: string | undefined;
223
+ buyQuantity?: string | undefined;
224
+ getQuantity?: string | undefined;
225
225
  }, {
226
- value: string;
227
226
  code: string;
228
227
  type: "fixed" | "percentage" | "free_shipping" | "buy_x_get_y";
229
228
  name: string;
230
229
  isActive: boolean;
231
230
  endDate: string;
232
231
  firstTimeUserOnly: boolean;
233
- maxDiscount: string;
234
232
  minPurchase: string;
235
- buyQuantity: string;
236
- getQuantity: string;
237
233
  applicableProducts: string[];
238
234
  applicableCategories: string[];
239
235
  totalLimit: string;
240
236
  perUserLimit: string;
241
237
  startDate: string;
242
238
  appliesToAuctions: boolean;
239
+ value?: string | undefined;
243
240
  description?: string | undefined;
244
241
  currentUsage?: number | undefined;
242
+ maxDiscount?: string | undefined;
243
+ buyQuantity?: string | undefined;
244
+ getQuantity?: string | undefined;
245
245
  }>, {
246
- value: string;
247
246
  code: string;
248
247
  type: "fixed" | "percentage" | "free_shipping" | "buy_x_get_y";
249
248
  name: string;
250
249
  isActive: boolean;
251
250
  endDate: string;
252
251
  firstTimeUserOnly: boolean;
253
- maxDiscount: string;
254
252
  minPurchase: string;
255
- buyQuantity: string;
256
- getQuantity: string;
257
253
  applicableProducts: string[];
258
254
  applicableCategories: string[];
259
255
  totalLimit: string;
260
256
  perUserLimit: string;
261
257
  startDate: string;
262
258
  appliesToAuctions: boolean;
259
+ value?: string | undefined;
263
260
  description?: string | undefined;
264
261
  currentUsage?: number | undefined;
262
+ maxDiscount?: string | undefined;
263
+ buyQuantity?: string | undefined;
264
+ getQuantity?: string | undefined;
265
265
  }, {
266
- value: string;
267
266
  code: string;
268
267
  type: "fixed" | "percentage" | "free_shipping" | "buy_x_get_y";
269
268
  name: string;
270
269
  isActive: boolean;
271
270
  endDate: string;
272
271
  firstTimeUserOnly: boolean;
273
- maxDiscount: string;
274
272
  minPurchase: string;
275
- buyQuantity: string;
276
- getQuantity: string;
277
273
  applicableProducts: string[];
278
274
  applicableCategories: string[];
279
275
  totalLimit: string;
280
276
  perUserLimit: string;
281
277
  startDate: string;
282
278
  appliesToAuctions: boolean;
279
+ value?: string | undefined;
283
280
  description?: string | undefined;
284
281
  currentUsage?: number | undefined;
282
+ maxDiscount?: string | undefined;
283
+ buyQuantity?: string | undefined;
284
+ getQuantity?: string | undefined;
285
285
  }>;
286
286
  export type AdminCouponFormValues = z.infer<typeof adminCouponFormSchema>;
@@ -70,7 +70,10 @@ export const couponFormBase = z
70
70
  * `when` carries what the editor held as JSX conditionals, so a field the
71
71
  * form hides is one the payload cannot carry — see `couponDraftToPayload`.
72
72
  */
73
- value: annotate(numericString("The discount value"), {
73
+ // Optional for the same reason as buyQuantity/getQuantity below: this field
74
+ // is `when`-gated, so visibleValues() strips it on a free-shipping coupon.
75
+ // refineCouponCommon still requires it for every other type.
76
+ value: annotate(numericString("The discount value").optional(), {
74
77
  section: "basics", quick: true, order: 3, row: "pair",
75
78
  when: (v) => v.type !== "free_shipping",
76
79
  }),
@@ -83,7 +86,9 @@ export const couponFormBase = z
83
86
  * cap typed while the type was "percentage" persisted after switching to
84
87
  * free shipping, invisibly.
85
88
  */
86
- maxDiscount: annotate(numericString("Maximum discount"), {
89
+ // `when`-gated to percentage only, so it is stripped for fixed and
90
+ // free-shipping coupons — optional, or those two types cannot be saved.
91
+ maxDiscount: annotate(numericString("Maximum discount").optional(), {
87
92
  section: "limits", order: 2, row: "pair",
88
93
  when: (v) => v.type === "percentage",
89
94
  }),
@@ -121,8 +126,9 @@ export function refineCouponCommon(v, ctx) {
121
126
  // a percentage above 100 is not a discount — it is the store paying the
122
127
  // customer. Nothing bounded this before.
123
128
  if (v.type !== "free_shipping") {
124
- const amount = Number(v.value);
125
- if (v.value.trim() === "")
129
+ const rawValue = v.value ?? "";
130
+ const amount = Number(rawValue);
131
+ if (rawValue.trim() === "")
126
132
  issue("value", "A discount value is required.");
127
133
  else if (amount <= 0)
128
134
  issue("value", "The discount must be greater than zero.");
@@ -205,11 +211,28 @@ export const adminCouponFormSchema = couponFormBase
205
211
  description: annotate(z.string().max(500).optional().or(z.literal("")), {
206
212
  section: "basics", order: 5, row: "full", kind: "textarea",
207
213
  }),
208
- buyQuantity: annotate(numericString("Buy quantity"), {
214
+ /*
215
+ * 🛑 A `when`-gated field MUST be optional, or the form cannot be submitted
216
+ * at all while the condition is false.
217
+ *
218
+ * `visibleValues()` strips every field whose `when` is false before the
219
+ * submit path parses the draft — that is its whole job, so a hidden input
220
+ * cannot smuggle a stale value into the payload. But a stripped key is
221
+ * `undefined` to Zod, and these were required strings, so editing any
222
+ * ordinary percentage coupon produced exactly two "This field is required"
223
+ * issues for the two buy-x-get-y inputs that are not on screen. The summary
224
+ * rendered them as "Coupon: This field is required" twice, naming the
225
+ * section rather than any field, and no PATCH was ever issued.
226
+ *
227
+ * Optional here is not a loosening: the superRefine below still demands a
228
+ * whole number of one or more whenever the type IS buy_x_get_y, which is
229
+ * the only time either field is real.
230
+ */
231
+ buyQuantity: annotate(numericString("Buy quantity").optional(), {
209
232
  section: "basics", order: 6, row: "pair", kind: "number",
210
233
  when: (v) => v.type === "buy_x_get_y",
211
234
  }),
212
- getQuantity: annotate(numericString("Get quantity"), {
235
+ getQuantity: annotate(numericString("Get quantity").optional(), {
213
236
  section: "basics", order: 7, row: "pair", kind: "number",
214
237
  when: (v) => v.type === "buy_x_get_y",
215
238
  }),
@@ -235,8 +258,13 @@ export const adminCouponFormSchema = couponFormBase
235
258
  // A buy-x-get-y with a zero on either side is not an offer.
236
259
  if (v.type === "buy_x_get_y") {
237
260
  for (const key of ["buyQuantity", "getQuantity"]) {
238
- const n = Number(v[key]);
239
- if (v[key].trim() === "" || !Number.isFinite(n) || n < 1) {
261
+ // `?? ""` because the field is optional now — see the note on its
262
+ // declaration. When the type IS buy_x_get_y the input is on screen, so
263
+ // an absent value here means the admin left it blank, which this
264
+ // rejects exactly as it did before.
265
+ const raw = v[key] ?? "";
266
+ const n = Number(raw);
267
+ if (raw.trim() === "" || !Number.isFinite(n) || n < 1) {
240
268
  ctx.addIssue({
241
269
  code: z.ZodIssueCode.custom,
242
270
  path: [key],
@@ -4755,9 +4755,9 @@ export declare const SCHEMAS: {
4755
4755
  readonly sellerCoupon: import("zod").ZodEffects<import("zod").ZodObject<{
4756
4756
  code: import("zod").ZodString;
4757
4757
  type: import("zod").ZodEnum<["percentage", "fixed", "free_shipping"]>;
4758
- value: import("zod").ZodEffects<import("zod").ZodString, string, string>;
4758
+ value: import("zod").ZodOptional<import("zod").ZodEffects<import("zod").ZodString, string, string>>;
4759
4759
  minPurchase: import("zod").ZodEffects<import("zod").ZodString, string, string>;
4760
- maxDiscount: import("zod").ZodEffects<import("zod").ZodString, string, string>;
4760
+ maxDiscount: import("zod").ZodOptional<import("zod").ZodEffects<import("zod").ZodString, string, string>>;
4761
4761
  totalLimit: import("zod").ZodEffects<import("zod").ZodString, string, string>;
4762
4762
  perUserLimit: import("zod").ZodEffects<import("zod").ZodString, string, string>;
4763
4763
  startDate: import("zod").ZodString;
@@ -4766,57 +4766,57 @@ export declare const SCHEMAS: {
4766
4766
  applicableProducts: import("zod").ZodArray<import("zod").ZodString, "many">;
4767
4767
  applicableCategories: import("zod").ZodArray<import("zod").ZodString, "many">;
4768
4768
  }, "strip", import("zod").ZodTypeAny, {
4769
- value: string;
4770
4769
  code: string;
4771
4770
  type: "fixed" | "percentage" | "free_shipping";
4772
4771
  isActive: boolean;
4773
4772
  endDate: string;
4774
- maxDiscount: string;
4775
4773
  minPurchase: string;
4776
4774
  applicableProducts: string[];
4777
4775
  applicableCategories: string[];
4778
4776
  totalLimit: string;
4779
4777
  perUserLimit: string;
4780
4778
  startDate: string;
4779
+ value?: string | undefined;
4780
+ maxDiscount?: string | undefined;
4781
4781
  }, {
4782
- value: string;
4783
4782
  code: string;
4784
4783
  type: "fixed" | "percentage" | "free_shipping";
4785
4784
  isActive: boolean;
4786
4785
  endDate: string;
4787
- maxDiscount: string;
4788
4786
  minPurchase: string;
4789
4787
  applicableProducts: string[];
4790
4788
  applicableCategories: string[];
4791
4789
  totalLimit: string;
4792
4790
  perUserLimit: string;
4793
4791
  startDate: string;
4792
+ value?: string | undefined;
4793
+ maxDiscount?: string | undefined;
4794
4794
  }>, {
4795
- value: string;
4796
4795
  code: string;
4797
4796
  type: "fixed" | "percentage" | "free_shipping";
4798
4797
  isActive: boolean;
4799
4798
  endDate: string;
4800
- maxDiscount: string;
4801
4799
  minPurchase: string;
4802
4800
  applicableProducts: string[];
4803
4801
  applicableCategories: string[];
4804
4802
  totalLimit: string;
4805
4803
  perUserLimit: string;
4806
4804
  startDate: string;
4805
+ value?: string | undefined;
4806
+ maxDiscount?: string | undefined;
4807
4807
  }, {
4808
- value: string;
4809
4808
  code: string;
4810
4809
  type: "fixed" | "percentage" | "free_shipping";
4811
4810
  isActive: boolean;
4812
4811
  endDate: string;
4813
- maxDiscount: string;
4814
4812
  minPurchase: string;
4815
4813
  applicableProducts: string[];
4816
4814
  applicableCategories: string[];
4817
4815
  totalLimit: string;
4818
4816
  perUserLimit: string;
4819
4817
  startDate: string;
4818
+ value?: string | undefined;
4819
+ maxDiscount?: string | undefined;
4820
4820
  }>;
4821
4821
  readonly customRole: import("zod").ZodObject<{
4822
4822
  name: import("zod").ZodString;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mohasinac/appkit",
3
- "version": "4.41.1",
3
+ "version": "4.41.3",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public"