@mohasinac/appkit 3.5.4 → 3.5.6

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 (70) hide show
  1. package/dist/_internal/client/features/layout/DashboardLayoutClient.js +24 -3
  2. package/dist/_internal/client/scaffolds/index.d.ts +0 -2
  3. package/dist/_internal/client/scaffolds/index.js +0 -1
  4. package/dist/client-entry.d.ts +2 -2
  5. package/dist/client-entry.js +2 -2
  6. package/dist/features/account/components/UserSidebar.js +1 -1
  7. package/dist/features/admin/components/AdminFeaturesView.js +1 -1
  8. package/dist/features/admin/components/AdminSidebar.js +1 -1
  9. package/dist/features/admin/components/DataTable.js +1 -1
  10. package/dist/features/admin/components/index.d.ts +0 -2
  11. package/dist/features/admin/components/index.js +0 -1
  12. package/dist/features/auctions/components/MarketplaceAuctionCard.js +1 -1
  13. package/dist/features/before-after/components/BeforeAfterSlider.js +4 -0
  14. package/dist/features/blog/components/BlogFeaturedCard.js +1 -1
  15. package/dist/features/categories/components/BundleDynamicRuleEditor.js +2 -9
  16. package/dist/features/events/components/EventCard.js +1 -1
  17. package/dist/features/events/components/EventRafflesSection.d.ts +1 -1
  18. package/dist/features/events/components/EventRafflesSection.js +3 -3
  19. package/dist/features/homepage/components/BeforeAfterCard.js +5 -0
  20. package/dist/features/homepage/components/FeaturedBundlesSection.d.ts +1 -1
  21. package/dist/features/homepage/components/FeaturedBundlesSection.js +3 -1
  22. package/dist/features/homepage/components/GoogleReviewsSection.js +2 -2
  23. package/dist/features/homepage/components/HeroCarousel.js +1 -1
  24. package/dist/features/layout/NavbarLayout.js +1 -1
  25. package/dist/features/layout/TitleBarLayout.js +1 -1
  26. package/dist/features/media/MediaLightbox.js +7 -1
  27. package/dist/features/media/components/MediaSlider.js +4 -1
  28. package/dist/features/media/modals/ImageCropModal.js +1 -1
  29. package/dist/features/media/upload/ImageUpload.js +24 -12
  30. package/dist/features/orders/components/MarketplaceOrderCard.js +1 -1
  31. package/dist/features/pre-orders/components/MarketplacePreorderCard.js +1 -1
  32. package/dist/features/products/components/MarketplaceBundleCard.js +1 -1
  33. package/dist/features/products/components/MarketplacePrizeDrawCard.js +1 -1
  34. package/dist/features/products/components/PrizeDrawsSection.d.ts +1 -1
  35. package/dist/features/products/components/PrizeDrawsSection.js +3 -2
  36. package/dist/features/products/components/ProductForm.js +4 -9
  37. package/dist/features/products/components/ProductGrid.js +1 -1
  38. package/dist/features/promotions/components/CouponCard.js +1 -1
  39. package/dist/features/seller/components/SellerProductShell.js +4 -4
  40. package/dist/features/seller/components/SellerSidebar.js +1 -1
  41. package/dist/features/shipments/components/ShipmentItemLinkModal.js +5 -5
  42. package/dist/features/stores/components/InteractiveStoreCard.js +1 -1
  43. package/dist/index.d.ts +0 -3
  44. package/dist/index.js +0 -3
  45. package/dist/react/hooks/useClickOutside.js +3 -0
  46. package/dist/seed/site-settings-seed-data.js +9 -2
  47. package/dist/server-entry.d.ts +2 -2
  48. package/dist/server-entry.js +1 -1
  49. package/dist/styles.css +11 -4
  50. package/dist/tailwind-utilities.css +1 -1
  51. package/dist/tokens/themes/cobalt-night.d.ts +17 -0
  52. package/dist/tokens/themes/cobalt-night.js +50 -0
  53. package/dist/tokens/themes/index.d.ts +9 -1
  54. package/dist/tokens/themes/index.js +12 -1
  55. package/dist/tokens/themes/sunset.d.ts +18 -0
  56. package/dist/tokens/themes/sunset.js +51 -0
  57. package/dist/tokens/tokens.css +10 -3
  58. package/dist/ui/components/BaseListingCard.d.ts +2 -1
  59. package/dist/ui/components/BaseListingCard.js +4 -4
  60. package/dist/ui/components/Card.d.ts +1 -1
  61. package/dist/ui/components/Card.js +2 -2
  62. package/dist/ui/components/Div.js +2 -2
  63. package/dist/ui/components/Form.d.ts +1 -1
  64. package/dist/ui/components/Form.js +2 -2
  65. package/dist/ui/components/Input.js +1 -0
  66. package/dist/ui/components/ListingToolbar.js +1 -1
  67. package/dist/ui/components/QuickCreateModal.style.css +1 -1
  68. package/dist/ui/components/RichTextEditor.js +28 -24
  69. package/dist/ui/components/Slider.js +5 -1
  70. package/package.json +2 -2
@@ -46,7 +46,7 @@ export function MarketplaceBundleCard({ bundle, className = "", variant = "grid"
46
46
  }
47
47
  router.push(String(detailHref));
48
48
  }, [detailHref, onNavigate, router]);
49
- 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, 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" }) })) }), _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) => {
49
+ 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" }) })) }), _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) => {
50
50
  event.stopPropagation();
51
51
  onSelect(bundle.id, !isSelected);
52
52
  }, className: selectable || isSelected
@@ -89,7 +89,7 @@ export function MarketplacePrizeDrawCard({ product, className = "", variant = "g
89
89
  }
90
90
  router.push(String(detailHref));
91
91
  }, [detailHref, onNavigate, router]);
92
- 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, children: [_jsxs(BaseListingCard.Hero, { aspect: "square", variant: variant, children: [_jsx(TextLink, { href: String(detailHref), className: "absolute inset-0 block", children: thumbItems.length > 0 ? (_jsx(Div, { layout: "grid", className: "grid-cols-2 grid-rows-2 gap-[0.125rem] h-full w-full bg-[var(--appkit-color-surface-muted)]", children: Array.from({ length: 4 }).map((_, i) => {
92
+ 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: thumbItems.length > 0 ? (_jsx(Div, { layout: "grid", className: "grid-cols-2 grid-rows-2 gap-[0.125rem] h-full w-full bg-[var(--appkit-color-surface-muted)]", children: Array.from({ length: 4 }).map((_, i) => {
93
93
  const it = thumbItems[i];
94
94
  const img = it?.images?.[0];
95
95
  return (_jsx(Div, { className: "relative overflow-hidden bg-[var(--appkit-color-surface-muted)]", children: img ? (_jsx(MediaImage, { src: img, alt: it?.title ?? `Prize ${i + 1}`, size: "card" })) : null }, `thumb-${i}`));
@@ -6,4 +6,4 @@ export interface PrizeDrawsSectionProps {
6
6
  * Renders a prize-draws strip on the homepage.
7
7
  * W1-38 (2026-05-23): fetches active prize-draw listings from productRepository.
8
8
  */
9
- export declare function PrizeDrawsSection({ config, }: PrizeDrawsSectionProps): Promise<import("react").JSX.Element>;
9
+ export declare function PrizeDrawsSection({ config, }: PrizeDrawsSectionProps): Promise<import("react").JSX.Element | null>;
@@ -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";
@@ -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
  }
@@ -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
@@ -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
  }
@@ -22,7 +22,7 @@ export function InteractiveStoreCard({ store, href, selectable, isSelected = fal
22
22
  ? "border-[var(--appkit-color-primary,theme(colors.violet.600))] outline outline-2 outline-[var(--appkit-color-primary,theme(colors.violet.600))]"
23
23
  : "border-[var(--appkit-color-border)]",
24
24
  className,
25
- ].join(" "), 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, children: [onSelect && (_jsx(BaseListingCard.Checkbox, { selected: isSelected, onSelect: (e) => {
25
+ ].join(" "), 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: [onSelect && (_jsx(BaseListingCard.Checkbox, { selected: isSelected, onSelect: (e) => {
26
26
  e.preventDefault();
27
27
  onSelect(store.id, !isSelected);
28
28
  }, label: isSelected ? "Deselect store" : "Select store", position: "top-2 left-2", className: selectable || isSelected ? "opacity-100" : "opacity-0 group-hover:opacity-100 transition-opacity" })), _jsxs(Link, { href: href, className: "flex flex-col flex-1 min-h-0", children: [_jsxs(Div, { surface: "muted", className: `relative aspect-video w-full ${__O.hidden} flex-shrink-0`, children: [hasBanner ? (_jsx(MediaImage, { src: store.storeBannerURL, alt: `${store.storeName} banner`, size: "banner", className: "h-full w-full object-cover transition-transform duration-500 group-hover:scale-105" })) : (_jsx(Row, { className: "absolute inset-0", align: "center", justify: "center", children: _jsx(Span, { className: "opacity-20 select-none", size: "5xl", "aria-hidden": "true", children: "\uD83C\uDFEA" }) })), _jsx(Scrim, { direction: "bottom-up", intensity: "subtle", className: "absolute inset-x-0 bottom-0 h-12" }), store.averageRating != null && store.averageRating > 0 && (_jsxs(Div, { className: CLS_RATING_BADGE, children: ["\u2605 ", store.averageRating.toFixed(1)] }))] }), _jsxs(Stack, { paddingY: "b-md", className: "flex-1", padding: "x-md", children: [_jsx(Row, { className: "-mt-5 mb-2", align: "end", justify: "between", children: _jsx(Div, { className: "flex-shrink-0", children: hasLogo ? (_jsx(Div, { className: "relative h-10 w-10 border-2 border-white border-[var(--appkit-color-border-subtle)] bg-[var(--appkit-color-surface)]", rounded: "lg", shadow: "md", overflow: "hidden", children: _jsx(MediaImage, { src: store.storeLogoURL, alt: store.storeName, size: "thumbnail", onError: () => setLogoBroken(true) }) })) : (_jsx(Row, { textWeight: "bold", textSize: "base", className: "h-10 w-10 border-2 border-white bg-primary/10 dark:bg-primary/20 text-primary", align: "center", justify: "center", rounded: "lg", shadow: "md", children: initial })) }) }), _jsx(Heading, { level: 3, className: `group-hover:text-primary transition-colors`, truncate: true, size: "sm", weight: "bold", color: "primary", children: store.storeName }), store.storeDescription ? (_jsx(RichText, { html: normalizeRichTextHtml(store.storeDescription), proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "mt-1 line-clamp-2 text-[length:var(--appkit-text-xs)] text-[var(--appkit-color-text-muted)] flex-1" })) : (_jsx(Div, { className: "flex-1" })), _jsxs(Row, { color: "muted", textSize: "xs", gap: "sm", className: "mt-2.5", wrap: true, children: [store.totalProducts != null && store.totalProducts > 0 && (_jsxs(Span, { layout: "flex", gap: "2xs", children: [_jsx(Span, { "aria-hidden": "true", children: "\uD83D\uDCE6" }), " ", store.totalProducts, " ", labels.products ?? "products"] })), store.itemsSold != null && store.itemsSold > 0 && (_jsxs(Span, { layout: "flex", gap: "2xs", children: [_jsx(Span, { "aria-hidden": "true", children: "\uD83D\uDECD\uFE0F" }), " ", store.itemsSold, " ", labels.sold ?? "sold"] }))] }), _jsxs(Row, { border: "top-subtle", paddingY: "y-xs-tall", className: "mt-3", align: "center", justify: "between", children: [_jsxs(Span, { size: "xs", weight: "semibold", className: "text-primary group-hover:underline transition-colors", children: [labels.visitStore ?? "Visit store", " \u2192"] }), store.totalReviews != null && store.totalReviews > 0 && (_jsxs(Span, { size: "xs", color: "muted", children: [store.totalReviews, " ", labels.reviews ?? "reviews"] }))] })] })] })] }));
package/dist/index.d.ts CHANGED
@@ -1198,7 +1198,6 @@ export { NOTIFICATIONS_COLLECTION } from "./features/admin/index";
1198
1198
  export { NOTIFICATIONS_INDEXED_FIELDS } from "./features/admin/index";
1199
1199
  export { NOTIFICATION_FIELDS } from "./features/admin/index";
1200
1200
  export { NOTIFICATION_PUBLIC_FIELDS } from "./features/admin/index";
1201
- export { QuickActionsPanel } from "./features/admin/index";
1202
1201
  export { SITE_SETTINGS_COLLECTION } from "./features/admin/index";
1203
1202
  export { SITE_SETTINGS_INDEXED_FIELDS } from "./features/admin/index";
1204
1203
  export { SITE_SETTINGS_PUBLIC_FIELDS } from "./features/admin/index";
@@ -1283,8 +1282,6 @@ export type { NotificationPriority } from "./features/admin/index";
1283
1282
  export type { NotificationUpdateInput } from "./features/admin/index";
1284
1283
  export type { SendNotificationInput } from "./features/admin/server";
1285
1284
  export type { SendNotificationResult } from "./features/admin/server";
1286
- export type { QuickActionItem } from "./features/admin/index";
1287
- export type { QuickActionsPanelProps } from "./features/admin/index";
1288
1285
  export type { SiteSettingsCredentials } from "./features/admin/index";
1289
1286
  export type { SiteSettingsCredentialsMasked } from "./features/admin/index";
1290
1287
  export type { SiteSettingsDocument } from "./features/admin/index";
package/dist/index.js CHANGED
@@ -2342,9 +2342,6 @@ export { NOTIFICATION_FIELDS } from "./features/admin/index";
2342
2342
  // [SCHEMA]-Schema / data-shape constant â€" Zod validator, default-value object, or Firestore collection/field name constant.
2343
2343
  // NOTIFICATION_PUBLIC_FIELDS - Constant used across modules.
2344
2344
  export { NOTIFICATION_PUBLIC_FIELDS } from "./features/admin/index";
2345
- // [CLIENT-SSR]-Runs in both SSR and browser â€" React component or hook that does not depend on browser-only APIs.
2346
- // QuickActionsPanel - Shared export for quick actions panel.
2347
- export { QuickActionsPanel } from "./features/admin/index";
2348
2345
  // [SCHEMA]-Schema / data-shape constant â€" Zod validator, default-value object, or Firestore collection/field name constant.
2349
2346
  // SITE_SETTINGS_COLLECTION - Constant used across modules.
2350
2347
  export { SITE_SETTINGS_COLLECTION } from "./features/admin/index";
@@ -46,6 +46,9 @@ export function useClickOutside(ref, callback, options = {}) {
46
46
  }
47
47
  };
48
48
  // Add event listeners for both mouse and touch
49
+ // audit-touch-handler-ok: single-shot "did a touch start outside this
50
+ // element" detection, no gesture-in-progress state to leave stale if the
51
+ // touch is later cancelled.
49
52
  document.addEventListener(eventType, handleClickOutside);
50
53
  document.addEventListener("touchstart", handleClickOutside);
51
54
  return () => {
@@ -907,7 +907,14 @@ export const siteSettingsSeedData = {
907
907
  },
908
908
  },
909
909
  ],
910
- defaultLightThemeId: "crimson-warrior",
911
- defaultDarkThemeId: "shadow-abyss",
910
+ // Reverted 2026-08-15 — the live site had drifted onto the "Teal Tide" /
911
+ // "Teal Depths" custom pair (added in a prior session's theme-experiment
912
+ // sweep), which is not the brand's cobalt+lime/hot-pink identity. The
913
+ // custom themes above stay available as selectable options (nothing
914
+ // deleted); this just restores the appkit built-ins as what's live by
915
+ // default. buildThemeRegistry() always resolves "default-light"/
916
+ // "default-dark" to the true built-in records regardless of this list.
917
+ defaultLightThemeId: "default-light",
918
+ defaultDarkThemeId: "default-dark",
912
919
  },
913
920
  };
@@ -70,8 +70,8 @@ export { processRefundAction, type ProcessRefundInput, } from "./_internal/serve
70
70
  export { createPaymentIntentAction, verifyPaymentSignatureAction, resolvePaymentFee, PAYMENTS_DEFAULT_PLATFORM_FEE_PERCENT, PAYMENTS_DEFAULT_GST_PERCENT, PAYMENTS_RECEIPT_PREFIX, type CreatePaymentIntentInput, type CreatePaymentIntentResult, type VerifyPaymentSignatureInput, type ResolvedPaymentFee, } from "./_internal/server/features/payments/index";
71
71
  export { getSublistingCategoryForDetail } from "./_internal/server/features/sublisting-categories/index";
72
72
  export { getStoreForDetail, listStoreProductsInitial, listStoreAuctionsInitial, listStorePreOrdersInitial, listSitemapStores, STORES_PAGE_SIZE, STORES_PRODUCTS_PAGE_SIZE, STORES_SITEMAP_LIMIT, STORES_FEATURED_LIMIT, } from "./_internal/server/features/stores/index";
73
- export { AppShell, DashboardScaffold } from "./_internal/client/scaffolds/index";
74
- export type { AppShellProps, AppShellRenderContext, DashboardScaffoldProps, DashboardScaffoldRenderContext, } from "./_internal/client/scaffolds/index";
73
+ export { AppShell } from "./_internal/client/scaffolds/index";
74
+ export type { AppShellProps, AppShellRenderContext, } from "./_internal/client/scaffolds/index";
75
75
  export { toClient, clientInitial } from "./_internal/shared/serialization/index";
76
76
  export * as accountServer from "./_internal/server/features/account/index";
77
77
  export * as authServer from "./_internal/server/features/auth/index";
@@ -108,7 +108,7 @@ export { getStoreForDetail, listStoreProductsInitial, listStoreAuctionsInitial,
108
108
  // Next.js lambdas. Consumers that need them (i.e. `functions/src/index.ts`)
109
109
  // import directly from `@mohasinac/appkit/jobs`.
110
110
  // S6: client scaffolds (server-side type-aware import; runtime is "use client")
111
- export { AppShell, DashboardScaffold } from "./_internal/client/scaffolds/index";
111
+ export { AppShell } from "./_internal/client/scaffolds/index";
112
112
  // CC-3: hydration helpers
113
113
  export { toClient, clientInitial } from "./_internal/shared/serialization/index";
114
114
  // S7 server feature shims (data + actions) — canonical _internal/ entries