@mohasinac/appkit 3.5.5 → 3.5.7

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.
Files changed (121) hide show
  1. package/dist/_internal/client/features/layout/DashboardLayoutClient.js +24 -3
  2. package/dist/_internal/client/features/tour/TourProvider.d.ts +11 -3
  3. package/dist/_internal/client/features/tour/TourProvider.js +93 -4
  4. package/dist/_internal/client/scaffolds/index.d.ts +0 -2
  5. package/dist/_internal/client/scaffolds/index.js +0 -1
  6. package/dist/_internal/server/features/checkout/actions.js +65 -11
  7. package/dist/_internal/server/features/orders/adapters.js +3 -0
  8. package/dist/_internal/shared/actions/action-registry.js +20 -0
  9. package/dist/_internal/shared/checkout/order-math.d.ts +21 -0
  10. package/dist/_internal/shared/checkout/order-math.js +24 -0
  11. package/dist/_internal/shared/features/orders/config.d.ts +1 -1
  12. package/dist/_internal/shared/features/orders/config.js +1 -1
  13. package/dist/_internal/shared/fees/calculator.d.ts +13 -0
  14. package/dist/_internal/shared/fees/calculator.js +8 -0
  15. package/dist/client-entry.d.ts +2 -2
  16. package/dist/client-entry.js +2 -2
  17. package/dist/client.d.ts +8 -0
  18. package/dist/client.js +8 -0
  19. package/dist/constants/api-endpoints.d.ts +9 -0
  20. package/dist/constants/api-endpoints.js +3 -0
  21. package/dist/features/account/components/UserSidebar.js +1 -1
  22. package/dist/features/admin/components/AdminBundleEditorView.d.ts +8 -1
  23. package/dist/features/admin/components/AdminBundleEditorView.js +15 -11
  24. package/dist/features/admin/components/AdminFeaturesView.js +1 -1
  25. package/dist/features/admin/components/AdminPayoutsView.js +41 -4
  26. package/dist/features/admin/components/AdminSidebar.js +1 -1
  27. package/dist/features/admin/components/AdminSiteSettingsView.js +49 -1
  28. package/dist/features/admin/components/DataTable.js +1 -1
  29. package/dist/features/admin/components/index.d.ts +0 -2
  30. package/dist/features/admin/components/index.js +0 -1
  31. package/dist/features/admin/schemas/firestore.d.ts +8 -0
  32. package/dist/features/admin/schemas/firestore.js +6 -0
  33. package/dist/features/auctions/components/MarketplaceAuctionCard.js +1 -1
  34. package/dist/features/before-after/components/BeforeAfterSlider.js +4 -0
  35. package/dist/features/blog/components/BlogFeaturedCard.js +1 -1
  36. package/dist/features/categories/components/BundleDynamicRuleEditor.js +2 -9
  37. package/dist/features/events/components/EventCard.js +1 -1
  38. package/dist/features/events/components/EventRafflesSection.d.ts +1 -1
  39. package/dist/features/events/components/EventRafflesSection.js +3 -3
  40. package/dist/features/homepage/components/BeforeAfterCard.js +5 -0
  41. package/dist/features/homepage/components/FeaturedBundlesSection.d.ts +1 -1
  42. package/dist/features/homepage/components/FeaturedBundlesSection.js +3 -1
  43. package/dist/features/homepage/components/GoogleReviewsSection.js +2 -2
  44. package/dist/features/homepage/components/HeroCarousel.js +1 -1
  45. package/dist/features/layout/NavbarLayout.js +1 -1
  46. package/dist/features/layout/TitleBarLayout.js +5 -5
  47. package/dist/features/media/MediaLightbox.js +7 -1
  48. package/dist/features/media/components/MediaSlider.js +4 -1
  49. package/dist/features/media/modals/ImageCropModal.js +1 -1
  50. package/dist/features/media/upload/ImageUpload.js +24 -12
  51. package/dist/features/messages/hooks/useConversations.d.ts +7 -1
  52. package/dist/features/messages/hooks/useConversations.js +7 -6
  53. package/dist/features/orders/actions/order-actions.d.ts +10 -0
  54. package/dist/features/orders/actions/order-actions.js +57 -4
  55. package/dist/features/orders/components/MarketplaceOrderCard.js +1 -1
  56. package/dist/features/orders/schemas/firestore.d.ts +16 -0
  57. package/dist/features/orders/types/index.d.ts +2 -0
  58. package/dist/features/pre-orders/components/MarketplacePreorderCard.js +1 -1
  59. package/dist/features/products/components/MarketplaceBundleCard.js +1 -1
  60. package/dist/features/products/components/MarketplacePrizeDrawCard.js +1 -1
  61. package/dist/features/products/components/PrizeDrawsSection.d.ts +1 -1
  62. package/dist/features/products/components/PrizeDrawsSection.js +3 -2
  63. package/dist/features/products/components/ProductForm.js +4 -9
  64. package/dist/features/products/components/ProductGrid.js +1 -1
  65. package/dist/features/products/schemas/firestore.d.ts +4 -0
  66. package/dist/features/promotions/components/CouponCard.js +1 -1
  67. package/dist/features/scams/actions/scam-actions.d.ts +12 -0
  68. package/dist/features/scams/actions/scam-actions.js +31 -0
  69. package/dist/features/scams/components/SellerTrustBadge.d.ts +16 -0
  70. package/dist/features/scams/components/SellerTrustBadge.js +21 -0
  71. package/dist/features/scams/components/index.d.ts +2 -0
  72. package/dist/features/scams/components/index.js +1 -0
  73. package/dist/features/seller/components/SellerBundlesView.d.ts +5 -5
  74. package/dist/features/seller/components/SellerBundlesView.js +81 -84
  75. package/dist/features/seller/components/SellerProductShell.d.ts +2 -0
  76. package/dist/features/seller/components/SellerProductShell.js +11 -5
  77. package/dist/features/seller/components/SellerSidebar.js +1 -1
  78. package/dist/features/shipments/components/ShipmentItemLinkModal.js +5 -5
  79. package/dist/features/stores/components/InteractiveStoreCard.js +1 -1
  80. package/dist/features/stores/components/StoreDetailLayoutView.d.ts +8 -1
  81. package/dist/features/stores/components/StoreDetailLayoutView.js +9 -3
  82. package/dist/features/stores/components/StoreHeader.d.ts +4 -1
  83. package/dist/features/stores/components/StoreHeader.js +3 -2
  84. package/dist/index.d.ts +7 -6
  85. package/dist/index.js +6 -5
  86. package/dist/jobs.d.ts +2 -0
  87. package/dist/jobs.js +6 -0
  88. package/dist/next/routing/route-map.d.ts +3 -0
  89. package/dist/next/routing/route-map.js +1 -0
  90. package/dist/react/hooks/useClickOutside.js +3 -0
  91. package/dist/security/rate-limit.d.ts +5 -0
  92. package/dist/security/rate-limit.js +2 -0
  93. package/dist/seed/categories-seed-data.js +180 -1
  94. package/dist/seed/grouped-listings-seed-data.d.ts +2 -0
  95. package/dist/seed/products-preorders-seed-data.d.ts +2 -0
  96. package/dist/seed/site-settings-seed-data.js +9 -2
  97. package/dist/server-entry.d.ts +2 -2
  98. package/dist/server-entry.js +1 -1
  99. package/dist/server.d.ts +4 -0
  100. package/dist/server.js +5 -0
  101. package/dist/styles.css +11 -4
  102. package/dist/tailwind-utilities.css +1 -1
  103. package/dist/tokens/themes/cobalt-night.d.ts +17 -0
  104. package/dist/tokens/themes/cobalt-night.js +50 -0
  105. package/dist/tokens/themes/index.d.ts +9 -1
  106. package/dist/tokens/themes/index.js +12 -1
  107. package/dist/tokens/themes/sunset.d.ts +18 -0
  108. package/dist/tokens/themes/sunset.js +51 -0
  109. package/dist/tokens/tokens.css +10 -3
  110. package/dist/ui/components/BaseListingCard.d.ts +2 -1
  111. package/dist/ui/components/BaseListingCard.js +4 -4
  112. package/dist/ui/components/Card.d.ts +1 -1
  113. package/dist/ui/components/Card.js +2 -2
  114. package/dist/ui/components/Div.js +2 -2
  115. package/dist/ui/components/Form.d.ts +1 -1
  116. package/dist/ui/components/Form.js +2 -2
  117. package/dist/ui/components/Input.js +1 -0
  118. package/dist/ui/components/ListingToolbar.js +1 -1
  119. package/dist/ui/components/QuickCreateModal.style.css +1 -1
  120. package/dist/ui/components/Slider.js +5 -1
  121. package/package.json +3 -2
@@ -2,7 +2,6 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { normalizeError } from "../../../errors/normalize";
3
3
  import { sieveFilter, sieveAnd, SIEVE_OP } from "@mohasinac/appkit";
4
4
  import { sortBy } from "@mohasinac/appkit";
5
- import Link from "next/link";
6
5
  import { Container, Div, Heading, Section, Stack, Text, } from "../../../ui";
7
6
  import { ROUTES } from "../../../next";
8
7
  import { productRepository } from "../repository/products.repository";
@@ -28,5 +27,7 @@ export async function PrizeDrawsSection({ config, }) {
28
27
  void normalizeError(_err);
29
28
  draws = [];
30
29
  }
31
- return (_jsx(Section, { padding: "y-2xl", children: _jsx(Container, { size: "xl", children: _jsxs(Stack, { gap: "md", children: [_jsxs(Stack, { gap: "xs", children: [_jsx(Heading, { level: 2, size: "2xl", weight: "semibold", color: "primary", children: title }), subtitle ? (_jsx(Text, { size: "sm", color: "muted", children: subtitle })) : null] }), draws.length === 0 ? (_jsxs(Stack, { align: "start", gap: "sm", className: "border-dashed text-left", border: "strong", paddingY: "y-3xl", paddingX: "x-lg", rounded: "2xl", children: [_jsx(Text, { size: "sm", color: "muted", children: "No active prize draws \u2014 new draws are announced regularly." }), _jsx(Link, { href: String(ROUTES.PUBLIC.AUCTIONS), className: "text-[length:var(--appkit-text-sm)] font-medium text-primary hover:underline", children: "Browse live auctions \u2192" })] })) : (_jsx(Div, { gap: "3", className: "fluid-grid-card", children: draws.map((draw) => (_jsx(InteractiveProductCard, { product: draw, href: String(ROUTES.PUBLIC.PRODUCT_DETAIL(draw.slug ?? draw.id ?? "")) }, draw.id))) }))] }) }) }));
30
+ if (draws.length === 0)
31
+ return null;
32
+ return (_jsx(Section, { padding: "y-2xl", children: _jsx(Container, { size: "xl", children: _jsxs(Stack, { gap: "md", children: [_jsxs(Stack, { gap: "xs", children: [_jsx(Heading, { level: 2, size: "2xl", weight: "semibold", color: "primary", children: title }), subtitle ? (_jsx(Text, { size: "sm", color: "muted", children: subtitle })) : null] }), _jsx(Div, { gap: "3", className: "fluid-grid-card", children: draws.map((draw) => (_jsx(InteractiveProductCard, { product: draw, href: String(ROUTES.PUBLIC.PRODUCT_DETAIL(draw.slug ?? draw.id ?? "")) }, draw.id))) })] }) }) }));
32
33
  }
@@ -3,7 +3,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
3
3
  import { Row } from "@mohasinac/appkit/ui";
4
4
  import { useRef } from "react";
5
5
  import { useTranslations } from "next-intl";
6
- import { Alert, Checkbox, Div, FormField, FormGroup, Heading, Label, RichTextEditor, Stack, Text, } from "../../../ui";
6
+ import { Alert, Checkbox, Div, FormField, FormGroup, Heading, Label, RichTextEditor, Stack, TagInput, Text, } from "../../../ui";
7
7
  import { CustomSectionsEditor } from "./CustomSectionsEditor";
8
8
  import { SublistingCategorySelect } from "./SublistingCategorySelect";
9
9
  import { ProductFeaturesSelector } from "./ProductFeaturesSelector";
@@ -101,7 +101,7 @@ export function ProductForm({ product, onChange, isReadonly = false, renderDescr
101
101
  onValueChange: (value) => update({ brand: value }),
102
102
  onValuesChange: (values) => update({ brands: values }),
103
103
  disabled: isReadonly,
104
- })) : product.brandMode === "mixed" ? (_jsx(FormField, { name: "brands", label: "Brands (comma-separated)", type: "text", value: (product.brands || []).join(", "), onChange: (value) => update({ brands: value.split(",").map((b) => b.trim()).filter(Boolean) }), disabled: isReadonly, placeholder: "e.g. Nike, Adidas, Puma" })) : (_jsx(FormField, { name: "brand", label: t("formBrand"), type: "text", value: product.brand || "", onChange: (value) => update({ brand: value }), disabled: isReadonly, placeholder: "e.g. Apple" })) }))] }), _jsxs(FormGroup, { columns: 2, children: [_jsx(Div, {}), " ", _jsx(FormField, { name: "status", label: t("formStatus"), type: "select", value: product.status || "draft", onChange: (value) => update({ status: value }), disabled: isReadonly, options: PRODUCT_STATUS_OPTIONS })] }), _jsxs(FormGroup, { columns: 2, children: [_jsx(FormField, { name: "price", label: t("formPrice"), type: "number", value: String(product.price ?? ""), onChange: (value) => update({ price: Number(value) }), disabled: isReadonly, placeholder: "0" }), _jsx(FormField, { name: "stockQuantity", label: t("formStock"), type: "number", value: String(product.stockQuantity ?? ""), onChange: (value) => update({ stockQuantity: Number(value) }), disabled: isReadonly, placeholder: "0" })] }), !isReadonly && (_jsx(ImageUpload, { currentImage: product.mainImage, onUpload: (file) => upload(file, "products", true, {
104
+ })) : product.brandMode === "mixed" ? (_jsx(TagInput, { label: "Brands", value: product.brands || [], onChange: (brands) => update({ brands }), disabled: isReadonly, placeholder: "e.g. Nike, Adidas, Puma" })) : (_jsx(FormField, { name: "brand", label: t("formBrand"), type: "text", value: product.brand || "", onChange: (value) => update({ brand: value }), disabled: isReadonly, placeholder: "e.g. Apple" })) }))] }), _jsxs(FormGroup, { columns: 2, children: [_jsx(Div, {}), " ", _jsx(FormField, { name: "status", label: t("formStatus"), type: "select", value: product.status || "draft", onChange: (value) => update({ status: value }), disabled: isReadonly, options: PRODUCT_STATUS_OPTIONS })] }), _jsxs(FormGroup, { columns: 2, children: [_jsx(FormField, { name: "price", label: t("formPrice"), type: "number", value: String(product.price ?? ""), onChange: (value) => update({ price: Number(value) }), disabled: isReadonly, placeholder: "0" }), _jsx(FormField, { name: "stockQuantity", label: t("formStock"), type: "number", value: String(product.stockQuantity ?? ""), onChange: (value) => update({ stockQuantity: Number(value) }), disabled: isReadonly, placeholder: "0" })] }), !isReadonly && (_jsx(ImageUpload, { currentImage: product.mainImage, onUpload: (file) => upload(file, "products", true, {
105
105
  type: "product-image",
106
106
  index: 1,
107
107
  name: product.title || "product",
@@ -132,12 +132,7 @@ export function ProductForm({ product, onChange, isReadonly = false, renderDescr
132
132
  thumbnailUrl: media.thumbnailUrl,
133
133
  },
134
134
  });
135
- }, onUpload: handleVideoUpload, accept: "video/*", maxSizeMB: 50, helperText: t("formVideoHelper"), onAbort: onMediaAbort })), _jsx(FormField, { name: "tags", label: t("formTags"), type: "text", value: (product.tags || []).join(", "), onChange: (value) => update({
136
- tags: value
137
- .split(",")
138
- .map((tag) => tag.trim())
139
- .filter(Boolean),
140
- }), disabled: isReadonly, placeholder: t("formTagsPlaceholder") }), _jsxs(FormGroup, { columns: 2, children: [_jsx(Checkbox, { label: t("formFeatured"), checked: !!product.featured, onChange: (e) => update({ featured: e.target.checked }), disabled: isReadonly }), _jsx(Checkbox, { label: t("formIsPromoted"), checked: !!product.isPromoted, onChange: (e) => update({ isPromoted: e.target.checked }), disabled: isReadonly }), _jsx(Checkbox, { label: t("formAllowShipBeforeEmiComplete"), checked: !!product.allowShipBeforeEmiComplete, onChange: (e) => update({ allowShipBeforeEmiComplete: e.target.checked }), disabled: isReadonly })] }), _jsx(Heading, { level: 4, className: "mt-4", children: t("sectionConditionShipping") }), _jsxs(FormGroup, { columns: 2, children: [_jsx(FormField, { name: "condition", label: t("formCondition"), type: "select", value: product.condition || "new", onChange: (value) => update({
135
+ }, onUpload: handleVideoUpload, accept: "video/*", maxSizeMB: 50, helperText: t("formVideoHelper"), onAbort: onMediaAbort })), _jsx(TagInput, { label: t("formTags"), value: product.tags || [], onChange: (tags) => update({ tags }), disabled: isReadonly, placeholder: t("formTagsPlaceholder") }), _jsxs(FormGroup, { columns: 2, children: [_jsx(Checkbox, { label: t("formFeatured"), checked: !!product.featured, onChange: (e) => update({ featured: e.target.checked }), disabled: isReadonly }), _jsx(Checkbox, { label: t("formIsPromoted"), checked: !!product.isPromoted, onChange: (e) => update({ isPromoted: e.target.checked }), disabled: isReadonly }), _jsx(Checkbox, { label: t("formAllowShipBeforeEmiComplete"), checked: !!product.allowShipBeforeEmiComplete, onChange: (e) => update({ allowShipBeforeEmiComplete: e.target.checked }), disabled: isReadonly })] }), _jsx(Heading, { level: 4, className: "mt-4", children: t("sectionConditionShipping") }), _jsxs(FormGroup, { columns: 2, children: [_jsx(FormField, { name: "condition", label: t("formCondition"), type: "select", value: product.condition || "new", onChange: (value) => update({
141
136
  condition: value,
142
137
  }), disabled: isReadonly, options: [
143
138
  { value: "new", label: t("formConditionNew") },
@@ -209,7 +204,7 @@ export function ProductForm({ product, onChange, isReadonly = false, renderDescr
209
204
  value: product.pickupAddressId || "",
210
205
  onChange: (value) => update({ pickupAddressId: value }),
211
206
  disabled: isReadonly,
212
- }) })) : (_jsx(FormField, { name: "pickupAddressId", label: t("formPickupAddress"), type: "text", value: product.pickupAddressId || "", onChange: (value) => update({ pickupAddressId: value }), disabled: isReadonly })), _jsx(SublistingCategorySelect, { value: product.sublistingCategoryId ?? "", onChange: (id) => update({ sublistingCategoryId: id || undefined }), disabled: isReadonly }), !isAuctionListing(product) && renderGroupJoinField?.({
207
+ }) })) : (_jsx(FormField, { name: "pickupAddressId", label: t("formPickupAddress"), type: "text", value: product.pickupAddressId || "", onChange: (value) => update({ pickupAddressId: value }), disabled: isReadonly })), _jsx(SublistingCategorySelect, { value: product.sublistingCategoryId ?? "", onChange: (id) => update({ sublistingCategoryId: id || undefined }), disabled: isReadonly, allowCreate: true }), !isAuctionListing(product) && renderGroupJoinField?.({
213
208
  label: "Group (optional)",
214
209
  value: product.groupId,
215
210
  onChange: (id) => update({ groupId: id || undefined }),
@@ -24,7 +24,7 @@ const CLS_BADGE_SALE = "rounded-full bg-success-surface px-[var(--appkit-space-2
24
24
  const CLS_BADGE_TRENDING = "rounded-full bg-indigo-600 px-[var(--appkit-space-2)] py-[var(--appkit-space-0-5)] text-[10px] font-bold text-white shadow-sm";
25
25
  const CLS_BADGE_LIMITED = "rounded-full bg-info-surface px-[var(--appkit-space-2)] py-[var(--appkit-space-0-5)] text-[10px] font-bold text-white shadow-sm";
26
26
  const CLS_HEART_ACTIVE = "bg-error-surface text-white hover:bg-error-surface";
27
- const CLS_HEART_IDLE = "bg-white/90 bg-[var(--appkit-color-surface-elevated)]/90 text-[var(--appkit-color-text-muted)] hover:text-error hover:bg-[var(--appkit-color-surface)] hover:bg-[var(--appkit-color-surface-elevated)]";
27
+ const CLS_HEART_IDLE = "bg-[var(--appkit-color-surface-elevated)]/90 text-[var(--appkit-color-text-muted)] hover:text-error hover:bg-[var(--appkit-color-surface-elevated)]";
28
28
  const CLS_STAR = "text-[11px] text-warning";
29
29
  const CLS_DISCOUNT_TEXT = "mt-1 text-[11px] font-semibold text-error";
30
30
  const CLS_DISCOUNT_TEXT_BARE = "text-[10px] text-error";
@@ -195,6 +195,10 @@ export interface ProductDocument extends BaseDocument {
195
195
  isSold?: boolean;
196
196
  /** When true, an EMI order for this product ships as soon as it's confirmed instead of waiting for every installment to be paid. Default false. */
197
197
  allowShipBeforeEmiComplete?: boolean;
198
+ /** P-8 GST — buyer-facing tax rate on this product (%). Unset/0 = exempt. */
199
+ gstRate?: 0 | 5 | 12 | 18 | 28;
200
+ /** P-8 GST — Harmonized System of Nomenclature code for GST-compliant invoices. */
201
+ hsnCode?: string;
198
202
  /** Print-specific metadata for "art" / "stickers" listings — printed-only physical goods. Optional on every listing type; only populated for art/stickers. */
199
203
  printMeta?: ProductPrintMeta;
200
204
  promotionEndDate?: Date;
@@ -167,7 +167,7 @@ export function CouponCard({ coupon, labels: labelsProp, onCopy, className = "",
167
167
  setBusy(null);
168
168
  }
169
169
  };
170
- return (_jsxs(Stack, { className: `group relative h-full border-2 ${colors.card} ${isSelected ? "ring-2 ring-primary" : ""} ${!n.isActive ? "opacity-70" : ""} ${className}`, rounded: "xl", padding: "md", onMouseDown: onSelect && !isSelected ? longPress.onMouseDown : undefined, onMouseUp: onSelect && !isSelected ? longPress.onMouseUp : undefined, onMouseLeave: onSelect && !isSelected ? longPress.onMouseLeave : undefined, onTouchStart: onSelect && !isSelected ? longPress.onTouchStart : undefined, onTouchEnd: onSelect && !isSelected ? longPress.onTouchEnd : undefined, children: [onSelect && (_jsx(BaseListingCard.Checkbox, { selected: isSelected, onSelect: (e) => { e.preventDefault(); onSelect(n.id, !isSelected); }, label: isSelected ? "Deselect coupon" : "Select coupon", position: "top-2 right-2", className: selectable || isSelected ? "opacity-100" : "opacity-0 group-hover:opacity-100 transition-opacity" })), (hasAdminActions || n.scope) && (_jsxs(Row, { gap: "xs", wrap: true, className: "mb-2", children: [_jsx(Badge, { variant: n.isActive ? "active" : "inactive", size: "xs", className: "text-[10px] uppercase tracking-wide", children: n.isActive ? labels.active : labels.inactive }), n.scope && (_jsx(Span, { layout: "inline-flex", weight: "semibold", className: "bg-neutral-200/70 text-[10px] tracking-wide bg-[var(--appkit-color-border)]/60", rounded: "full", padding: "pill-xs", color: "primary", transform: "uppercase", children: n.scope }))] })), _jsxs(Div, { className: "mb-3", children: [_jsx(Text, { className: "font-extrabold tracking-tight leading-none", size: "2xl", children: discountLabel }), n.name && (_jsx(Text, { className: "mt-0.5 opacity-80", size: "sm", weight: "medium", children: n.name }))] }), n.description && (_jsx(Text, { className: "opacity-60 mb-3", size: "xs", children: n.description })), _jsxs(Row, { className: `border border-dashed ${colors.code}`, align: "center", gap: "sm", rounded: "lg", padding: "inlineSm", children: [_jsx(Span, { size: "sm", weight: "bold", className: "flex-1 font-mono tracking-widest select-all", transform: "uppercase", children: n.code || "—" }), n.code && (_jsx(Button, { variant: "outline", type: "button", onClick: handleCopy, rounded: "md", paddingX: "sm", paddingY: "none", textSize: "xs", weight: "semibold", className: "shrink-0 hover:opacity-80 active:scale-95 border-current/20", "aria-label": "Copy coupon code", children: copied ? labels.copied : labels.copy }))] }), showClaim && n.code && n.isActive && (_jsx(Div, { className: "mt-2", children: _jsx(Button, { variant: "primary", type: "button", onClick: handleClaim, disabled: claiming, rounded: "md", paddingX: "md", paddingY: "xs", textSize: "xs", weight: "semibold", className: "w-full active:scale-95 bg-[var(--appkit-color-primary-700)] hover:bg-[var(--appkit-color-primary-800)]", "aria-label": `Claim coupon ${n.code} and apply at checkout`, children: claiming ? labels.claiming : labels.claim }) })), _jsxs(Row, { textSize: "xs", wrap: true, gap: "sm", className: "opacity-60 mt-2", children: [n.minOrderAmount ? (_jsxs(Span, { children: [labels.minOrder, ": \u20B9", (n.minOrderAmount / 100).toFixed(0)] })) : null, expiry && (_jsxs(Span, { children: [labels.expires, ": ", expiry] })), n.usageLimit ? (_jsxs(Span, { children: [n.usageCount, "/", n.usageLimit, " ", labels.used] })) : n.usageCount > 0 ? (_jsxs(Span, { children: [n.usageCount, " ", labels.used] })) : null] }), hasAdminActions && (_jsxs(Row, { gap: "xs", justify: "end", className: "mt-auto", padding: "t-sm", children: [onEdit && (_jsx(IconButton, { type: "button", onClick: () => onEdit(n.id), title: labels.edit, "aria-label": labels.edit, variant: "ghost", rounded: "lg", className: "text-[var(--appkit-color-text-muted)]", children: _jsx(Pencil, { className: "h-4 w-4" }) })), onToggleActive && (_jsx(IconButton, { type: "button", onClick: handleToggle, disabled: busy === "toggle", title: n.isActive ? labels.deactivate : labels.activate, "aria-label": n.isActive ? labels.deactivate : labels.activate, variant: "ghost", rounded: "lg", className: "text-[var(--appkit-color-text-muted)]", children: n.isActive
170
+ return (_jsxs(Stack, { className: `group relative h-full border-2 ${colors.card} ${isSelected ? "ring-2 ring-primary" : ""} ${!n.isActive ? "opacity-70" : ""} ${className}`, rounded: "xl", padding: "md", onMouseDown: onSelect && !isSelected ? longPress.onMouseDown : undefined, onMouseUp: onSelect && !isSelected ? longPress.onMouseUp : undefined, onMouseLeave: onSelect && !isSelected ? longPress.onMouseLeave : undefined, onTouchStart: onSelect && !isSelected ? longPress.onTouchStart : undefined, onTouchEnd: onSelect && !isSelected ? longPress.onTouchEnd : undefined, onTouchCancel: onSelect && !isSelected ? longPress.onTouchCancel : undefined, children: [onSelect && (_jsx(BaseListingCard.Checkbox, { selected: isSelected, onSelect: (e) => { e.preventDefault(); onSelect(n.id, !isSelected); }, label: isSelected ? "Deselect coupon" : "Select coupon", position: "top-2 right-2", className: selectable || isSelected ? "opacity-100" : "opacity-0 group-hover:opacity-100 transition-opacity" })), (hasAdminActions || n.scope) && (_jsxs(Row, { gap: "xs", wrap: true, className: "mb-2", children: [_jsx(Badge, { variant: n.isActive ? "active" : "inactive", size: "xs", className: "text-[10px] uppercase tracking-wide", children: n.isActive ? labels.active : labels.inactive }), n.scope && (_jsx(Span, { layout: "inline-flex", weight: "semibold", className: "bg-neutral-200/70 text-[10px] tracking-wide bg-[var(--appkit-color-border)]/60", rounded: "full", padding: "pill-xs", color: "primary", transform: "uppercase", children: n.scope }))] })), _jsxs(Div, { className: "mb-3", children: [_jsx(Text, { className: "font-extrabold tracking-tight leading-none", size: "2xl", children: discountLabel }), n.name && (_jsx(Text, { className: "mt-0.5 opacity-80", size: "sm", weight: "medium", children: n.name }))] }), n.description && (_jsx(Text, { className: "opacity-60 mb-3", size: "xs", children: n.description })), _jsxs(Row, { className: `border border-dashed ${colors.code}`, align: "center", gap: "sm", rounded: "lg", padding: "inlineSm", children: [_jsx(Span, { size: "sm", weight: "bold", className: "flex-1 font-mono tracking-widest select-all", transform: "uppercase", children: n.code || "—" }), n.code && (_jsx(Button, { variant: "outline", type: "button", onClick: handleCopy, rounded: "md", paddingX: "sm", paddingY: "none", textSize: "xs", weight: "semibold", className: "shrink-0 hover:opacity-80 active:scale-95 border-current/20", "aria-label": "Copy coupon code", children: copied ? labels.copied : labels.copy }))] }), showClaim && n.code && n.isActive && (_jsx(Div, { className: "mt-2", children: _jsx(Button, { variant: "primary", type: "button", onClick: handleClaim, disabled: claiming, rounded: "md", paddingX: "md", paddingY: "xs", textSize: "xs", weight: "semibold", className: "w-full active:scale-95 bg-[var(--appkit-color-primary-700)] hover:bg-[var(--appkit-color-primary-800)]", "aria-label": `Claim coupon ${n.code} and apply at checkout`, children: claiming ? labels.claiming : labels.claim }) })), _jsxs(Row, { textSize: "xs", wrap: true, gap: "sm", className: "opacity-60 mt-2", children: [n.minOrderAmount ? (_jsxs(Span, { children: [labels.minOrder, ": \u20B9", (n.minOrderAmount / 100).toFixed(0)] })) : null, expiry && (_jsxs(Span, { children: [labels.expires, ": ", expiry] })), n.usageLimit ? (_jsxs(Span, { children: [n.usageCount, "/", n.usageLimit, " ", labels.used] })) : n.usageCount > 0 ? (_jsxs(Span, { children: [n.usageCount, " ", labels.used] })) : null] }), hasAdminActions && (_jsxs(Row, { gap: "xs", justify: "end", className: "mt-auto", padding: "t-sm", children: [onEdit && (_jsx(IconButton, { type: "button", onClick: () => onEdit(n.id), title: labels.edit, "aria-label": labels.edit, variant: "ghost", rounded: "lg", className: "text-[var(--appkit-color-text-muted)]", children: _jsx(Pencil, { className: "h-4 w-4" }) })), onToggleActive && (_jsx(IconButton, { type: "button", onClick: handleToggle, disabled: busy === "toggle", title: n.isActive ? labels.deactivate : labels.activate, "aria-label": n.isActive ? labels.deactivate : labels.activate, variant: "ghost", rounded: "lg", className: "text-[var(--appkit-color-text-muted)]", children: n.isActive
171
171
  ? _jsx(ToggleRight, { className: CLS_TOGGLE_ON })
172
172
  : _jsx(ToggleLeft, { className: "h-4 w-4" }) })), onDelete && (_jsx(IconButton, { type: "button", onClick: () => setDeleteConfirmOpen(true), disabled: busy === "delete", title: labels.delete, "aria-label": labels.delete, variant: "ghost", rounded: "lg", className: CLS_DELETE_BTN, children: _jsx(Trash2, { className: "h-4 w-4" }) }))] })), deleteConfirmOpen && (_jsx(ConfirmDeleteModal, { isOpen: true, title: "Delete Coupon", message: labels.deleteConfirm, onConfirm: handleDeleteConfirmed, onClose: () => setDeleteConfirmOpen(false), isDeleting: busy === "delete" }))] }));
173
173
  }
@@ -27,3 +27,15 @@ export declare function getPublicScammerById(id: string): Promise<ScammerDocumen
27
27
  * Returns null if not found or not verified.
28
28
  */
29
29
  export declare function getScammerProfilePageData(id: string): Promise<ScammerProfilePageData | null>;
30
+ export type SellerTrustStatus = "clear" | "flagged";
31
+ export interface SellerTrustResult {
32
+ status: SellerTrustStatus;
33
+ /** seoSlug of matched verified scammer profiles — links to /scams/[slug]. Empty when clear. */
34
+ matchedProfileSlugs: string[];
35
+ }
36
+ /**
37
+ * P-12 — resolves a store owner's phone/email against the verified (admin-published)
38
+ * scammer registry only. Never surfaces "under review" reports to buyers — a report
39
+ * that hasn't been verified must not damage a seller's storefront reputation.
40
+ */
41
+ export declare function getSellerTrustStatus(storeId: string): Promise<SellerTrustResult>;
@@ -1,6 +1,8 @@
1
1
  "use server";
2
2
  import { sieveFilter, SIEVE_OP } from "@mohasinac/appkit";
3
3
  import { scammerRepository } from "../repository/scammer.repository";
4
+ import { userRepository } from "../../auth/repository/user.repository";
5
+ import { storeRepository } from "../../stores/repository/store.repository";
4
6
  import { safeFireAndForget } from "../../../utils/safe-fire-forget";
5
7
  /**
6
8
  * List verified scammer profiles for the public registry page.
@@ -62,3 +64,32 @@ export async function getScammerProfilePageData(id) {
62
64
  ]);
63
65
  return { scammer, incidents, comments, relatedScammers };
64
66
  }
67
+ const TRUST_STATUS_CLEAR = { status: "clear", matchedProfileSlugs: [] };
68
+ /**
69
+ * P-12 — resolves a store owner's phone/email against the verified (admin-published)
70
+ * scammer registry only. Never surfaces "under review" reports to buyers — a report
71
+ * that hasn't been verified must not damage a seller's storefront reputation.
72
+ */
73
+ export async function getSellerTrustStatus(storeId) {
74
+ const store = await storeRepository.findById(storeId).catch(() => null);
75
+ if (!store?.ownerId)
76
+ return TRUST_STATUS_CLEAR;
77
+ const owner = await userRepository.findById(store.ownerId).catch(() => null);
78
+ if (!owner)
79
+ return TRUST_STATUS_CLEAR;
80
+ const lookups = [];
81
+ if (owner.phoneNumber)
82
+ lookups.push(scammerRepository.findByContactField("phones", owner.phoneNumber));
83
+ if (owner.email)
84
+ lookups.push(scammerRepository.findByContactField("emails", owner.email));
85
+ if (lookups.length === 0)
86
+ return TRUST_STATUS_CLEAR;
87
+ const results = await Promise.all(lookups.map((p) => p.catch(() => [])));
88
+ const verified = results.flat().filter((s) => s.status === "verified");
89
+ if (verified.length === 0)
90
+ return TRUST_STATUS_CLEAR;
91
+ return {
92
+ status: "flagged",
93
+ matchedProfileSlugs: [...new Set(verified.map((s) => s.seoSlug))],
94
+ };
95
+ }
@@ -0,0 +1,16 @@
1
+ import type { SellerTrustResult } from "../actions/scam-actions";
2
+ export interface SellerTrustBadgeProps {
3
+ trust: SellerTrustResult;
4
+ className?: string;
5
+ }
6
+ /**
7
+ * P-12 — renders next to a seller's storefront name. Only ever reflects
8
+ * admin-verified scammer profiles (see getSellerTrustStatus) — unverified
9
+ * reports never reach this component, so "clear" here means "no verified
10
+ * match," not "no reports filed."
11
+ *
12
+ * Named SellerTrustBadge (not TrustBadge) to avoid colliding with the
13
+ * unrelated homepage-section `TrustBadge` config type in
14
+ * features/homepage/index.ts (marketing trust-badge icons row).
15
+ */
16
+ export declare function SellerTrustBadge({ trust, className }: SellerTrustBadgeProps): import("react").JSX.Element;
@@ -0,0 +1,21 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Badge } from "../../../ui/components/Badge";
3
+ import { TextLink } from "../../../ui/components/TextLink";
4
+ import { ROUTES } from "../../../next";
5
+ /**
6
+ * P-12 — renders next to a seller's storefront name. Only ever reflects
7
+ * admin-verified scammer profiles (see getSellerTrustStatus) — unverified
8
+ * reports never reach this component, so "clear" here means "no verified
9
+ * match," not "no reports filed."
10
+ *
11
+ * Named SellerTrustBadge (not TrustBadge) to avoid colliding with the
12
+ * unrelated homepage-section `TrustBadge` config type in
13
+ * features/homepage/index.ts (marketing trust-badge icons row).
14
+ */
15
+ export function SellerTrustBadge({ trust, className = "" }) {
16
+ if (trust.status === "clear") {
17
+ return (_jsx(Badge, { variant: "success", className: className, children: "\u2713 Verified Safe" }));
18
+ }
19
+ const firstSlug = trust.matchedProfileSlugs[0];
20
+ return (_jsx(Badge, { variant: "danger", className: className, children: firstSlug ? (_jsx(TextLink, { href: String(ROUTES.PUBLIC.SCAM_DETAIL(firstSlug)), children: "\u26A0 Flagged in Scam Registry" })) : ("⚠ Flagged in Scam Registry") }));
21
+ }
@@ -4,3 +4,5 @@ export { ScamProfileView } from "./ScamProfileView";
4
4
  export type { ScamProfileViewProps } from "./ScamProfileView";
5
5
  export { ScamAwarenessModal } from "./ScamAwarenessModal";
6
6
  export type { ScamAwarenessModalProps } from "./ScamAwarenessModal";
7
+ export { SellerTrustBadge } from "./SellerTrustBadge";
8
+ export type { SellerTrustBadgeProps } from "./SellerTrustBadge";
@@ -1,3 +1,4 @@
1
1
  export { ScamRegistryView } from "./ScamRegistryView";
2
2
  export { ScamProfileView } from "./ScamProfileView";
3
3
  export { ScamAwarenessModal } from "./ScamAwarenessModal";
4
+ export { SellerTrustBadge } from "./SellerTrustBadge";
@@ -1,8 +1,8 @@
1
1
  import React from "react";
2
2
  export interface SellerBundlesViewProps {
3
- onCreateClick?: () => void;
4
- onEditClick?: (id: string) => void;
5
- onDelete?: (id: string) => Promise<void>;
6
- onBulkDelete?: (ids: string[]) => Promise<void>;
3
+ getEditHref: (row: {
4
+ id: string;
5
+ }) => string;
6
+ newHref: string;
7
7
  }
8
- export declare function SellerBundlesView({ onCreateClick, onEditClick, onDelete, onBulkDelete, }: SellerBundlesViewProps): React.JSX.Element;
8
+ export declare function SellerBundlesView({ getEditHref, newHref }: SellerBundlesViewProps): React.JSX.Element;
@@ -1,111 +1,108 @@
1
1
  "use client";
2
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { Badge, Span, sortBy } from "@mohasinac/appkit";
4
- import { useState, useCallback } from "react";
5
- import { useEntityDelete } from "../../../react/hooks/useEntityDelete";
6
- import { ConfirmDeleteModal, RowActionMenu, Text } from "../../../ui";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { SIEVE_OP, sieveFilter, sortBy } from "@mohasinac/appkit";
4
+ import { Badge, Button, FilterChipGroup, Stack, Text, TextLink } from "../../../ui";
7
5
  import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
8
- import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
9
- import { ROUTES } from "../../..";
10
- import { SELLER_BULK_ACTIONS, ROW_ACTION_META } from "../../products/constants/action-defs";
11
- const CLS_ITEMS_PILL = "inline-flex items-center rounded-full px-[var(--appkit-space-2)] py-[var(--appkit-space-0-5)] text-[length:var(--appkit-text-xs)] font-medium bg-violet-100 text-violet-700 dark:bg-violet-900/40 dark:text-violet-300 tabular-nums";
12
- import { toRecordArray, toRelativeDate, toRupees, toStringValue, } from "../../admin/hooks/useAdminListingData";
6
+ import { ROW_ACTION_META, ROW_ACTION_ID } from "../../products/constants/action-defs";
7
+ import { SELLER_BULK_ACTIONS } from "../../products/constants/action-defs";
8
+ import { BUNDLE_COPY, BUNDLE_STOCK_VARIANT, } from "../../../_internal/shared/features/categories/bundle-copy";
9
+ import { toRecordArray, toRelativeDate, toStringValue, } from "../../admin/hooks/useAdminListingData";
13
10
  import { DataListingView } from "../../admin/components/DataListingView";
11
+ function formatPrice(paise) {
12
+ if (typeof paise !== "number" || paise <= 0)
13
+ return "—";
14
+ return `₹${Math.round(paise / 100).toLocaleString("en-IN")}`;
15
+ }
14
16
  const COLUMNS = [
15
- { key: "title", header: "Bundle", render: (row) => _jsx(Text, { size: "sm", weight: "medium", children: row.title }) },
16
- { key: "price", header: "Price", render: (row) => _jsx(Text, { className: "tabular-nums", size: "sm", children: row.price }) },
17
17
  {
18
- key: "itemCount",
19
- header: "Items",
20
- render: (row) => (_jsx(Span, { className: CLS_ITEMS_PILL, children: row.itemCount })),
18
+ key: "primary",
19
+ header: "Name",
20
+ render: (row) => (_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { weight: "medium", color: "primary", children: row.primary }), _jsx(Text, { size: "xs", color: "muted", children: row.secondary })] })),
21
21
  },
22
+ { key: "price", header: "Price", className: "w-28" },
23
+ { key: "members", header: "Members", className: "w-24" },
22
24
  {
23
- key: "status",
24
- header: "Status",
25
- render: (row) => (_jsx(Badge, { variant: row.status === "active" ? "active" : "inactive", size: "xs", className: "capitalize", children: row.status })),
25
+ key: "stockStatus",
26
+ header: "Stock",
27
+ className: "w-28",
28
+ render: (row) => (_jsx(Badge, { variant: BUNDLE_STOCK_VARIANT[row.stockStatus] ?? "default", children: row.stockStatus === "in_stock"
29
+ ? BUNDLE_COPY.stockBadge.listVariantInStock
30
+ : BUNDLE_COPY.stockBadge.listVariantOutOfStock })),
26
31
  },
27
32
  {
28
- key: "createdAt",
29
- header: "Created",
30
- render: (row) => (_jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "sm", children: row.createdAt })),
33
+ key: "status",
34
+ header: "Status",
35
+ className: "w-24",
36
+ render: (row) => (_jsx(Badge, { variant: row.isActive ? "success" : "default", children: row.status })),
31
37
  },
38
+ { key: "updatedAt", header: "Updated", className: "w-28" },
32
39
  ];
33
- export function SellerBundlesView({ onCreateClick, onEditClick, onDelete, onBulkDelete, }) {
34
- const [deleteTargetId, setDeleteTargetId] = useState(null);
35
- const { deletingId, handleDelete: performDelete } = useEntityDelete({
36
- endpoint: SELLER_ENDPOINTS.PRODUCT_BY_ID,
37
- deleteFn: onDelete,
38
- successMessage: "Bundle deleted.",
39
- fetchOptions: { credentials: "include" },
40
- });
41
- const handleDelete = useCallback(async (id) => {
42
- await performDelete(id);
43
- setDeleteTargetId(null);
44
- }, [performDelete]);
45
- const handleEdit = useCallback((id) => {
46
- if (onEditClick)
47
- onEditClick(id);
48
- else
49
- window.location.href = String(ROUTES.STORE.PRODUCTS_EDIT(id));
50
- }, [onEditClick]);
51
- const handleCreate = useCallback(() => {
52
- if (onCreateClick)
53
- onCreateClick();
54
- else
55
- window.location.href = String(ROUTES.STORE.PRODUCTS_NEW);
56
- }, [onCreateClick]);
40
+ export function SellerBundlesView({ getEditHref, newHref }) {
57
41
  const config = {
58
42
  portal: "seller",
59
43
  title: "Bundles",
60
- searchPlaceholder: "Search bundles...",
61
- emptyLabel: "No bundles yet — create a bundle to group multiple products together",
62
- filterKeys: [],
63
- defaultSort: sortBy("createdAt", "DESC"),
64
- queryKey: ["seller", "bundles"],
65
- endpoint: SELLER_ENDPOINTS.PRODUCTS,
44
+ searchPlaceholder: "Search bundles by name or slug…",
45
+ emptyLabel: "No bundles yet — create a bundle to group multiple of your products together",
46
+ filterKeys: ["isActive", "bundleStockStatus"],
47
+ defaultSort: sortBy("name", "ASC"),
48
+ queryKey: ["seller", "bundles", "listing"],
49
+ endpoint: SELLER_ENDPOINTS.BUNDLES,
66
50
  sortOptions: [
51
+ { value: sortBy("name", "ASC"), label: "Name A–Z" },
52
+ { value: sortBy("name", "DESC"), label: "Name Z–A" },
53
+ { value: sortBy("bundlePriceInPaise", "DESC"), label: "Price high→low" },
54
+ { value: "bundlePriceInPaise", label: "Price low→high" },
67
55
  { value: sortBy("createdAt", "DESC"), label: "Newest" },
68
56
  { value: sortBy("createdAt", "ASC"), label: "Oldest" },
69
- { value: "productTitle", label: "Name A–Z" },
70
- { value: sortBy("productTitle", "DESC"), label: "Name Z–A" },
71
- { value: sortBy("price", "ASC"), label: "Price: Low–High" },
72
- { value: sortBy("price", "DESC"), label: "Price: High–Low" },
73
57
  ],
74
58
  columns: COLUMNS,
75
- mapRows: (response) => toRecordArray(response.products).map((item, index) => ({
59
+ mapRows: (response) => toRecordArray(response.items).map((item, index) => ({
76
60
  id: toStringValue(item.id, `bundle-${index}`),
77
- title: toStringValue(item.productTitle ?? item.title, "Untitled bundle"),
78
- price: toRupees(item.price),
79
- itemCount: Array.isArray(item.bundleProductIds)
80
- ? item.bundleProductIds.length
81
- : Number(item.bundleItemCount ?? 0),
82
- status: toStringValue(item.status, "draft"),
83
- createdAt: toRelativeDate(item.createdAt),
61
+ primary: toStringValue(item.name, "Untitled bundle"),
62
+ secondary: toStringValue(item.slug, "no-slug"),
63
+ price: formatPrice(item.bundlePriceInPaise),
64
+ members: String(Array.isArray(item.bundleProductIds) ? item.bundleProductIds.length : 0),
65
+ stockStatus: toStringValue(item.bundleStockStatus, "in_stock"),
66
+ isActive: item.isActive === true,
67
+ status: item.isActive === true
68
+ ? BUNDLE_COPY.adminList.activeBadge
69
+ : BUNDLE_COPY.adminList.inactiveBadge,
70
+ updatedAt: toRelativeDate(item.updatedAt ?? item.createdAt),
84
71
  })),
85
- getTotal: (response, mappedRows) => typeof response.meta?.total === "number" ? response.meta.total : mappedRows.length,
86
- buildFilters: () => "listingType==bundle",
87
- primaryAction: { label: "New Bundle", onClick: () => handleCreate() },
72
+ getTotal: (response, mappedRows) => typeof response.total === "number" ? response.total : mappedRows.length,
73
+ buildFilters: (filterState) => {
74
+ const parts = [];
75
+ if (filterState.isActive)
76
+ parts.push(sieveFilter("isActive", SIEVE_OP.EQ, filterState.isActive));
77
+ if (filterState.bundleStockStatus)
78
+ parts.push(sieveFilter("bundleStockStatus", SIEVE_OP.EQ, filterState.bundleStockStatus));
79
+ return parts.join(",") || undefined;
80
+ },
81
+ getRowHref: getEditHref,
82
+ toolbarExtra: (_jsx(Button, { asChild: true, size: "sm", variant: "primary", children: _jsx(TextLink, { href: newHref, layout: "flex", align: "center", gap: "xs", children: "+ New Bundle" }) })),
88
83
  // Rule #7: bulk-action array sourced from the SELLER_BULK_ACTIONS preset.
89
- buildBulkActions: onBulkDelete
90
- ? (selection) => SELLER_BULK_ACTIONS.bundles.map((id) => ({
84
+ buildBulkActions: (selection) => {
85
+ const handlers = {
86
+ [ROW_ACTION_ID.DELETE]: async () => {
87
+ await Promise.all(selection.selectedIds.map((id) => fetch(SELLER_ENDPOINTS.BUNDLE_BY_ID(id), { method: "DELETE" })));
88
+ selection.clearSelection();
89
+ },
90
+ };
91
+ return SELLER_BULK_ACTIONS.bundles.map((id) => ({
91
92
  id,
92
93
  label: ROW_ACTION_META[id].label,
93
94
  destructive: ROW_ACTION_META[id].destructive,
94
- onClick: async () => {
95
- await onBulkDelete(selection.selectedIds);
96
- selection.clearSelection();
97
- },
98
- }))
99
- : undefined,
100
- renderRowActions: (row) => (_jsx(RowActionMenu, { actions: [
101
- { label: ACTIONS.STORE["edit-listing"].label, onClick: () => handleEdit(row.id) },
102
- {
103
- label: ACTIONS.STORE["delete-listing"].label,
104
- destructive: true,
105
- onClick: () => setDeleteTargetId(row.id),
106
- disabled: deletingId === row.id,
107
- },
108
- ] })),
95
+ onClick: handlers[id],
96
+ }));
97
+ },
98
+ renderFilterPanel: ({ pendingFilters, setPendingFilters }) => (_jsxs(_Fragment, { children: [_jsx(FilterChipGroup, { label: "Status", tabs: [
99
+ { id: "All", label: "All" },
100
+ { id: "true", label: "Active" },
101
+ { id: "false", label: "Inactive" },
102
+ ], value: pendingFilters.isActive || "", onChange: (v) => setPendingFilters((p) => ({ ...p, isActive: v })) }), _jsx(FilterChipGroup, { label: "Stock", tabs: [
103
+ { id: "All", label: "All" },
104
+ { id: "out_of_stock", label: "Sold out" },
105
+ ], value: pendingFilters.bundleStockStatus || "", onChange: (v) => setPendingFilters((p) => ({ ...p, bundleStockStatus: v })) })] })),
109
106
  };
110
- return (_jsxs(_Fragment, { children: [_jsx(DataListingView, { config: config }), deleteTargetId && (_jsx(ConfirmDeleteModal, { isOpen: true, title: "Delete Bundle", message: "Are you sure you want to delete this bundle? This cannot be undone.", onConfirm: () => handleDelete(deleteTargetId), onClose: () => setDeleteTargetId(null), isDeleting: deletingId === deleteTargetId }))] }));
107
+ return _jsx(DataListingView, { config: config });
111
108
  }
@@ -23,6 +23,8 @@ export interface SellerProductDraft {
23
23
  pickupAddressId?: string;
24
24
  insurance?: boolean;
25
25
  insuranceCost?: number;
26
+ gstRate?: 0 | 5 | 12 | 18 | 28;
27
+ hsnCode?: string;
26
28
  status?: "draft" | "published";
27
29
  seoTitle?: string;
28
30
  seoDescription?: string;
@@ -5,7 +5,7 @@ import { useRouter } from "next/navigation";
5
5
  import { z } from "zod";
6
6
  import { FormShell, StepForm, StepFormActions, useFormShell } from "../../shell";
7
7
  import { FormShellProvider } from "../../../ui/forms";
8
- import { Alert, Button, Div, FormField, FormGroup, Heading, Section, Stack, Text, Toggle, useToast } from "../../../ui";
8
+ import { Alert, Button, Div, FormField, FormGroup, Heading, Section, Stack, TagInput, Text, Toggle, useToast } from "../../../ui";
9
9
  import { ImageUpload, MediaUploadField, MediaUploadList, useMediaUpload } from "../../media";
10
10
  import { StoreAddressSelectorCreate } from "../../stores/components/StoreAddressSelectorCreate";
11
11
  import { QuickProductForm } from "./QuickProductForm";
@@ -54,7 +54,7 @@ function toPaise(rupeeStr) {
54
54
  }
55
55
  // ── Step 1: Basic ─────────────────────────────────────────────────────────────
56
56
  function StepBasic({ values, onChange, renderCategorySelector, renderBrandSelector, renderTemplateSelector, }) {
57
- return (_jsxs(Stack, { gap: "md", children: [renderTemplateSelector?.({ onApply: onChange }), _jsx(FormField, { name: "title", label: "Title", type: "text", value: values.title ?? "", onChange: (v) => onChange({ title: v }), placeholder: "e.g. Charizard Base Set PSA 9" }), _jsx(FormField, { name: "description", label: "Description", type: "textarea", value: values.description ?? "", onChange: (v) => onChange({ description: v }), placeholder: "Describe your listing in detail\u2026" }), _jsxs(FormGroup, { columns: 2, children: [renderCategorySelector ? (_jsxs(_Fragment, { children: [_jsx(Text, { className: "text-[var(--appkit-color-text)] mb-1", size: "sm", weight: "medium", children: "Category" }), renderCategorySelector({ value: values.category ?? "", onChange: (v) => onChange({ category: v }) })] })) : (_jsx(FormField, { name: "category", label: "Category", type: "text", value: values.category ?? "", onChange: (v) => onChange({ category: v }), placeholder: "e.g. Trading Cards" })), _jsx(FormField, { name: "condition", label: "Condition", type: "select", value: values.condition ?? "new", onChange: (v) => onChange({ condition: v }), options: CONDITION_OPTIONS })] }), renderBrandSelector ? (_jsxs(_Fragment, { children: [_jsx(Text, { className: "text-[var(--appkit-color-text)] mb-1", size: "sm", weight: "medium", children: "Brand" }), renderBrandSelector({ value: values.brand ?? "", onChange: (v) => onChange({ brand: v }) })] })) : (_jsx(FormField, { name: "brand", label: "Brand", type: "text", value: values.brand ?? "", onChange: (v) => onChange({ brand: v }), placeholder: "e.g. Pok\u00E9mon Company" })), _jsx(FormField, { name: "tags", label: "Tags (comma-separated)", type: "text", value: (values.tags ?? []).join(", "), onChange: (v) => onChange({ tags: v.split(",").map((t) => t.trim()).filter(Boolean) }), placeholder: "pokemon, psa9, charizard, holo" }), _jsx(BarcodeField, { value: values.barcodeId ?? "", onChange: (v) => onChange({ barcodeId: v || undefined }), onScan: (v) => onChange({ barcodeId: v }), helperText: "Leave blank to auto-generate. Scan a pre-printed sticker to link it." })] }));
57
+ return (_jsxs(Stack, { gap: "md", children: [renderTemplateSelector?.({ onApply: onChange }), _jsx(FormField, { name: "title", label: "Title", type: "text", value: values.title ?? "", onChange: (v) => onChange({ title: v }), placeholder: "e.g. Charizard Base Set PSA 9" }), _jsx(FormField, { name: "description", label: "Description", type: "textarea", value: values.description ?? "", onChange: (v) => onChange({ description: v }), placeholder: "Describe your listing in detail\u2026" }), _jsxs(FormGroup, { columns: 2, children: [renderCategorySelector ? (_jsxs(_Fragment, { children: [_jsx(Text, { className: "text-[var(--appkit-color-text)] mb-1", size: "sm", weight: "medium", children: "Category" }), renderCategorySelector({ value: values.category ?? "", onChange: (v) => onChange({ category: v }) })] })) : (_jsx(FormField, { name: "category", label: "Category", type: "text", value: values.category ?? "", onChange: (v) => onChange({ category: v }), placeholder: "e.g. Trading Cards" })), _jsx(FormField, { name: "condition", label: "Condition", type: "select", value: values.condition ?? "new", onChange: (v) => onChange({ condition: v }), options: CONDITION_OPTIONS })] }), renderBrandSelector ? (_jsxs(_Fragment, { children: [_jsx(Text, { className: "text-[var(--appkit-color-text)] mb-1", size: "sm", weight: "medium", children: "Brand" }), renderBrandSelector({ value: values.brand ?? "", onChange: (v) => onChange({ brand: v }) })] })) : (_jsx(FormField, { name: "brand", label: "Brand", type: "text", value: values.brand ?? "", onChange: (v) => onChange({ brand: v }), placeholder: "e.g. Pok\u00E9mon Company" })), _jsx(TagInput, { label: "Tags", value: values.tags ?? [], onChange: (tags) => onChange({ tags }), placeholder: "pokemon, psa9, charizard, holo" }), _jsx(BarcodeField, { value: values.barcodeId ?? "", onChange: (v) => onChange({ barcodeId: v || undefined }), onScan: (v) => onChange({ barcodeId: v }), helperText: "Leave blank to auto-generate. Scan a pre-printed sticker to link it." })] }));
58
58
  }
59
59
  // ── Step 2: Media ─────────────────────────────────────────────────────────────
60
60
  function StepMedia({ values, onChange, storeSlug = "store", }) {
@@ -70,7 +70,7 @@ function StepMedia({ values, onChange, storeSlug = "store", }) {
70
70
  name: values.title ?? "product",
71
71
  store: storeSlug,
72
72
  category: values.category ?? "uncategorized",
73
- }), onChange: (url) => onChange({ mainImage: url }), label: "Main Image", helperText: "Recommended: 800\u00D7800px square (JPG, PNG, WebP \u2014 max 10 MB)" }), _jsx(MediaUploadList, { label: "Gallery Images (up to 10)", value: galleryFields, onChange: (fields) => onChange({ images: fields.map((f) => f.url) }), onUpload: (file) => {
73
+ }), onChange: (url) => onChange({ mainImage: url }), label: "Main Image", helperText: "Recommended: 800\u00D7800px square (JPG, PNG, WebP \u2014 max 10 MB)", enableAdvancedCrop: true, cropAspectRatio: 1 }), _jsx(MediaUploadList, { label: "Gallery Images (up to 10)", value: galleryFields, onChange: (fields) => onChange({ images: fields.map((f) => f.url) }), onUpload: (file) => {
74
74
  idxRef.current += 1;
75
75
  return upload(file, "products", true, {
76
76
  type: "product-image",
@@ -126,7 +126,7 @@ const LIVE_TRANSPORT_OPTIONS = [
126
126
  { value: "specialist", label: "Third-party specialist transport" },
127
127
  ];
128
128
  function StepLiveItemSettings({ values, onChange, }) {
129
- return (_jsxs(Stack, { gap: "md", children: [_jsx(Alert, { variant: "warning", children: "Live animal / plant listings must comply with all applicable laws, CITES regulations, and local jurisdiction rules. LetItRip is not responsible for legal compliance \u2014 this is solely the seller's responsibility." }), _jsxs(FormGroup, { columns: 2, children: [_jsx(FormField, { name: "liveSpecies", label: "Species / Common Name", type: "text", value: values.liveSpecies ?? "", onChange: (v) => onChange({ liveSpecies: v }), placeholder: "e.g. Axolotl (Ambystoma mexicanum)" }), _jsx(FormField, { name: "liveAgeMonths", label: "Age (months, optional)", type: "number", value: String(values.liveAgeMonths ?? ""), onChange: (v) => onChange({ liveAgeMonths: v ? Number(v) : undefined }), placeholder: "e.g. 6" })] }), _jsx(FormField, { name: "liveSex", label: "Sex", type: "select", value: values.liveSex ?? "unknown", onChange: (v) => onChange({ liveSex: v }), options: LIVE_SEX_OPTIONS }), _jsx(FormField, { name: "liveCareInfo", label: "Care Instructions (optional)", type: "textarea", value: values.liveCareInfo ?? "", onChange: (v) => onChange({ liveCareInfo: v }), placeholder: "Diet, temperature, habitat requirements\u2026" }), _jsx(FormField, { name: "liveTransportMethod", label: "Transport Method", type: "select", value: values.liveTransportMethod ?? "in-person", onChange: (v) => onChange({ liveTransportMethod: v }), options: LIVE_TRANSPORT_OPTIONS }), values.liveTransportMethod !== "in-person" && (_jsx(FormField, { name: "liveHandlingFee", label: "Handling / Transport Fee (\u20B9, optional)", type: "number", value: toRupees(values.liveHandlingFee), onChange: (v) => onChange({ liveHandlingFee: v ? toPaise(v) : undefined }), placeholder: "0" })), _jsx(FormField, { name: "liveJurisdictions", label: "Jurisdictions where sale is permitted (comma-separated)", type: "text", value: (values.liveJurisdictions ?? []).join(", "), onChange: (v) => onChange({ liveJurisdictions: v.split(",").map((s) => s.trim()).filter(Boolean) }), placeholder: "e.g. Maharashtra, Karnataka", helpText: "List all Indian states / UTs where you can legally sell" }), _jsx(Toggle, { checked: !!values.liveCites, onChange: (checked) => onChange({ liveCites: checked }), label: "This species requires CITES documentation" })] }));
129
+ return (_jsxs(Stack, { gap: "md", children: [_jsx(Alert, { variant: "warning", children: "Live animal / plant listings must comply with all applicable laws, CITES regulations, and local jurisdiction rules. LetItRip is not responsible for legal compliance \u2014 this is solely the seller's responsibility." }), _jsxs(FormGroup, { columns: 2, children: [_jsx(FormField, { name: "liveSpecies", label: "Species / Common Name", type: "text", value: values.liveSpecies ?? "", onChange: (v) => onChange({ liveSpecies: v }), placeholder: "e.g. Axolotl (Ambystoma mexicanum)" }), _jsx(FormField, { name: "liveAgeMonths", label: "Age (months, optional)", type: "number", value: String(values.liveAgeMonths ?? ""), onChange: (v) => onChange({ liveAgeMonths: v ? Number(v) : undefined }), placeholder: "e.g. 6" })] }), _jsx(FormField, { name: "liveSex", label: "Sex", type: "select", value: values.liveSex ?? "unknown", onChange: (v) => onChange({ liveSex: v }), options: LIVE_SEX_OPTIONS }), _jsx(FormField, { name: "liveCareInfo", label: "Care Instructions (optional)", type: "textarea", value: values.liveCareInfo ?? "", onChange: (v) => onChange({ liveCareInfo: v }), placeholder: "Diet, temperature, habitat requirements\u2026" }), _jsx(FormField, { name: "liveTransportMethod", label: "Transport Method", type: "select", value: values.liveTransportMethod ?? "in-person", onChange: (v) => onChange({ liveTransportMethod: v }), options: LIVE_TRANSPORT_OPTIONS }), values.liveTransportMethod !== "in-person" && (_jsx(FormField, { name: "liveHandlingFee", label: "Handling / Transport Fee (\u20B9, optional)", type: "number", value: toRupees(values.liveHandlingFee), onChange: (v) => onChange({ liveHandlingFee: v ? toPaise(v) : undefined }), placeholder: "0" })), _jsx(TagInput, { label: "Jurisdictions where sale is permitted", value: values.liveJurisdictions ?? [], onChange: (liveJurisdictions) => onChange({ liveJurisdictions }), placeholder: "e.g. Maharashtra, Karnataka", helperText: "List all Indian states / UTs where you can legally sell" }), _jsx(Toggle, { checked: !!values.liveCites, onChange: (checked) => onChange({ liveCites: checked }), label: "This species requires CITES documentation" })] }));
130
130
  }
131
131
  // ── Step: Print Meta (art / stickers) ───────────────────────────────────────
132
132
  // Shared by both "art" and "stickers" — identical field set, only the intro
@@ -190,7 +190,13 @@ function StepShipping({ values, onChange, renderAddressSelector, }) {
190
190
  ] }), renderAddressSelector ? (_jsxs(_Fragment, { children: [_jsx(Text, { className: "text-[var(--appkit-color-text)] mb-1", size: "sm", weight: "medium", children: "Pickup Address (optional)" }), renderAddressSelector({
191
191
  value: values.pickupAddressId ?? "",
192
192
  onChange: (v) => onChange({ pickupAddressId: v }),
193
- })] })) : (_jsx(StoreAddressSelectorCreate, { label: "Pickup Address (optional)", value: values.pickupAddressId ?? "", onChange: (id) => onChange({ pickupAddressId: id }) })), _jsx(Toggle, { checked: !!values.insurance, onChange: (checked) => onChange({ insurance: checked, insuranceCost: checked ? values.insuranceCost ?? 0 : undefined }), label: "Offer shipping insurance" }), values.insurance && (_jsx(FormField, { name: "insuranceCost", label: "Insurance Cost (\u20B9)", type: "number", value: toRupees(values.insuranceCost), onChange: (v) => onChange({ insuranceCost: toPaise(v) }), placeholder: "0" }))] }));
193
+ })] })) : (_jsx(StoreAddressSelectorCreate, { label: "Pickup Address (optional)", value: values.pickupAddressId ?? "", onChange: (id) => onChange({ pickupAddressId: id }) })), _jsx(Toggle, { checked: !!values.insurance, onChange: (checked) => onChange({ insurance: checked, insuranceCost: checked ? values.insuranceCost ?? 0 : undefined }), label: "Offer shipping insurance" }), values.insurance && (_jsx(FormField, { name: "insuranceCost", label: "Insurance Cost (\u20B9)", type: "number", value: toRupees(values.insuranceCost), onChange: (v) => onChange({ insuranceCost: toPaise(v) }), placeholder: "0" })), _jsx(FormField, { name: "gstRate", label: "GST Rate", type: "select", value: String(values.gstRate ?? 0), onChange: (v) => onChange({ gstRate: Number(v) }), options: [
194
+ { value: "0", label: "Exempt (0%)" },
195
+ { value: "5", label: "5%" },
196
+ { value: "12", label: "12%" },
197
+ { value: "18", label: "18%" },
198
+ { value: "28", label: "28%" },
199
+ ] }), _jsx(FormField, { name: "hsnCode", label: "HSN Code", value: values.hsnCode ?? "", onChange: (v) => onChange({ hsnCode: v }), placeholder: "e.g. 9503" })] }));
194
200
  }
195
201
  // ── Step: Publish / SEO ───────────────────────────────────────────────────
196
202
  function StepPublish({ values, onChange, }) {
@@ -23,7 +23,7 @@ function isNavItemActive(item, activeHref) {
23
23
  function NavLink({ item, isActive, onClick }) {
24
24
  return (_jsxs(Link, { href: item.href, onClick: onClick, className: `flex items-center gap-[var(--appkit-space-2-5)] rounded-lg px-[var(--appkit-space-3)] py-[var(--appkit-space-2)] text-[0.8125rem] font-medium leading-tight transition-colors ${isActive
25
25
  ? CLS_NAV_ACTIVE
26
- : "text-[var(--appkit-color-text-muted)] hover:bg-zinc-50 hover:bg-[var(--appkit-color-surface-elevated)]/60 hover:text-zinc-800 hover:text-[var(--appkit-color-text-muted)]"}`, children: [item.icon && _jsx(Span, { size: "base", className: "shrink-0 opacity-60", children: item.icon }), _jsx(Span, { align: "end", className: "flex-1 truncate", children: item.label }), item.badge != null && item.badge > 0 && (_jsx(Span, { weight: "bold", className: CLS_NAV_BADGE, children: item.badge }))] }));
26
+ : "text-[var(--appkit-color-text-muted)] hover:bg-zinc-50 hover:bg-[var(--appkit-color-surface-elevated)]/60 hover:text-zinc-800 hover:text-[var(--appkit-color-text-muted)]"}`, children: [item.icon && _jsx(Span, { size: "base", className: "shrink-0 opacity-60", children: item.icon }), _jsx(Span, { className: "flex-1 truncate", children: item.label }), item.badge != null && item.badge > 0 && (_jsx(Span, { weight: "bold", className: CLS_NAV_BADGE, children: item.badge }))] }));
27
27
  }
28
28
  function FlatContent({ items, activeHref, storeName, storeLogoURL, onItemClick, }) {
29
29
  return (_jsxs(_Fragment, { children: [storeName && (_jsxs(Row, { border: "bottom-subtle", gap: "3", padding: "inline", children: [storeLogoURL ? (_jsx(Div, { className: CLS_STORE_AVATAR, children: _jsx(MediaImage, { src: storeLogoURL, alt: storeName, size: "thumbnail" }) })) : (_jsx(Div, { className: CLS_STORE_FALLBACK, children: storeName[0]?.toUpperCase() })), _jsx(Text, { className: CLS_STORE_NAME, children: storeName })] })), _jsx(Nav, { "aria-label": "Store navigation", padding: "y-sm", children: _jsx(Ul, { paddingX: "x-sm", spacing: "2xs", children: items.map((item) => {
@@ -2,7 +2,7 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import React from "react";
4
4
  import { useApiMutation } from "@mohasinac/appkit/client";
5
- import { Button, FieldInput, Modal, Row, Stack, Text, Toggle } from "../../../ui";
5
+ import { Button, FieldInput, Modal, Row, Stack, TagInput, Text, Toggle } from "../../../ui";
6
6
  import { apiClient } from "../../../http";
7
7
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
8
8
  import { ProductInlineSelect } from "../../seller/components/ProductInlineSelect";
@@ -13,7 +13,7 @@ import { ProductInlineSelect } from "../../seller/components/ProductInlineSelect
13
13
  export function ShipmentItemLinkModal({ shipmentId, lotId, itemId, itemTitle, isOpen, onClose, onLinked, }) {
14
14
  const [mode, setMode] = React.useState("create");
15
15
  const [existingProductId, setExistingProductId] = React.useState(null);
16
- const [categorySlugs, setCategorySlugs] = React.useState("");
16
+ const [categorySlugs, setCategorySlugs] = React.useState([]);
17
17
  const [brandSlug, setBrandSlug] = React.useState("");
18
18
  const linkMutation = useApiMutation({
19
19
  successMessage: "Item linked to product",
@@ -22,7 +22,7 @@ export function ShipmentItemLinkModal({ shipmentId, lotId, itemId, itemTitle, is
22
22
  ? { mode: "link", productId: existingProductId }
23
23
  : {
24
24
  mode: "create",
25
- categorySlugs: categorySlugs.split(",").map((s) => s.trim()).filter(Boolean),
25
+ categorySlugs,
26
26
  brandSlug: brandSlug.trim() || undefined,
27
27
  };
28
28
  return apiClient.post(ADMIN_ENDPOINTS.SHIPMENT_LOT_ITEM_LINK(shipmentId, lotId, itemId), body);
@@ -32,6 +32,6 @@ export function ShipmentItemLinkModal({ shipmentId, lotId, itemId, itemTitle, is
32
32
  onClose();
33
33
  },
34
34
  });
35
- const canSubmit = mode === "link" ? !!existingProductId : categorySlugs.trim().length > 0;
36
- return (_jsx(Modal, { isOpen: isOpen, onClose: onClose, title: `Create pre-order link — ${itemTitle}`, size: "md", children: _jsxs(Stack, { gap: "md", children: [_jsx(Text, { variant: "secondary", children: "Links this item to a real product listing. This is a deliberate, one-time action \u2014 nothing links automatically." }), _jsxs(Row, { gap: "sm", align: "center", children: [_jsx(Toggle, { checked: mode === "create", onChange: (checked) => setMode(checked ? "create" : "link"), size: "sm" }), _jsx(Text, { children: mode === "create" ? "Create a new pre-order product" : "Link to an existing product" })] }), mode === "link" ? (_jsx(ProductInlineSelect, { scope: "admin", value: existingProductId, onChange: setExistingProductId, placeholder: "Search products\u2026" })) : (_jsxs(Stack, { gap: "sm", children: [_jsx(FieldInput, { name: "categorySlugs", label: "Category slugs (comma-separated)", required: true, value: categorySlugs, onChange: setCategorySlugs, placeholder: "category-action-figures" }), _jsx(FieldInput, { name: "brandSlug", label: "Brand slug (optional)", value: brandSlug, onChange: setBrandSlug })] })), _jsx(Button, { isLoading: linkMutation.isPending, disabled: !canSubmit, onClick: () => linkMutation.mutate(), children: mode === "create" ? "Create & Link" : "Link" })] }) }));
35
+ const canSubmit = mode === "link" ? !!existingProductId : categorySlugs.length > 0;
36
+ return (_jsx(Modal, { isOpen: isOpen, onClose: onClose, title: `Create pre-order link — ${itemTitle}`, size: "md", children: _jsxs(Stack, { gap: "md", children: [_jsx(Text, { variant: "secondary", children: "Links this item to a real product listing. This is a deliberate, one-time action \u2014 nothing links automatically." }), _jsxs(Row, { gap: "sm", align: "center", children: [_jsx(Toggle, { checked: mode === "create", onChange: (checked) => setMode(checked ? "create" : "link"), size: "sm" }), _jsx(Text, { children: mode === "create" ? "Create a new pre-order product" : "Link to an existing product" })] }), mode === "link" ? (_jsx(ProductInlineSelect, { scope: "admin", value: existingProductId, onChange: setExistingProductId, placeholder: "Search products\u2026" })) : (_jsxs(Stack, { gap: "sm", children: [_jsx(TagInput, { label: "Category slugs", value: categorySlugs, onChange: setCategorySlugs, placeholder: "category-action-figures" }), _jsx(FieldInput, { name: "brandSlug", label: "Brand slug (optional)", value: brandSlug, onChange: setBrandSlug })] })), _jsx(Button, { isLoading: linkMutation.isPending, disabled: !canSubmit, onClick: () => linkMutation.mutate(), children: mode === "create" ? "Create & Link" : "Link" })] }) }));
37
37
  }