@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.
- package/dist/_internal/server/jobs/core/__tests__/couponExpiry.test.d.ts +1 -0
- package/dist/_internal/server/jobs/core/__tests__/couponExpiry.test.js +65 -0
- package/dist/_internal/server/jobs/core/__tests__/listingProcessor.test.d.ts +1 -0
- package/dist/_internal/server/jobs/core/__tests__/listingProcessor.test.js +163 -0
- package/dist/_internal/server/jobs/core/__tests__/mediaTmpCleanup.test.d.ts +1 -0
- package/dist/_internal/server/jobs/core/__tests__/mediaTmpCleanup.test.js +115 -0
- package/dist/_internal/server/jobs/core/__tests__/pendingOrderTimeout.test.d.ts +1 -0
- package/dist/_internal/server/jobs/core/__tests__/pendingOrderTimeout.test.js +96 -0
- package/dist/client.d.ts +29 -6
- package/dist/client.js +16 -4
- package/dist/constants/api-endpoints.d.ts +42 -0
- package/dist/constants/api-endpoints.js +14 -0
- package/dist/features/_guide-cls.d.ts +13 -0
- package/dist/features/_guide-cls.js +14 -0
- package/dist/features/about/components/HelpPageView.js +29 -26
- package/dist/features/account/components/BuyerAccountGuideView.d.ts +1 -0
- package/dist/features/account/components/BuyerAccountGuideView.js +39 -0
- package/dist/features/account/components/BuyerAuctionsGuideView.d.ts +1 -0
- package/dist/features/account/components/BuyerAuctionsGuideView.js +38 -0
- package/dist/features/account/components/BuyerOrdersGuideView.d.ts +1 -0
- package/dist/features/account/components/BuyerOrdersGuideView.js +48 -0
- package/dist/features/account/components/BuyerShoppingGuideView.d.ts +1 -0
- package/dist/features/account/components/BuyerShoppingGuideView.js +38 -0
- package/dist/features/admin/components/AdminAnalyticsGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminAnalyticsGuideView.js +24 -0
- package/dist/features/admin/components/AdminBlogEditorView.js +100 -77
- package/dist/features/admin/components/AdminBundleEditorView.js +20 -14
- package/dist/features/admin/components/AdminBundlesView.d.ts +1 -3
- package/dist/features/admin/components/AdminBundlesView.js +173 -52
- package/dist/features/admin/components/AdminCatalogGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminCatalogGuideView.js +28 -0
- package/dist/features/admin/components/AdminContentGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminContentGuideView.js +36 -0
- package/dist/features/admin/components/AdminCouponEditorView.js +1 -1
- package/dist/features/admin/components/AdminGuideHubView.d.ts +5 -0
- package/dist/features/admin/components/AdminGuideHubView.js +79 -0
- package/dist/features/admin/components/AdminOrdersGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminOrdersGuideView.js +44 -0
- package/dist/features/admin/components/AdminProductsView.js +8 -3
- package/dist/features/admin/components/AdminSiteConfigGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminSiteConfigGuideView.js +21 -0
- package/dist/features/admin/components/AdminStoresGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminStoresGuideView.js +32 -0
- package/dist/features/admin/components/AdminTeamGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminTeamGuideView.js +33 -0
- package/dist/features/admin/components/AdminTrustGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminTrustGuideView.js +40 -0
- package/dist/features/admin/components/AdminUsersGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminUsersGuideView.js +38 -0
- package/dist/features/categories/components/CategoryBundlesListing.js +3 -33
- package/dist/features/categories/components/index.d.ts +0 -2
- package/dist/features/categories/components/index.js +0 -1
- package/dist/features/events/components/AdminEventEditorView.js +223 -269
- package/dist/features/grouped/repository/grouped-listings.repository.d.ts +9 -0
- package/dist/features/grouped/repository/grouped-listings.repository.js +12 -0
- package/dist/features/media/upload/MediaUploadField.js +1 -1
- package/dist/features/pre-orders/components/PreOrdersIndexListing.js +3 -1
- package/dist/features/products/components/AuctionsIndexListing.js +3 -1
- package/dist/features/products/components/MarketplaceBundleCard.d.ts +21 -0
- package/dist/features/products/components/MarketplaceBundleCard.js +56 -0
- package/dist/features/products/components/ProductsIndexListing.js +3 -1
- package/dist/features/products/components/index.d.ts +2 -4
- package/dist/features/products/components/index.js +1 -2
- package/dist/features/promotions/schemas/firestore.js +1 -1
- package/dist/features/reviews/components/index.d.ts +0 -2
- package/dist/features/reviews/components/index.js +0 -1
- package/dist/features/seller/components/BrandInlineSelect.js +23 -3
- package/dist/features/seller/components/CategoryInlineSelect.js +20 -3
- package/dist/features/seller/components/SellerAnalyticsAlertsView.d.ts +6 -0
- package/dist/features/seller/components/SellerAnalyticsAlertsView.js +124 -0
- package/dist/features/seller/components/SellerAnalyticsView.d.ts +2 -0
- package/dist/features/seller/components/SellerAnalyticsView.js +17 -9
- package/dist/features/seller/components/SellerBundlesView.d.ts +5 -0
- package/dist/features/seller/components/SellerBundlesView.js +104 -0
- package/dist/features/seller/components/SellerClassifiedView.d.ts +5 -0
- package/dist/features/seller/components/SellerClassifiedView.js +113 -0
- package/dist/features/seller/components/SellerDashboardView.js +19 -7
- package/dist/features/seller/components/SellerDigitalCodesView.d.ts +5 -0
- package/dist/features/seller/components/SellerDigitalCodesView.js +115 -0
- package/dist/features/seller/components/SellerGoogleReviewsView.d.ts +14 -0
- package/dist/features/seller/components/SellerGoogleReviewsView.js +95 -0
- package/dist/features/seller/components/SellerGroupedListingsView.d.ts +6 -0
- package/dist/features/seller/components/SellerGroupedListingsView.js +112 -0
- package/dist/features/seller/components/SellerLiveView.d.ts +5 -0
- package/dist/features/seller/components/SellerLiveView.js +113 -0
- package/dist/features/seller/components/SellerOrdersView.js +18 -2
- package/dist/features/seller/components/SellerPayoutMethodsView.d.ts +7 -0
- package/dist/features/seller/components/SellerPayoutMethodsView.js +120 -0
- package/dist/features/seller/components/SellerPayoutSettingsView.js +50 -12
- package/dist/features/seller/components/SellerProductShell.d.ts +1 -1
- package/dist/features/seller/components/SellerProductShell.js +9 -7
- package/dist/features/seller/components/SellerProductsFilterDrawer.js +3 -1
- package/dist/features/seller/components/SellerProductsView.js +48 -54
- package/dist/features/seller/components/SellerShippingConfigsView.d.ts +7 -0
- package/dist/features/seller/components/SellerShippingConfigsView.js +149 -0
- package/dist/features/seller/components/SellerShippingView.js +37 -10
- package/dist/features/seller/components/SellerStoreCategoriesView.d.ts +9 -0
- package/dist/features/seller/components/SellerStoreCategoriesView.js +122 -0
- package/dist/features/seller/components/SellerStorefrontView.d.ts +0 -7
- package/dist/features/seller/components/SellerStorefrontView.js +26 -29
- package/dist/features/seller/components/SellerTemplatesView.d.ts +10 -0
- package/dist/features/seller/components/SellerTemplatesView.js +265 -0
- package/dist/features/seller/components/index.d.ts +26 -0
- package/dist/features/seller/components/index.js +13 -0
- package/dist/features/stores/api/[storeSlug]/reviews/route.js +49 -7
- package/dist/features/stores/components/StoreCapabilitiesGuideView.d.ts +3 -0
- package/dist/features/stores/components/StoreCapabilitiesGuideView.js +101 -0
- package/dist/features/stores/components/StoreFinanceGuideView.d.ts +3 -0
- package/dist/features/stores/components/StoreFinanceGuideView.js +79 -0
- package/dist/features/stores/components/StoreGuideHubView.d.ts +5 -0
- package/dist/features/stores/components/StoreGuideHubView.js +89 -0
- package/dist/features/stores/components/StoreListingsGuideView.d.ts +3 -0
- package/dist/features/stores/components/StoreListingsGuideView.js +151 -0
- package/dist/features/stores/components/StoreOrdersGuideView.d.ts +3 -0
- package/dist/features/stores/components/StoreOrdersGuideView.js +122 -0
- package/dist/features/stores/components/StoreReviewsListing.js +100 -23
- package/dist/features/stores/components/StoreSettingsGuideView.d.ts +3 -0
- package/dist/features/stores/components/StoreSettingsGuideView.js +56 -0
- package/dist/features/stores/components/index.d.ts +12 -1
- package/dist/features/stores/components/index.js +6 -1
- package/dist/features/stores/hooks/useStores.d.ts +5 -0
- package/dist/features/stores/hooks/useStores.js +10 -0
- package/dist/features/stores/manifest.js +1 -1
- package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +13 -2
- package/dist/index.d.ts +54 -9
- package/dist/index.js +58 -15
- package/dist/next/routing/route-map.d.ts +55 -0
- package/dist/next/routing/route-map.js +25 -0
- package/dist/providers/db-firebase/__tests__/filter-aliases.test.d.ts +1 -0
- package/dist/providers/db-firebase/__tests__/filter-aliases.test.js +93 -0
- package/dist/providers/db-firebase/sieve.d.ts +49 -0
- package/dist/providers/db-firebase/sieve.js +61 -8
- package/dist/repositories/index.d.ts +1 -0
- package/dist/repositories/index.js +2 -0
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/ListingToolbar.d.ts +8 -1
- package/dist/ui/components/ListingToolbar.js +4 -2
- package/dist/ui/components/__tests__/BulkActionBar.test.d.ts +1 -0
- package/dist/ui/components/__tests__/BulkActionBar.test.js +96 -0
- package/dist/ui/components/__tests__/ListingToolbar.test.d.ts +1 -0
- package/dist/ui/components/__tests__/ListingToolbar.test.js +125 -0
- package/dist/ui/index.d.ts +1 -1
- 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
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useCallback, useState } from "react";
|
|
4
|
-
import {
|
|
5
|
-
import { Alert,
|
|
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
|
|
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(
|
|
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
|
|
77
|
+
// Flatten all reviews
|
|
73
78
|
const productMap = new Map(products.map((p) => [p.id, p]));
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
79
|
-
?
|
|
80
|
-
:
|
|
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);
|