@mohasinac/appkit 2.7.50 → 2.7.53

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 (143) hide show
  1. package/dist/_internal/server/jobs/core/__tests__/couponExpiry.test.d.ts +1 -0
  2. package/dist/_internal/server/jobs/core/__tests__/couponExpiry.test.js +65 -0
  3. package/dist/_internal/server/jobs/core/__tests__/listingProcessor.test.d.ts +1 -0
  4. package/dist/_internal/server/jobs/core/__tests__/listingProcessor.test.js +163 -0
  5. package/dist/_internal/server/jobs/core/__tests__/mediaTmpCleanup.test.d.ts +1 -0
  6. package/dist/_internal/server/jobs/core/__tests__/mediaTmpCleanup.test.js +115 -0
  7. package/dist/_internal/server/jobs/core/__tests__/pendingOrderTimeout.test.d.ts +1 -0
  8. package/dist/_internal/server/jobs/core/__tests__/pendingOrderTimeout.test.js +96 -0
  9. package/dist/client.d.ts +29 -6
  10. package/dist/client.js +16 -4
  11. package/dist/constants/api-endpoints.d.ts +42 -0
  12. package/dist/constants/api-endpoints.js +14 -0
  13. package/dist/features/_guide-cls.d.ts +13 -0
  14. package/dist/features/_guide-cls.js +14 -0
  15. package/dist/features/about/components/HelpPageView.js +29 -26
  16. package/dist/features/account/components/BuyerAccountGuideView.d.ts +1 -0
  17. package/dist/features/account/components/BuyerAccountGuideView.js +39 -0
  18. package/dist/features/account/components/BuyerAuctionsGuideView.d.ts +1 -0
  19. package/dist/features/account/components/BuyerAuctionsGuideView.js +38 -0
  20. package/dist/features/account/components/BuyerOrdersGuideView.d.ts +1 -0
  21. package/dist/features/account/components/BuyerOrdersGuideView.js +48 -0
  22. package/dist/features/account/components/BuyerShoppingGuideView.d.ts +1 -0
  23. package/dist/features/account/components/BuyerShoppingGuideView.js +38 -0
  24. package/dist/features/admin/components/AdminAnalyticsGuideView.d.ts +1 -0
  25. package/dist/features/admin/components/AdminAnalyticsGuideView.js +24 -0
  26. package/dist/features/admin/components/AdminBlogEditorView.js +100 -77
  27. package/dist/features/admin/components/AdminBundleEditorView.js +20 -14
  28. package/dist/features/admin/components/AdminBundlesView.d.ts +1 -3
  29. package/dist/features/admin/components/AdminBundlesView.js +173 -52
  30. package/dist/features/admin/components/AdminCatalogGuideView.d.ts +1 -0
  31. package/dist/features/admin/components/AdminCatalogGuideView.js +28 -0
  32. package/dist/features/admin/components/AdminContentGuideView.d.ts +1 -0
  33. package/dist/features/admin/components/AdminContentGuideView.js +36 -0
  34. package/dist/features/admin/components/AdminCouponEditorView.js +1 -1
  35. package/dist/features/admin/components/AdminGuideHubView.d.ts +5 -0
  36. package/dist/features/admin/components/AdminGuideHubView.js +79 -0
  37. package/dist/features/admin/components/AdminOrdersGuideView.d.ts +1 -0
  38. package/dist/features/admin/components/AdminOrdersGuideView.js +44 -0
  39. package/dist/features/admin/components/AdminProductsView.js +8 -3
  40. package/dist/features/admin/components/AdminSiteConfigGuideView.d.ts +1 -0
  41. package/dist/features/admin/components/AdminSiteConfigGuideView.js +21 -0
  42. package/dist/features/admin/components/AdminStoresGuideView.d.ts +1 -0
  43. package/dist/features/admin/components/AdminStoresGuideView.js +32 -0
  44. package/dist/features/admin/components/AdminTeamGuideView.d.ts +1 -0
  45. package/dist/features/admin/components/AdminTeamGuideView.js +33 -0
  46. package/dist/features/admin/components/AdminTrustGuideView.d.ts +1 -0
  47. package/dist/features/admin/components/AdminTrustGuideView.js +40 -0
  48. package/dist/features/admin/components/AdminUsersGuideView.d.ts +1 -0
  49. package/dist/features/admin/components/AdminUsersGuideView.js +38 -0
  50. package/dist/features/categories/components/CategoryBundlesListing.js +3 -33
  51. package/dist/features/categories/components/index.d.ts +0 -2
  52. package/dist/features/categories/components/index.js +0 -1
  53. package/dist/features/events/components/AdminEventEditorView.js +223 -269
  54. package/dist/features/grouped/repository/grouped-listings.repository.d.ts +9 -0
  55. package/dist/features/grouped/repository/grouped-listings.repository.js +12 -0
  56. package/dist/features/media/upload/MediaUploadField.js +1 -1
  57. package/dist/features/pre-orders/components/PreOrdersIndexListing.js +3 -1
  58. package/dist/features/products/components/AuctionsIndexListing.js +3 -1
  59. package/dist/features/products/components/MarketplaceBundleCard.d.ts +21 -0
  60. package/dist/features/products/components/MarketplaceBundleCard.js +56 -0
  61. package/dist/features/products/components/ProductsIndexListing.js +3 -1
  62. package/dist/features/products/components/index.d.ts +2 -4
  63. package/dist/features/products/components/index.js +1 -2
  64. package/dist/features/promotions/schemas/firestore.js +1 -1
  65. package/dist/features/reviews/components/index.d.ts +0 -2
  66. package/dist/features/reviews/components/index.js +0 -1
  67. package/dist/features/seller/components/BrandInlineSelect.js +23 -3
  68. package/dist/features/seller/components/CategoryInlineSelect.js +20 -3
  69. package/dist/features/seller/components/SellerAnalyticsAlertsView.d.ts +6 -0
  70. package/dist/features/seller/components/SellerAnalyticsAlertsView.js +124 -0
  71. package/dist/features/seller/components/SellerAnalyticsView.d.ts +2 -0
  72. package/dist/features/seller/components/SellerAnalyticsView.js +17 -9
  73. package/dist/features/seller/components/SellerBundlesView.d.ts +5 -0
  74. package/dist/features/seller/components/SellerBundlesView.js +104 -0
  75. package/dist/features/seller/components/SellerClassifiedView.d.ts +5 -0
  76. package/dist/features/seller/components/SellerClassifiedView.js +113 -0
  77. package/dist/features/seller/components/SellerDashboardView.js +19 -7
  78. package/dist/features/seller/components/SellerDigitalCodesView.d.ts +5 -0
  79. package/dist/features/seller/components/SellerDigitalCodesView.js +115 -0
  80. package/dist/features/seller/components/SellerGoogleReviewsView.d.ts +14 -0
  81. package/dist/features/seller/components/SellerGoogleReviewsView.js +95 -0
  82. package/dist/features/seller/components/SellerGroupedListingsView.d.ts +6 -0
  83. package/dist/features/seller/components/SellerGroupedListingsView.js +112 -0
  84. package/dist/features/seller/components/SellerLiveView.d.ts +5 -0
  85. package/dist/features/seller/components/SellerLiveView.js +113 -0
  86. package/dist/features/seller/components/SellerOrdersView.js +18 -2
  87. package/dist/features/seller/components/SellerPayoutMethodsView.d.ts +7 -0
  88. package/dist/features/seller/components/SellerPayoutMethodsView.js +120 -0
  89. package/dist/features/seller/components/SellerPayoutSettingsView.js +50 -12
  90. package/dist/features/seller/components/SellerProductShell.d.ts +1 -1
  91. package/dist/features/seller/components/SellerProductShell.js +9 -7
  92. package/dist/features/seller/components/SellerProductsFilterDrawer.js +3 -1
  93. package/dist/features/seller/components/SellerProductsView.js +48 -54
  94. package/dist/features/seller/components/SellerShippingConfigsView.d.ts +7 -0
  95. package/dist/features/seller/components/SellerShippingConfigsView.js +149 -0
  96. package/dist/features/seller/components/SellerShippingView.js +37 -10
  97. package/dist/features/seller/components/SellerStoreCategoriesView.d.ts +9 -0
  98. package/dist/features/seller/components/SellerStoreCategoriesView.js +122 -0
  99. package/dist/features/seller/components/SellerStorefrontView.d.ts +0 -7
  100. package/dist/features/seller/components/SellerStorefrontView.js +26 -29
  101. package/dist/features/seller/components/SellerTemplatesView.d.ts +10 -0
  102. package/dist/features/seller/components/SellerTemplatesView.js +265 -0
  103. package/dist/features/seller/components/index.d.ts +26 -0
  104. package/dist/features/seller/components/index.js +13 -0
  105. package/dist/features/stores/api/[storeSlug]/reviews/route.js +49 -7
  106. package/dist/features/stores/components/StoreCapabilitiesGuideView.d.ts +3 -0
  107. package/dist/features/stores/components/StoreCapabilitiesGuideView.js +101 -0
  108. package/dist/features/stores/components/StoreFinanceGuideView.d.ts +3 -0
  109. package/dist/features/stores/components/StoreFinanceGuideView.js +79 -0
  110. package/dist/features/stores/components/StoreGuideHubView.d.ts +5 -0
  111. package/dist/features/stores/components/StoreGuideHubView.js +89 -0
  112. package/dist/features/stores/components/StoreListingsGuideView.d.ts +3 -0
  113. package/dist/features/stores/components/StoreListingsGuideView.js +151 -0
  114. package/dist/features/stores/components/StoreOrdersGuideView.d.ts +3 -0
  115. package/dist/features/stores/components/StoreOrdersGuideView.js +122 -0
  116. package/dist/features/stores/components/StoreReviewsListing.js +100 -23
  117. package/dist/features/stores/components/StoreSettingsGuideView.d.ts +3 -0
  118. package/dist/features/stores/components/StoreSettingsGuideView.js +56 -0
  119. package/dist/features/stores/components/index.d.ts +12 -1
  120. package/dist/features/stores/components/index.js +6 -1
  121. package/dist/features/stores/hooks/useStores.d.ts +5 -0
  122. package/dist/features/stores/hooks/useStores.js +10 -0
  123. package/dist/features/stores/manifest.js +1 -1
  124. package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +13 -2
  125. package/dist/index.d.ts +54 -9
  126. package/dist/index.js +58 -15
  127. package/dist/next/routing/route-map.d.ts +55 -0
  128. package/dist/next/routing/route-map.js +25 -0
  129. package/dist/providers/db-firebase/__tests__/filter-aliases.test.d.ts +1 -0
  130. package/dist/providers/db-firebase/__tests__/filter-aliases.test.js +93 -0
  131. package/dist/providers/db-firebase/sieve.d.ts +49 -0
  132. package/dist/providers/db-firebase/sieve.js +61 -8
  133. package/dist/repositories/index.d.ts +1 -0
  134. package/dist/repositories/index.js +2 -0
  135. package/dist/tailwind-utilities.css +1 -1
  136. package/dist/ui/components/ListingToolbar.d.ts +8 -1
  137. package/dist/ui/components/ListingToolbar.js +4 -2
  138. package/dist/ui/components/__tests__/BulkActionBar.test.d.ts +1 -0
  139. package/dist/ui/components/__tests__/BulkActionBar.test.js +96 -0
  140. package/dist/ui/components/__tests__/ListingToolbar.test.d.ts +1 -0
  141. package/dist/ui/components/__tests__/ListingToolbar.test.js +125 -0
  142. package/dist/ui/index.d.ts +1 -1
  143. package/package.json +8 -2
@@ -18,13 +18,6 @@ export interface StorefrontDraft {
18
18
  isVacationMode?: boolean;
19
19
  vacationMessage?: string;
20
20
  isPublic?: boolean;
21
- googleReviews?: {
22
- placeId: string;
23
- enabled: boolean;
24
- maxReviews?: number;
25
- minRating?: number;
26
- layout?: "grid" | "carousel";
27
- };
28
21
  }
29
22
  export interface SellerStorefrontViewProps {
30
23
  initialValues?: StorefrontDraft;
@@ -1,34 +1,22 @@
1
1
  "use client";
2
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useCallback, useState } from "react";
4
- import { Section, StackedViewShell } from "../../../ui";
5
- import { Alert, Button, FormField, FormGroup, Heading, Stack, Text, Toggle, } from "../../../ui";
4
+ import { StackedViewShell } from "../../../ui";
5
+ import { Alert, FormField, FormGroup, Heading, Stack, Text, Toggle, } from "../../../ui";
6
6
  import { ImageUpload, useMediaUpload } from "../../media";
7
- import { useFormShell } from "../../shell";
7
+ import { StepForm, useFormShell } from "../../shell";
8
8
  export function SellerStorefrontView({ initialValues, onSave, isLoading = false, storeSlug = "store", }) {
9
9
  const [draft, setDraft] = useState(initialValues ?? {});
10
10
  const [saving, setSaving] = useState(false);
11
11
  const [saved, setSaved] = useState(false);
12
- const { isDirty, markDirty, markClean } = useFormShell();
12
+ const [currentStep, setCurrentStep] = useState(0);
13
+ const { markDirty, markClean } = useFormShell();
13
14
  const { upload } = useMediaUpload();
14
15
  const update = useCallback((partial) => {
15
16
  setDraft((prev) => ({ ...prev, ...partial }));
16
17
  markDirty();
17
18
  setSaved(false);
18
19
  }, [markDirty]);
19
- const updateSocial = (key, value) => {
20
- update({ socialLinks: { ...draft.socialLinks, [key]: value } });
21
- };
22
- const updateGoogleReviews = (patch) => {
23
- update({
24
- googleReviews: {
25
- placeId: draft.googleReviews?.placeId ?? "",
26
- enabled: draft.googleReviews?.enabled ?? false,
27
- ...draft.googleReviews,
28
- ...patch,
29
- },
30
- });
31
- };
32
20
  const handleSave = async () => {
33
21
  setSaving(true);
34
22
  setSaved(false);
@@ -42,17 +30,26 @@ export function SellerStorefrontView({ initialValues, onSave, isLoading = false,
42
30
  }
43
31
  };
44
32
  const busy = saving || isLoading;
33
+ const steps = [
34
+ {
35
+ label: "Store Identity",
36
+ validate: (values) => !values.storeName?.trim() ? "Store name is required" : null,
37
+ render: ({ values, onChange }) => (_jsxs(Stack, { gap: "md", children: [_jsx(Heading, { level: 3, className: "mb-2", children: "Store Identity" }), _jsx(FormField, { name: "storeName", label: "Store Name", type: "text", value: values.storeName ?? "", onChange: (v) => onChange({ storeName: v }), placeholder: "e.g. Pok\u00E9mon Palace", disabled: busy }), _jsx(FormField, { name: "storeCategory", label: "Store Category", type: "text", value: values.storeCategory ?? "", onChange: (v) => onChange({ storeCategory: v }), placeholder: "e.g. Trading Cards, Action Figures", disabled: busy }), _jsx(FormField, { name: "bio", label: "Short Bio (shown in search results)", type: "textarea", value: values.bio ?? "", onChange: (v) => onChange({ bio: v }), placeholder: "One-liner about your store (max 300 chars)", disabled: busy }), _jsx(FormField, { name: "storeDescription", label: "Full Description", type: "textarea", value: values.storeDescription ?? "", onChange: (v) => onChange({ storeDescription: v }), placeholder: "Detailed store description shown on your public store page\u2026", disabled: busy })] })),
38
+ },
39
+ {
40
+ label: "Branding",
41
+ render: ({ values, onChange }) => (_jsxs(Stack, { gap: "md", children: [_jsx(Heading, { level: 3, className: "mb-2", children: "Branding" }), _jsxs(FormGroup, { columns: 2, children: [_jsx(ImageUpload, { label: "Store Logo", currentImage: values.storeLogoURL, onUpload: (file) => upload(file, "stores", true, { type: "store-logo", store: storeSlug }), onChange: (url) => onChange({ storeLogoURL: url }), helperText: "Square image, min 200\u00D7200px" }), _jsx(ImageUpload, { label: "Store Banner", currentImage: values.storeBannerURL, onUpload: (file) => upload(file, "stores", true, { type: "store-banner", store: storeSlug }), onChange: (url) => onChange({ storeBannerURL: url }), helperText: "Recommended: 1200\u00D7300px" })] })] })),
42
+ },
43
+ {
44
+ label: "Policies",
45
+ render: ({ values, onChange }) => (_jsxs(Stack, { gap: "md", children: [_jsx(Heading, { level: 3, className: "mb-2", children: "Policies" }), _jsx(FormField, { name: "returnPolicy", label: "Return Policy", type: "textarea", value: values.returnPolicy ?? "", onChange: (v) => onChange({ returnPolicy: v }), placeholder: "Describe your return policy\u2026", disabled: busy }), _jsx(FormField, { name: "shippingPolicy", label: "Shipping Policy", type: "textarea", value: values.shippingPolicy ?? "", onChange: (v) => onChange({ shippingPolicy: v }), placeholder: "How and when you ship orders\u2026", disabled: busy })] })),
46
+ },
47
+ {
48
+ label: "Contact & Visibility",
49
+ render: ({ values, onChange }) => (_jsxs(Stack, { gap: "md", children: [_jsx(Heading, { level: 3, className: "mb-2", children: "Contact & Social" }), _jsxs(FormGroup, { columns: 2, children: [_jsx(FormField, { name: "website", label: "Website", type: "text", value: values.website ?? "", onChange: (v) => onChange({ website: v }), placeholder: "https://example.com", disabled: busy }), _jsx(FormField, { name: "location", label: "Location", type: "text", value: values.location ?? "", onChange: (v) => onChange({ location: v }), placeholder: "e.g. Mumbai, India", disabled: busy })] }), _jsxs(FormGroup, { columns: 2, children: [_jsx(FormField, { name: "twitter", label: "Twitter / X URL", type: "text", value: values.socialLinks?.twitter ?? "", onChange: (v) => onChange({ socialLinks: { ...values.socialLinks, twitter: v } }), placeholder: "https://twitter.com/...", disabled: busy }), _jsx(FormField, { name: "instagram", label: "Instagram URL", type: "text", value: values.socialLinks?.instagram ?? "", onChange: (v) => onChange({ socialLinks: { ...values.socialLinks, instagram: v } }), placeholder: "https://instagram.com/...", disabled: busy })] }), _jsxs(FormGroup, { columns: 2, children: [_jsx(FormField, { name: "facebook", label: "Facebook URL", type: "text", value: values.socialLinks?.facebook ?? "", onChange: (v) => onChange({ socialLinks: { ...values.socialLinks, facebook: v } }), placeholder: "https://facebook.com/...", disabled: busy }), _jsx(FormField, { name: "linkedin", label: "LinkedIn URL", type: "text", value: values.socialLinks?.linkedin ?? "", onChange: (v) => onChange({ socialLinks: { ...values.socialLinks, linkedin: v } }), placeholder: "https://linkedin.com/...", disabled: busy })] }), _jsx(Heading, { level: 4, className: "mt-4 mb-2", children: "Visibility" }), _jsx(Toggle, { checked: values.isPublic !== false, onChange: (checked) => onChange({ isPublic: checked }), label: "Make store public \u2014 visible to all buyers", disabled: busy }), values.isPublic === false && (_jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: "Your store is hidden. Existing orders are unaffected." })), _jsx(Heading, { level: 4, className: "mt-4 mb-2", children: "Vacation Mode" }), _jsx(Toggle, { checked: !!values.isVacationMode, onChange: (checked) => onChange({ isVacationMode: checked }), label: "Enable vacation mode \u2014 your store will show a notice to buyers", disabled: busy }), values.isVacationMode && (_jsx(FormField, { name: "vacationMessage", label: "Vacation Message", type: "textarea", value: values.vacationMessage ?? "", onChange: (v) => onChange({ vacationMessage: v }), placeholder: "I'm away until\u2026 Orders placed now will ship when I return.", disabled: busy }))] })),
50
+ },
51
+ ];
45
52
  return (_jsx(StackedViewShell, { portal: "seller", title: "Storefront Settings", sections: [
46
- _jsxs(Stack, { gap: "lg", children: [saved && (_jsx(Alert, { variant: "success", children: "Changes saved successfully." })), _jsxs(Section, { children: [_jsx(Heading, { level: 3, className: "mb-4", children: "Store Profile" }), _jsxs(Stack, { gap: "md", children: [_jsx(FormField, { name: "storeName", label: "Store Name", type: "text", value: draft.storeName ?? "", onChange: (v) => update({ storeName: v }), placeholder: "e.g. Pok\u00E9mon Palace", disabled: busy }), _jsx(FormField, { name: "bio", label: "Short Bio (shown in search results)", type: "textarea", value: draft.bio ?? "", onChange: (v) => update({ bio: v }), placeholder: "One-liner about your store (max 300 chars)", disabled: busy }), _jsxs(FormGroup, { columns: 2, children: [_jsx(ImageUpload, { label: "Store Logo", currentImage: draft.storeLogoURL, onUpload: (file) => {
47
- return upload(file, "stores", true, {
48
- type: "store-logo",
49
- store: storeSlug,
50
- });
51
- }, onChange: (url) => update({ storeLogoURL: url }), helperText: "Square image, min 200\u00D7200px" }), _jsx(ImageUpload, { label: "Store Banner", currentImage: draft.storeBannerURL, onUpload: (file) => {
52
- return upload(file, "stores", true, {
53
- type: "store-banner",
54
- store: storeSlug,
55
- });
56
- }, onChange: (url) => update({ storeBannerURL: url }), helperText: "Recommended: 1200\u00D7300px" })] })] })] }), _jsxs(Section, { children: [_jsx(Heading, { level: 3, className: "mb-4", children: "Store Details" }), _jsxs(Stack, { gap: "md", children: [_jsx(FormField, { name: "storeCategory", label: "Store Category", type: "text", value: draft.storeCategory ?? "", onChange: (v) => update({ storeCategory: v }), placeholder: "e.g. Trading Cards, Action Figures", disabled: busy }), _jsx(FormField, { name: "storeDescription", label: "Full Description", type: "textarea", value: draft.storeDescription ?? "", onChange: (v) => update({ storeDescription: v }), placeholder: "Detailed store description shown on your public store page\u2026", disabled: busy })] })] }), _jsxs(Section, { children: [_jsx(Heading, { level: 3, className: "mb-4", children: "Policies" }), _jsxs(Stack, { gap: "md", children: [_jsx(FormField, { name: "returnPolicy", label: "Return Policy", type: "textarea", value: draft.returnPolicy ?? "", onChange: (v) => update({ returnPolicy: v }), placeholder: "Describe your return policy\u2026", disabled: busy }), _jsx(FormField, { name: "shippingPolicy", label: "Shipping Policy", type: "textarea", value: draft.shippingPolicy ?? "", onChange: (v) => update({ shippingPolicy: v }), placeholder: "How and when you ship orders\u2026", disabled: busy })] })] }), _jsxs(Section, { children: [_jsx(Heading, { level: 3, className: "mb-4", children: "Contact & Social" }), _jsxs(Stack, { gap: "md", children: [_jsxs(FormGroup, { columns: 2, children: [_jsx(FormField, { name: "website", label: "Website", type: "text", value: draft.website ?? "", onChange: (v) => update({ website: v }), placeholder: "https://example.com", disabled: busy }), _jsx(FormField, { name: "location", label: "Location", type: "text", value: draft.location ?? "", onChange: (v) => update({ location: v }), placeholder: "e.g. Mumbai, India", disabled: busy })] }), _jsxs(FormGroup, { columns: 2, children: [_jsx(FormField, { name: "twitter", label: "Twitter / X URL", type: "text", value: draft.socialLinks?.twitter ?? "", onChange: (v) => updateSocial("twitter", v), placeholder: "https://twitter.com/...", disabled: busy }), _jsx(FormField, { name: "instagram", label: "Instagram URL", type: "text", value: draft.socialLinks?.instagram ?? "", onChange: (v) => updateSocial("instagram", v), placeholder: "https://instagram.com/...", disabled: busy })] }), _jsxs(FormGroup, { columns: 2, children: [_jsx(FormField, { name: "facebook", label: "Facebook URL", type: "text", value: draft.socialLinks?.facebook ?? "", onChange: (v) => updateSocial("facebook", v), placeholder: "https://facebook.com/...", disabled: busy }), _jsx(FormField, { name: "linkedin", label: "LinkedIn URL", type: "text", value: draft.socialLinks?.linkedin ?? "", onChange: (v) => updateSocial("linkedin", v), placeholder: "https://linkedin.com/...", disabled: busy })] })] })] }), _jsxs(Section, { children: [_jsx(Heading, { level: 3, className: "mb-4", children: "Vacation Mode" }), _jsxs(Stack, { gap: "md", children: [_jsx(Toggle, { checked: !!draft.isVacationMode, onChange: (checked) => update({ isVacationMode: checked }), label: "Enable vacation mode \u2014 your store will show a notice to buyers", disabled: busy }), draft.isVacationMode && (_jsx(FormField, { name: "vacationMessage", label: "Vacation Message", type: "textarea", value: draft.vacationMessage ?? "", onChange: (v) => update({ vacationMessage: v }), placeholder: "I'm away until\u2026 Orders placed now will ship when I return.", disabled: busy }))] })] }), _jsxs(Section, { children: [_jsx(Heading, { level: 3, className: "mb-4", children: "Visibility" }), _jsx(Toggle, { checked: draft.isPublic !== false, onChange: (checked) => update({ isPublic: checked }), label: "Make store public \u2014 visible to all buyers", disabled: busy }), draft.isPublic === false && (_jsx(Text, { className: "mt-2 text-sm text-[var(--appkit-color-text-muted)]", children: "Your store is hidden. Existing orders are unaffected." }))] }), _jsxs(Section, { children: [_jsx(Heading, { level: 3, className: "mb-1", children: "Google Business Reviews" }), _jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)] mb-4", children: "Display real Google reviews on your store About page. Requires a Google Place ID." }), _jsxs(Stack, { gap: "md", children: [_jsx(Toggle, { checked: !!draft.googleReviews?.enabled, onChange: (checked) => updateGoogleReviews({ enabled: checked }), label: "Show Google reviews on my store About page", disabled: busy }), draft.googleReviews?.enabled && (_jsxs(_Fragment, { children: [_jsx(FormField, { name: "googlePlaceId", label: "Google Place ID", type: "text", value: draft.googleReviews.placeId ?? "", onChange: (v) => updateGoogleReviews({ placeId: v }), placeholder: "ChIJ...", helpText: "Find your Place ID at developers.google.com/maps/documentation/places/web-service/place-id", disabled: busy }), _jsxs(FormGroup, { columns: 2, children: [_jsx(FormField, { name: "googleMaxReviews", label: "Max reviews to show", type: "number", value: String(draft.googleReviews.maxReviews ?? 6), onChange: (v) => updateGoogleReviews({ maxReviews: Number(v) }), disabled: busy }), _jsx(FormField, { name: "googleMinRating", label: "Minimum star rating (1\u20135)", type: "number", value: String(draft.googleReviews.minRating ?? 0), onChange: (v) => updateGoogleReviews({ minRating: Number(v) }), disabled: busy })] })] }))] })] }), _jsxs("div", { className: "flex items-center justify-end gap-3 pt-2 border-t border-[var(--appkit-color-border)]", children: [isDirty && !saving && (_jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: "You have unsaved changes." })), _jsx(Button, { variant: "primary", onClick: handleSave, disabled: busy || !isDirty, isLoading: saving, children: "Save Changes" })] })] }, "profile"),
53
+ _jsxs("div", { children: [saved && (_jsx(Alert, { variant: "success", className: "mb-6", children: "Changes saved successfully." })), _jsx(StepForm, { steps: steps, values: draft, onChange: update, onComplete: handleSave, formId: "seller-storefront", currentStep: currentStep, onStepChange: setCurrentStep, completeLabel: "Save Changes", isLoading: busy })] }, "stepform"),
57
54
  ] }));
58
55
  }
@@ -0,0 +1,10 @@
1
+ export interface SellerTemplatesViewProps {
2
+ onDelete?: (id: string) => Promise<void>;
3
+ onBulkDelete?: (ids: string[]) => Promise<void>;
4
+ onDuplicate?: (row: {
5
+ id: string;
6
+ name: string;
7
+ raw: Record<string, unknown>;
8
+ }) => Promise<void>;
9
+ }
10
+ export declare function SellerTemplatesView({ onDelete, onBulkDelete, onDuplicate, }: SellerTemplatesViewProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,265 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState, useCallback, useMemo } from "react";
4
+ import { Plus } from "lucide-react";
5
+ import { useUrlTable } from "../../../react/hooks/useUrlTable";
6
+ import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
7
+ import { BulkActionBar, Button, ConfirmDeleteModal, DataTable, Div, FilterDrawer, Input, ListingToolbar, Pagination, RowActionMenu, Select, SideDrawer, Stack, Text, } from "../../../ui";
8
+ import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
9
+ import { toRecordArray, toStringValue, useSellerListingData, } from "../hooks/useSellerListingData";
10
+ import { TABLE_KEYS } from "../../../constants/table-keys";
11
+ const PAGE_SIZE = 25;
12
+ const DEFAULT_SORT = "name";
13
+ const SORT_OPTIONS = [
14
+ { value: "name", label: "Name A–Z" },
15
+ { value: "-name", label: "Name Z–A" },
16
+ { value: "-createdAt", label: "Newest" },
17
+ ];
18
+ const CONDITION_OPTIONS = [
19
+ { value: "", label: "Any condition" },
20
+ { value: "new", label: "New" },
21
+ { value: "like_new", label: "Like New" },
22
+ { value: "good", label: "Good" },
23
+ { value: "fair", label: "Fair" },
24
+ { value: "used", label: "Used" },
25
+ ];
26
+ const EMPTY_DRAFT = {
27
+ name: "",
28
+ description: "",
29
+ category: "",
30
+ brand: "",
31
+ condition: "",
32
+ };
33
+ const COLUMNS = [
34
+ {
35
+ key: "name",
36
+ header: "Name",
37
+ render: (row) => _jsx(Text, { className: "text-sm font-medium", children: row.name }),
38
+ },
39
+ {
40
+ key: "category",
41
+ header: "Category",
42
+ render: (row) => (_jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: row.category || "—" })),
43
+ },
44
+ {
45
+ key: "brand",
46
+ header: "Brand",
47
+ render: (row) => (_jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: row.brand || "—" })),
48
+ },
49
+ {
50
+ key: "condition",
51
+ header: "Condition",
52
+ render: (row) => row.condition ? (_jsx("span", { className: "inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium bg-zinc-100 text-zinc-600 dark:bg-slate-800 dark:text-slate-400 capitalize", children: row.condition.replace(/_/g, " ") })) : (_jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: "\u2014" })),
53
+ },
54
+ ];
55
+ export function SellerTemplatesView({ onDelete, onBulkDelete, onDuplicate, }) {
56
+ const table = useUrlTable({ defaults: { sort: DEFAULT_SORT } });
57
+ const [searchInput, setSearchInput] = useState(table.get(TABLE_KEYS.QUERY) || "");
58
+ const [filterOpen, setFilterOpen] = useState(false);
59
+ const [pendingCondition, setPendingCondition] = useState(table.get(TABLE_KEYS.CONDITION) || "");
60
+ // Drawer state
61
+ const [drawerOpen, setDrawerOpen] = useState(false);
62
+ const [drawerMode, setDrawerMode] = useState("create");
63
+ const [editingId, setEditingId] = useState(null);
64
+ const [draft, setDraft] = useState(EMPTY_DRAFT);
65
+ const [saving, setSaving] = useState(false);
66
+ const [savingError, setSavingError] = useState(null);
67
+ // Delete state
68
+ const [deleteTargetId, setDeleteTargetId] = useState(null);
69
+ const [deletingId, setDeletingId] = useState(null);
70
+ const [duplicatingId, setDuplicatingId] = useState(null);
71
+ const commitSearch = useCallback(() => {
72
+ table.set(TABLE_KEYS.QUERY, searchInput.trim());
73
+ }, [searchInput, table]);
74
+ const openFilters = useCallback(() => {
75
+ setPendingCondition(table.get(TABLE_KEYS.CONDITION) || "");
76
+ setFilterOpen(true);
77
+ }, [table]);
78
+ const applyFilters = useCallback(() => {
79
+ table.setMany({ [TABLE_KEYS.CONDITION]: pendingCondition });
80
+ setFilterOpen(false);
81
+ }, [pendingCondition, table]);
82
+ const resetAll = useCallback(() => {
83
+ table.setMany({ [TABLE_KEYS.QUERY]: "", [TABLE_KEYS.SORT]: "", [TABLE_KEYS.CONDITION]: "" });
84
+ setSearchInput("");
85
+ }, [table]);
86
+ const q = table.get(TABLE_KEYS.QUERY) || "";
87
+ const conditionFilter = table.get(TABLE_KEYS.CONDITION) || "";
88
+ const sort = table.get(TABLE_KEYS.SORT) || DEFAULT_SORT;
89
+ const currentPage = table.getNumber(TABLE_KEYS.PAGE, 1);
90
+ const hasActiveState = !!q || sort !== DEFAULT_SORT || !!conditionFilter;
91
+ const filterActiveCount = conditionFilter ? 1 : 0;
92
+ // Fetch all templates (server doesn't paginate this endpoint)
93
+ const { rows: allRows, isLoading, errorMessage, refetch } = useSellerListingData({
94
+ queryKey: ["seller", "templates"],
95
+ endpoint: SELLER_ENDPOINTS.TEMPLATES,
96
+ page: 1,
97
+ pageSize: 1000,
98
+ mapRows: (response) => toRecordArray(response.templates).map((item, index) => ({
99
+ id: toStringValue(item.id, `tpl-${index}`),
100
+ raw: item,
101
+ name: String(item.name ?? ""),
102
+ category: String(item.category ?? ""),
103
+ brand: String(item.brand ?? ""),
104
+ condition: String(item.condition ?? ""),
105
+ })),
106
+ });
107
+ // Client-side filter + sort
108
+ const filteredRows = useMemo(() => {
109
+ let result = allRows;
110
+ if (q)
111
+ result = result.filter((r) => r.name.toLowerCase().includes(q.toLowerCase()));
112
+ if (conditionFilter)
113
+ result = result.filter((r) => r.condition === conditionFilter);
114
+ return [...result].sort((a, b) => sort === "-name" ? b.name.localeCompare(a.name) : a.name.localeCompare(b.name));
115
+ }, [allRows, q, conditionFilter, sort]);
116
+ const totalPages = Math.max(1, Math.ceil(filteredRows.length / PAGE_SIZE));
117
+ const safePage = Math.min(Math.max(1, currentPage), totalPages);
118
+ const pageRows = filteredRows.slice((safePage - 1) * PAGE_SIZE, safePage * PAGE_SIZE);
119
+ const selection = useBulkSelection({ items: filteredRows, keyExtractor: (r) => r.id });
120
+ // Drawer helpers
121
+ const openCreate = useCallback(() => {
122
+ setDraft(EMPTY_DRAFT);
123
+ setEditingId(null);
124
+ setDrawerMode("create");
125
+ setSavingError(null);
126
+ setDrawerOpen(true);
127
+ }, []);
128
+ const openEdit = useCallback((row) => {
129
+ setDraft({
130
+ name: row.name,
131
+ description: String(row.raw.description ?? ""),
132
+ category: row.category,
133
+ brand: row.brand,
134
+ condition: row.condition,
135
+ });
136
+ setEditingId(row.id);
137
+ setDrawerMode("edit");
138
+ setSavingError(null);
139
+ setDrawerOpen(true);
140
+ }, []);
141
+ const closeDrawer = useCallback(() => {
142
+ setDrawerOpen(false);
143
+ setEditingId(null);
144
+ setSavingError(null);
145
+ }, []);
146
+ const handleSave = useCallback(async () => {
147
+ if (!draft.name.trim()) {
148
+ setSavingError("Template name is required.");
149
+ return;
150
+ }
151
+ setSaving(true);
152
+ setSavingError(null);
153
+ try {
154
+ const body = {
155
+ name: draft.name.trim(),
156
+ description: draft.description.trim() || undefined,
157
+ category: draft.category.trim() || undefined,
158
+ brand: draft.brand.trim() || undefined,
159
+ condition: draft.condition || undefined,
160
+ };
161
+ const url = drawerMode === "edit" && editingId
162
+ ? SELLER_ENDPOINTS.TEMPLATE_BY_ID(editingId)
163
+ : SELLER_ENDPOINTS.TEMPLATES;
164
+ const method = drawerMode === "edit" ? "PUT" : "POST";
165
+ await fetch(url, {
166
+ method,
167
+ headers: { "Content-Type": "application/json" },
168
+ credentials: "include",
169
+ body: JSON.stringify(body),
170
+ });
171
+ closeDrawer();
172
+ refetch?.();
173
+ }
174
+ catch {
175
+ setSavingError("Failed to save template. Please try again.");
176
+ }
177
+ finally {
178
+ setSaving(false);
179
+ }
180
+ }, [draft, drawerMode, editingId, closeDrawer, refetch]);
181
+ const handleDelete = useCallback(async (id) => {
182
+ setDeletingId(id);
183
+ try {
184
+ if (onDelete) {
185
+ await onDelete(id);
186
+ }
187
+ else {
188
+ await fetch(SELLER_ENDPOINTS.TEMPLATE_BY_ID(id), {
189
+ method: "DELETE",
190
+ credentials: "include",
191
+ });
192
+ }
193
+ refetch?.();
194
+ }
195
+ finally {
196
+ setDeletingId(null);
197
+ setDeleteTargetId(null);
198
+ }
199
+ }, [onDelete, refetch]);
200
+ const handleDuplicate = useCallback(async (row) => {
201
+ setDuplicatingId(row.id);
202
+ try {
203
+ if (onDuplicate) {
204
+ await onDuplicate(row);
205
+ }
206
+ else {
207
+ await fetch(SELLER_ENDPOINTS.TEMPLATES, {
208
+ method: "POST",
209
+ headers: { "Content-Type": "application/json" },
210
+ credentials: "include",
211
+ body: JSON.stringify({
212
+ name: `Copy of ${row.name}`,
213
+ description: String(row.raw.description ?? "") || undefined,
214
+ category: row.category || undefined,
215
+ brand: row.brand || undefined,
216
+ condition: row.condition || undefined,
217
+ }),
218
+ });
219
+ }
220
+ refetch?.();
221
+ }
222
+ finally {
223
+ setDuplicatingId(null);
224
+ }
225
+ }, [onDuplicate, refetch]);
226
+ const bulkActions = [
227
+ {
228
+ id: "bulk-delete",
229
+ label: "Delete selected",
230
+ variant: "danger",
231
+ onClick: async () => {
232
+ if (onBulkDelete) {
233
+ await onBulkDelete(selection.selectedIds);
234
+ }
235
+ else {
236
+ await Promise.all(selection.selectedIds.map((id) => fetch(SELLER_ENDPOINTS.TEMPLATE_BY_ID(id), {
237
+ method: "DELETE",
238
+ credentials: "include",
239
+ })));
240
+ }
241
+ selection.clearSelection();
242
+ refetch?.();
243
+ },
244
+ },
245
+ ];
246
+ return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: filterActiveCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search templates by name...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: sort, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, showTableView: true, view: "table", onViewChange: () => { }, onResetAll: resetAll, hasActiveState: hasActiveState, extra: _jsxs(Button, { size: "sm", onClick: openCreate, className: "flex items-center gap-1.5", children: [_jsx(Plus, { className: "h-4 w-4" }), _jsx("span", { children: "New Template" })] }) }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: safePage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), selection.selectedCount > 0 && (_jsx(BulkActionBar, { selectedCount: selection.selectedCount, actions: bulkActions, onClearSelection: selection.clearSelection })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx(Div, { className: "mb-4 rounded-xl border border-red-200 bg-red-50 dark:bg-red-950/40 dark:border-red-900/60 px-4 py-3 text-sm text-red-700 dark:text-red-200", children: errorMessage })), isLoading ? (_jsx(Div, { className: "space-y-2", children: Array.from({ length: 5 }).map((_, i) => (_jsx(Div, { className: "h-14 animate-pulse rounded-xl border border-zinc-100 dark:border-slate-700 bg-zinc-50 dark:bg-slate-800" }, i))) })) : pageRows.length === 0 ? (_jsx(Div, { className: "py-16 text-center", children: _jsx(Text, { className: "text-zinc-400 dark:text-zinc-500", children: q || conditionFilter
247
+ ? "No templates match your search or filters"
248
+ : "No templates yet — add your first product template" }) })) : (_jsx(DataTable, { columns: COLUMNS, data: pageRows, keyExtractor: (r) => r.id, selectable: bulkActions.length > 0, selectedIds: selection.selectedIds, onSelectionChange: (ids) => selection.setSelectedIds(ids), actions: (row) => (_jsx(RowActionMenu, { actions: [
249
+ {
250
+ label: "Edit",
251
+ onClick: () => openEdit(row),
252
+ },
253
+ {
254
+ label: "Clone",
255
+ disabled: duplicatingId === row.id,
256
+ onClick: () => handleDuplicate(row),
257
+ },
258
+ {
259
+ label: "Delete",
260
+ destructive: true,
261
+ onClick: () => setDeleteTargetId(row.id),
262
+ disabled: deletingId === row.id,
263
+ },
264
+ ] })) }))] }), _jsx(FilterDrawer, { open: filterOpen, onOpen: openFilters, onClose: () => setFilterOpen(false), onApply: applyFilters, onReset: () => setPendingCondition(""), activeCount: filterActiveCount, hideTrigger: true, children: _jsx(Div, { className: "space-y-4 p-4", children: _jsxs(Div, { children: [_jsx("label", { className: "mb-1.5 block text-sm font-medium text-zinc-700 dark:text-zinc-300", children: "Condition" }), _jsx(Select, { value: pendingCondition, onValueChange: setPendingCondition, options: CONDITION_OPTIONS })] }) }) }), _jsx(SideDrawer, { isOpen: drawerOpen, onClose: closeDrawer, title: drawerMode === "create" ? "New Template" : "Edit Template", mode: drawerMode, isDirty: draft.name !== "" || draft.category !== "" || draft.brand !== "", footer: _jsxs("div", { className: "flex items-center justify-end gap-2", children: [_jsx(Button, { size: "sm", variant: "ghost", onClick: closeDrawer, disabled: saving, children: "Cancel" }), _jsx(Button, { size: "sm", isLoading: saving, onClick: handleSave, children: drawerMode === "create" ? "Create Template" : "Save Changes" })] }), children: _jsxs(Stack, { gap: "md", children: [savingError && (_jsx(Div, { className: "rounded-lg border border-red-200 bg-red-50 dark:bg-red-950/40 dark:border-red-900/60 px-3 py-2 text-sm text-red-700 dark:text-red-200", children: savingError })), _jsx(Input, { id: "tpl-name", label: "Template Name", value: draft.name, onChange: (e) => setDraft((d) => ({ ...d, name: e.target.value })), placeholder: "e.g. Pok\u00E9mon Card Standard", required: true, autoComplete: "off" }), _jsx(Input, { id: "tpl-description", label: "Description (optional)", value: draft.description, onChange: (e) => setDraft((d) => ({ ...d, description: e.target.value })), placeholder: "Short note about when to use this template", autoComplete: "off" }), _jsx(Input, { id: "tpl-category", label: "Category (optional)", value: draft.category, onChange: (e) => setDraft((d) => ({ ...d, category: e.target.value })), placeholder: "e.g. trading-cards", autoComplete: "off" }), _jsx(Input, { id: "tpl-brand", label: "Brand (optional)", value: draft.brand, onChange: (e) => setDraft((d) => ({ ...d, brand: e.target.value })), placeholder: "e.g. Pok\u00E9mon Company", autoComplete: "off" }), _jsx(Select, { label: "Condition (optional)", name: "tpl-condition", value: draft.condition, onValueChange: (v) => setDraft((d) => ({ ...d, condition: v })), options: CONDITION_OPTIONS })] }) }), deleteTargetId && (_jsx(ConfirmDeleteModal, { isOpen: true, title: "Delete Template", message: "Are you sure you want to delete this template? This cannot be undone.", onConfirm: () => handleDelete(deleteTargetId), onClose: () => setDeleteTargetId(null), isDeleting: deletingId === deleteTargetId }))] }));
265
+ }
@@ -97,3 +97,29 @@ export { CategoryInlineSelect } from "./CategoryInlineSelect";
97
97
  export type { CategoryInlineSelectProps } from "./CategoryInlineSelect";
98
98
  export { BrandInlineSelect } from "./BrandInlineSelect";
99
99
  export type { BrandInlineSelectProps } from "./BrandInlineSelect";
100
+ export { SellerGroupedListingsView } from "./SellerGroupedListingsView";
101
+ export type { SellerGroupedListingsViewProps } from "./SellerGroupedListingsView";
102
+ export { SellerGroupedListingsView as StoreGroupedListingsView } from "./SellerGroupedListingsView";
103
+ export type { SellerGroupedListingsViewProps as StoreGroupedListingsViewProps } from "./SellerGroupedListingsView";
104
+ export { SellerAnalyticsAlertsView } from "./SellerAnalyticsAlertsView";
105
+ export type { SellerAnalyticsAlertsViewProps } from "./SellerAnalyticsAlertsView";
106
+ export { SellerAnalyticsAlertsView as StoreAnalyticsAlertsView } from "./SellerAnalyticsAlertsView";
107
+ export type { SellerAnalyticsAlertsViewProps as StoreAnalyticsAlertsViewProps } from "./SellerAnalyticsAlertsView";
108
+ export { SellerStoreCategoriesView } from "./SellerStoreCategoriesView";
109
+ export type { SellerStoreCategoriesViewProps } from "./SellerStoreCategoriesView";
110
+ export { SellerTemplatesView } from "./SellerTemplatesView";
111
+ export type { SellerTemplatesViewProps } from "./SellerTemplatesView";
112
+ export { SellerPayoutMethodsView } from "./SellerPayoutMethodsView";
113
+ export type { SellerPayoutMethodsViewProps } from "./SellerPayoutMethodsView";
114
+ export { SellerShippingConfigsView } from "./SellerShippingConfigsView";
115
+ export type { SellerShippingConfigsViewProps } from "./SellerShippingConfigsView";
116
+ export { SellerGoogleReviewsView } from "./SellerGoogleReviewsView";
117
+ export type { SellerGoogleReviewsViewProps } from "./SellerGoogleReviewsView";
118
+ export { SellerBundlesView } from "./SellerBundlesView";
119
+ export type { SellerBundlesViewProps } from "./SellerBundlesView";
120
+ export { SellerClassifiedView } from "./SellerClassifiedView";
121
+ export type { SellerClassifiedViewProps } from "./SellerClassifiedView";
122
+ export { SellerDigitalCodesView } from "./SellerDigitalCodesView";
123
+ export type { SellerDigitalCodesViewProps } from "./SellerDigitalCodesView";
124
+ export { SellerLiveView } from "./SellerLiveView";
125
+ export type { SellerLiveViewProps } from "./SellerLiveView";
@@ -52,3 +52,16 @@ export { SellerAddressesView as StoreAddressesView } from "./SellerAddressesView
52
52
  export { SellerBidsView as StoreBidsView } from "./SellerBidsView";
53
53
  export { CategoryInlineSelect } from "./CategoryInlineSelect";
54
54
  export { BrandInlineSelect } from "./BrandInlineSelect";
55
+ export { SellerGroupedListingsView } from "./SellerGroupedListingsView";
56
+ export { SellerGroupedListingsView as StoreGroupedListingsView } from "./SellerGroupedListingsView";
57
+ export { SellerAnalyticsAlertsView } from "./SellerAnalyticsAlertsView";
58
+ export { SellerAnalyticsAlertsView as StoreAnalyticsAlertsView } from "./SellerAnalyticsAlertsView";
59
+ export { SellerStoreCategoriesView } from "./SellerStoreCategoriesView";
60
+ export { SellerTemplatesView } from "./SellerTemplatesView";
61
+ export { SellerPayoutMethodsView } from "./SellerPayoutMethodsView";
62
+ export { SellerShippingConfigsView } from "./SellerShippingConfigsView";
63
+ export { SellerGoogleReviewsView } from "./SellerGoogleReviewsView";
64
+ export { SellerBundlesView } from "./SellerBundlesView";
65
+ export { SellerClassifiedView } from "./SellerClassifiedView";
66
+ export { SellerDigitalCodesView } from "./SellerDigitalCodesView";
67
+ export { SellerLiveView } from "./SellerLiveView";
@@ -24,6 +24,11 @@ export async function GET(request, context) {
24
24
  const ratingFilter = ratingParam ? Number(ratingParam) : 0;
25
25
  const page = Math.max(1, Number(url.searchParams.get("page") || "1"));
26
26
  const pageSize = Math.min(50, Math.max(1, Number(url.searchParams.get("pageSize") || "12")));
27
+ const sort = url.searchParams.get("sort") || url.searchParams.get("sorts") || "-createdAt";
28
+ const q = (url.searchParams.get("q") || "").trim().toLowerCase();
29
+ const dateFrom = url.searchParams.get("dateFrom") || "";
30
+ const dateTo = url.searchParams.get("dateTo") || "";
31
+ const hasImages = url.searchParams.get("hasImages") === "true";
27
32
  const { db } = getProviders();
28
33
  if (!db) {
29
34
  return NextResponse.json({ success: false, error: "Database provider not registered" }, { status: 503 });
@@ -69,15 +74,52 @@ export async function GET(request, context) {
69
74
  }
70
75
  }
71
76
  const averageRating = totalReviews > 0 ? ratingSum / totalReviews : 0;
72
- // Flatten and sort all reviews by date desc
77
+ // Flatten all reviews
73
78
  const productMap = new Map(products.map((p) => [p.id, p]));
74
- const allSorted = reviewArrays
75
- .flat()
76
- .sort((a, b) => new Date(b.createdAt ?? 0).getTime() - new Date(a.createdAt ?? 0).getTime());
79
+ const allFlat = reviewArrays.flat();
80
+ // Apply sort
81
+ const sorted = [...allFlat].sort((a, b) => {
82
+ if (sort === "createdAt" || sort === "+createdAt") {
83
+ return new Date(a.createdAt ?? 0).getTime() - new Date(b.createdAt ?? 0).getTime();
84
+ }
85
+ if (sort === "-rating" || sort === "rating") {
86
+ return (b.rating ?? 0) - (a.rating ?? 0);
87
+ }
88
+ if (sort === "rating,asc" || sort === "+rating" || sort === "ratingAsc") {
89
+ return (a.rating ?? 0) - (b.rating ?? 0);
90
+ }
91
+ // Default: newest first
92
+ return new Date(b.createdAt ?? 0).getTime() - new Date(a.createdAt ?? 0).getTime();
93
+ });
77
94
  // Apply rating filter
78
- const filtered = ratingFilter > 0
79
- ? allSorted.filter((r) => Math.round(r.rating) === ratingFilter)
80
- : allSorted;
95
+ let filtered = ratingFilter > 0
96
+ ? sorted.filter((r) => Math.round(r.rating) === ratingFilter)
97
+ : sorted;
98
+ // Apply search
99
+ if (q) {
100
+ filtered = filtered.filter((r) => {
101
+ const title = (r.title ?? "").toLowerCase();
102
+ const body = (r.body ?? "").toLowerCase();
103
+ const pt = (r.productTitle ?? "").toLowerCase();
104
+ return title.includes(q) || body.includes(q) || pt.includes(q);
105
+ });
106
+ }
107
+ // Apply date range
108
+ if (dateFrom) {
109
+ const from = new Date(dateFrom).getTime();
110
+ filtered = filtered.filter((r) => new Date(r.createdAt ?? 0).getTime() >= from);
111
+ }
112
+ if (dateTo) {
113
+ const to = new Date(dateTo).getTime() + 86400000; // inclusive end of day
114
+ filtered = filtered.filter((r) => new Date(r.createdAt ?? 0).getTime() <= to);
115
+ }
116
+ // Apply hasImages filter
117
+ if (hasImages) {
118
+ filtered = filtered.filter((r) => {
119
+ const imgs = r.images;
120
+ return Array.isArray(imgs) && imgs.length > 0;
121
+ });
122
+ }
81
123
  const totalFiltered = filtered.length;
82
124
  const totalPages = Math.max(1, Math.ceil(totalFiltered / pageSize));
83
125
  const pageSlice = filtered.slice((page - 1) * pageSize, page * pageSize);
@@ -0,0 +1,3 @@
1
+ export interface StoreCapabilitiesGuideViewProps {
2
+ }
3
+ export declare function StoreCapabilitiesGuideView(_props: StoreCapabilitiesGuideViewProps): import("react/jsx-runtime").JSX.Element;