@mohasinac/appkit 2.7.58 → 2.7.59
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/features/scams/og.js +1 -1
- package/dist/_internal/shared/actions/bulk-helpers.d.ts +3 -0
- package/dist/_internal/shared/actions/bulk-helpers.js +19 -0
- package/dist/_internal/shared/config/schema.d.ts +4 -4
- package/dist/client.d.ts +3 -0
- package/dist/client.js +2 -0
- package/dist/configs/next.js +11 -0
- package/dist/features/account/components/AddressesIndexListing.js +3 -4
- package/dist/features/account/components/UserSidebar.js +14 -10
- package/dist/features/admin/components/AdminAllEventEntriesView.js +3 -9
- package/dist/features/admin/components/AdminBidsView.js +2 -8
- package/dist/features/admin/components/AdminBlogView.js +4 -9
- package/dist/features/admin/components/AdminBrandEditorView.js +14 -17
- package/dist/features/admin/components/AdminBrandsView.js +4 -9
- package/dist/features/admin/components/AdminBundlesView.js +39 -58
- package/dist/features/admin/components/AdminCarouselView.js +7 -20
- package/dist/features/admin/components/AdminCartsView.js +3 -4
- package/dist/features/admin/components/AdminCategoriesView.js +4 -9
- package/dist/features/admin/components/AdminCategoryEditorView.js +1 -5
- package/dist/features/admin/components/AdminContactView.js +6 -24
- package/dist/features/admin/components/AdminCouponEditorView.js +14 -17
- package/dist/features/admin/components/AdminCouponsView.js +8 -16
- package/dist/features/admin/components/AdminFaqEditorView.js +14 -17
- package/dist/features/admin/components/AdminFaqsView.js +18 -69
- package/dist/features/admin/components/AdminFeatureEditorView.js +7 -9
- package/dist/features/admin/components/AdminNewsletterView.js +4 -14
- package/dist/features/admin/components/AdminNotificationsView.js +2 -8
- package/dist/features/admin/components/AdminOrdersView.js +2 -8
- package/dist/features/admin/components/AdminPayoutsView.js +2 -8
- package/dist/features/admin/components/AdminPrizeDrawsView.js +4 -9
- package/dist/features/admin/components/AdminProductEditorView.js +1 -5
- package/dist/features/admin/components/AdminProductsView.js +4 -9
- package/dist/features/admin/components/AdminReviewsView.js +2 -8
- package/dist/features/admin/components/AdminScammersView.js +2 -8
- package/dist/features/admin/components/AdminSessionsView.js +2 -8
- package/dist/features/admin/components/AdminStoresView.js +2 -8
- package/dist/features/admin/components/AdminSublistingCategoriesView.js +2 -6
- package/dist/features/admin/components/AdminSublistingCategoryEditorView.js +17 -20
- package/dist/features/admin/components/AdminSupportTicketsView.js +2 -8
- package/dist/features/admin/components/AdminTeamView.js +5 -14
- package/dist/features/admin/components/AdminUsersView.js +2 -8
- package/dist/features/admin/hooks/useAdminListing.d.ts +58 -0
- package/dist/features/admin/hooks/useAdminListing.js +71 -0
- package/dist/features/auth/schemas/firestore.d.ts +1 -0
- package/dist/features/blog/components/BlogIndexListing.js +3 -6
- package/dist/features/categories/components/CategoriesIndexListing.js +3 -6
- package/dist/features/events/components/AdminEventsView.js +4 -4
- package/dist/features/events/components/EventsIndexListing.js +3 -4
- package/dist/features/homepage/components/AnnouncementBar.d.ts +4 -1
- package/dist/features/homepage/components/AnnouncementBar.js +51 -4
- package/dist/features/homepage/components/MarketplaceHomepageView.d.ts +3 -1
- package/dist/features/homepage/components/MarketplaceHomepageView.js +2 -2
- package/dist/features/homepage/components/index.d.ts +1 -1
- package/dist/features/homepage/components/index.js +1 -1
- package/dist/features/homepage/lib/section-renderer.d.ts +2 -2
- package/dist/features/homepage/lib/section-renderer.js +2 -2
- package/dist/features/orders/repository/orders.repository.d.ts +0 -1
- package/dist/features/orders/repository/orders.repository.js +0 -1
- package/dist/features/orders/server.d.ts +1 -1
- package/dist/features/orders/server.js +1 -1
- package/dist/features/products/components/GroupSettingsPanel.js +64 -48
- package/dist/features/products/repository/products.repository.d.ts +0 -2
- package/dist/features/products/repository/products.repository.js +0 -2
- package/dist/features/products/server.d.ts +1 -1
- package/dist/features/products/server.js +1 -1
- package/dist/features/promotions/components/CouponCard.js +5 -5
- package/dist/features/promotions/components/CouponsIndexListing.js +3 -2
- package/dist/features/reviews/components/ReviewsIndexListing.js +3 -4
- package/dist/features/reviews/repository/reviews.repository.d.ts +0 -1
- package/dist/features/reviews/repository/reviews.repository.js +0 -1
- package/dist/features/reviews/server.d.ts +1 -1
- package/dist/features/reviews/server.js +1 -1
- package/dist/features/seller/components/SellerAddressesView.js +10 -20
- package/dist/features/seller/components/SellerAuctionsView.js +13 -22
- package/dist/features/seller/components/SellerBidsView.js +5 -5
- package/dist/features/seller/components/SellerBundlesView.js +11 -19
- package/dist/features/seller/components/SellerClassifiedView.js +11 -19
- package/dist/features/seller/components/SellerCouponsView.js +14 -20
- package/dist/features/seller/components/SellerDigitalCodesView.js +11 -19
- package/dist/features/seller/components/SellerGroupedListingsView.js +6 -10
- package/dist/features/seller/components/SellerLiveView.js +11 -19
- package/dist/features/seller/components/SellerOffersView.js +3 -4
- package/dist/features/seller/components/SellerOrdersView.js +8 -22
- package/dist/features/seller/components/SellerPayoutMethodsView.js +11 -23
- package/dist/features/seller/components/SellerPayoutsView.js +3 -4
- package/dist/features/seller/components/SellerPreOrdersView.js +13 -22
- package/dist/features/seller/components/SellerPrizeDrawsView.js +13 -22
- package/dist/features/seller/components/SellerProductsFilterDrawer.d.ts +1 -1
- package/dist/features/seller/components/SellerProductsFilterDrawer.js +3 -6
- package/dist/features/seller/components/SellerProductsView.js +9 -22
- package/dist/features/seller/components/SellerShippingConfigsView.js +11 -23
- package/dist/features/seller/components/SellerStoreCategoriesView.js +14 -33
- package/dist/features/seller/components/SellerTemplatesView.js +25 -41
- package/dist/features/stores/components/StoreReviewsListing.js +3 -4
- package/dist/features/stores/components/StoresIndexListing.js +3 -4
- package/dist/index.d.ts +2 -6
- package/dist/index.js +3 -12
- package/dist/react/contexts/SessionContext.d.ts +2 -0
- package/dist/react/contexts/SessionContext.js +2 -0
- package/dist/react/hooks/useEntityDelete.d.ts +15 -0
- package/dist/react/hooks/useEntityDelete.js +39 -0
- package/dist/react/index.d.ts +2 -0
- package/dist/react/index.js +2 -0
- package/dist/repositories/index.d.ts +3 -3
- package/dist/repositories/index.js +3 -3
- package/dist/styles.css +1 -1
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/ListingFilterDrawer.d.ts +10 -0
- package/dist/ui/components/ListingFilterDrawer.js +8 -0
- package/dist/ui/index.d.ts +2 -0
- package/dist/ui/index.js +1 -0
- package/package.json +1 -1
|
@@ -1,10 +1,57 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useState } from "react";
|
|
3
|
+
import { useState, useEffect } from "react";
|
|
4
4
|
import { X } from "lucide-react";
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
import { useAuth } from "../../../react/contexts/SessionContext";
|
|
6
|
+
const STORAGE_KEY = "letitrip:announcement-dismissed";
|
|
7
|
+
const ONE_WEEK_MS = 7 * 24 * 60 * 60 * 1000;
|
|
8
|
+
export function hashBannerMessage(msg) {
|
|
9
|
+
let h = 0;
|
|
10
|
+
for (let i = 0; i < msg.length; i++) {
|
|
11
|
+
h = (Math.imul(31, h) + msg.charCodeAt(i)) | 0;
|
|
12
|
+
}
|
|
13
|
+
return h.toString(36);
|
|
14
|
+
}
|
|
15
|
+
function isLocallyDismissed(message) {
|
|
16
|
+
try {
|
|
17
|
+
const raw = localStorage.getItem(STORAGE_KEY);
|
|
18
|
+
if (!raw)
|
|
19
|
+
return false;
|
|
20
|
+
const { hash, at } = JSON.parse(raw);
|
|
21
|
+
if (hash !== hashBannerMessage(message))
|
|
22
|
+
return false;
|
|
23
|
+
return Date.now() - at < ONE_WEEK_MS;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function saveLocalDismissal(message) {
|
|
30
|
+
try {
|
|
31
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify({ hash: hashBannerMessage(message), at: Date.now() }));
|
|
32
|
+
}
|
|
33
|
+
catch { /* quota exceeded — ignore */ }
|
|
34
|
+
}
|
|
35
|
+
export function AnnouncementBar({ message, className = "", onDismiss }) {
|
|
36
|
+
const { user } = useAuth();
|
|
37
|
+
const bannerHash = hashBannerMessage(message);
|
|
38
|
+
const userAlreadyDismissed = !!user?.dismissedBannerHash && user.dismissedBannerHash === bannerHash;
|
|
39
|
+
const [dismissed, setDismissed] = useState(userAlreadyDismissed);
|
|
40
|
+
const [mounted, setMounted] = useState(userAlreadyDismissed);
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (userAlreadyDismissed) {
|
|
43
|
+
setDismissed(true);
|
|
44
|
+
}
|
|
45
|
+
else if (isLocallyDismissed(message)) {
|
|
46
|
+
setDismissed(true);
|
|
47
|
+
}
|
|
48
|
+
setMounted(true);
|
|
49
|
+
}, [message, userAlreadyDismissed]);
|
|
7
50
|
if (dismissed)
|
|
8
51
|
return null;
|
|
9
|
-
return (_jsx("div", { className: `relative left-1/2 right-1/2 w-screen -translate-x-1/2 bg-primary-700 ${className}`, role: "banner", "data-section": "announcementbar-div-310", children: _jsx("div", { className: "container mx-auto max-w-[1920px] px-4 sm:px-6 lg:px-8", "data-section": "announcementbar-div-311", children: _jsxs("div", { className: "relative flex items-center justify-center py-2 text-center text-sm font-medium text-white", "data-section": "announcementbar-div-312", children: [_jsx("span", { children: message }), _jsx("button", { type: "button", onClick: () =>
|
|
52
|
+
return (_jsx("div", { className: `relative left-1/2 right-1/2 w-screen -translate-x-1/2 bg-primary-700 transition-opacity duration-200 ${mounted ? "opacity-100" : "opacity-0"} ${className}`, role: "banner", "data-section": "announcementbar-div-310", children: _jsx("div", { className: "container mx-auto max-w-[1920px] px-4 sm:px-6 lg:px-8", "data-section": "announcementbar-div-311", children: _jsxs("div", { className: "relative flex items-center justify-center py-2 text-center text-sm font-medium text-white", "data-section": "announcementbar-div-312", children: [_jsx("span", { children: message }), _jsx("button", { type: "button", onClick: () => {
|
|
53
|
+
saveLocalDismissal(message);
|
|
54
|
+
onDismiss?.(bannerHash);
|
|
55
|
+
setDismissed(true);
|
|
56
|
+
}, className: "absolute right-0 top-1/2 -translate-y-1/2 rounded p-0.5 hover:bg-white/20 focus:outline-none focus:ring-2 focus:ring-white", "aria-label": "Dismiss announcement", children: _jsx(X, { className: "h-4 w-4" }) })] }) }) }));
|
|
10
57
|
}
|
|
@@ -3,5 +3,7 @@ export type { MarketplaceHomepageViewAdSlots } from "../lib/section-renderer";
|
|
|
3
3
|
export interface MarketplaceHomepageViewProps {
|
|
4
4
|
adSlots?: import("../lib/section-renderer").MarketplaceHomepageViewAdSlots;
|
|
5
5
|
newsletterFormSlot?: React.ReactNode;
|
|
6
|
+
/** Callback when user dismisses announcement banner — wire to a server action to persist. */
|
|
7
|
+
onBannerDismiss?: (hash: string) => void;
|
|
6
8
|
}
|
|
7
|
-
export declare function MarketplaceHomepageView({ adSlots, newsletterFormSlot, }?: MarketplaceHomepageViewProps): Promise<import("react/jsx-runtime").JSX.Element>;
|
|
9
|
+
export declare function MarketplaceHomepageView({ adSlots, newsletterFormSlot, onBannerDismiss, }?: MarketplaceHomepageViewProps): Promise<import("react/jsx-runtime").JSX.Element>;
|
|
@@ -23,7 +23,7 @@ function toCarouselSlides(docs) {
|
|
|
23
23
|
updatedAt: updatedAt instanceof Date ? updatedAt.toISOString() : undefined,
|
|
24
24
|
}));
|
|
25
25
|
}
|
|
26
|
-
export async function MarketplaceHomepageView({ adSlots, newsletterFormSlot, } = {}) {
|
|
26
|
+
export async function MarketplaceHomepageView({ adSlots, newsletterFormSlot, onBannerDismiss, } = {}) {
|
|
27
27
|
const slides = await carouselRepository.getActiveSlides().catch(() => []);
|
|
28
28
|
const siteSettings = await siteSettingsRepository.getSingleton().catch(() => null);
|
|
29
29
|
const announcementMessage = siteSettings?.announcementBar?.message?.trim() ||
|
|
@@ -113,5 +113,5 @@ export async function MarketplaceHomepageView({ adSlots, newsletterFormSlot, } =
|
|
|
113
113
|
: undefined,
|
|
114
114
|
};
|
|
115
115
|
const carouselSlides = toCarouselSlides(slides);
|
|
116
|
-
return (_jsxs(Main, { children: [showAnnouncement ? _jsx(AnnouncementBar, { message: announcementMessage }) : null, orderedSections.map((section) => renderSection(section, adSlots, newsletterFormSlot ?? null, faqItems, carouselSlides, liveStats, sectionData))] }));
|
|
116
|
+
return (_jsxs(Main, { children: [showAnnouncement ? _jsx(AnnouncementBar, { message: announcementMessage, onDismiss: onBannerDismiss }) : null, orderedSections.map((section) => renderSection(section, adSlots, newsletterFormSlot ?? null, faqItems, carouselSlides, liveStats, sectionData))] }));
|
|
117
117
|
}
|
|
@@ -53,7 +53,7 @@ export { FeaturedResultsSection } from "./FeaturedResultsSection";
|
|
|
53
53
|
export type { HowItWorksInfoStep, HowItWorksInfoDetail, HowItWorksInfoViewProps, } from "./HowItWorksInfoView";
|
|
54
54
|
export { HowItWorksInfoView } from "./HowItWorksInfoView";
|
|
55
55
|
export type { AnnouncementBarProps } from "./AnnouncementBar";
|
|
56
|
-
export { AnnouncementBar } from "./AnnouncementBar";
|
|
56
|
+
export { AnnouncementBar, hashBannerMessage } from "./AnnouncementBar";
|
|
57
57
|
export type { ShopByCategorySectionProps } from "./ShopByCategorySection";
|
|
58
58
|
export { ShopByCategorySection } from "./ShopByCategorySection";
|
|
59
59
|
export type { FeaturedProductsSectionProps } from "./FeaturedProductsSection";
|
|
@@ -26,7 +26,7 @@ export { WhatsAppCommunitySection } from "./WhatsAppCommunitySection";
|
|
|
26
26
|
export { HomepageSkeleton } from "./HomepageSkeleton";
|
|
27
27
|
export { FeaturedResultsSection } from "./FeaturedResultsSection";
|
|
28
28
|
export { HowItWorksInfoView } from "./HowItWorksInfoView";
|
|
29
|
-
export { AnnouncementBar } from "./AnnouncementBar";
|
|
29
|
+
export { AnnouncementBar, hashBannerMessage } from "./AnnouncementBar";
|
|
30
30
|
export { ShopByCategorySection } from "./ShopByCategorySection";
|
|
31
31
|
export { FeaturedProductsSection } from "./FeaturedProductsSection";
|
|
32
32
|
export { FeaturedAuctionsSection } from "./FeaturedAuctionsSection";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import type { LiveStatsMap } from "./live-stats";
|
|
3
|
-
import { AnnouncementBar } from "../components/AnnouncementBar";
|
|
3
|
+
import { AnnouncementBar, hashBannerMessage } from "../components/AnnouncementBar";
|
|
4
4
|
import type { CarouselSlide } from "../types/index";
|
|
5
5
|
import type { HomepageSectionDocument } from "../schemas";
|
|
6
6
|
import type { ProductItem } from "../../products/types";
|
|
@@ -33,4 +33,4 @@ export type FaqItem = {
|
|
|
33
33
|
category: string;
|
|
34
34
|
};
|
|
35
35
|
export declare function renderSection(section: HomepageSectionDocument, adSlots: MarketplaceHomepageViewAdSlots | undefined, newsletterFormSlot: React.ReactNode, faqItems: FaqItem[], slides: CarouselSlide[], liveStats: LiveStatsMap, sectionData?: SectionData): React.ReactNode;
|
|
36
|
-
export { AnnouncementBar };
|
|
36
|
+
export { AnnouncementBar, hashBannerMessage };
|
|
@@ -3,7 +3,7 @@ import React from "react";
|
|
|
3
3
|
import { ROUTES } from "../../../next";
|
|
4
4
|
import { cleanTitle, parseWelcomeDescription } from "./section-helpers";
|
|
5
5
|
import { DEFAULT_TRUST_FEATURES, DEFAULT_SECURITY_ITEMS } from "./section-defaults";
|
|
6
|
-
import { AnnouncementBar } from "../components/AnnouncementBar";
|
|
6
|
+
import { AnnouncementBar, hashBannerMessage } from "../components/AnnouncementBar";
|
|
7
7
|
import { HeroCarousel } from "../components/HeroCarousel";
|
|
8
8
|
import { StatsCounterSection } from "../components/StatsCounterSection";
|
|
9
9
|
import { TrustFeaturesSection } from "../components/TrustFeaturesSection";
|
|
@@ -202,4 +202,4 @@ export function renderSection(section, adSlots, newsletterFormSlot, faqItems, sl
|
|
|
202
202
|
const adSlotKey = AD_SLOT_MAP[section.type];
|
|
203
203
|
return (_jsxs(React.Fragment, { children: [sectionElement, adSlots && adSlotKey !== undefined && adSlotKey in adSlots && adSlots[adSlotKey]] }, section.id));
|
|
204
204
|
}
|
|
205
|
-
export { AnnouncementBar };
|
|
205
|
+
export { AnnouncementBar, hashBannerMessage };
|
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
* Server-only entry point for order repositories.
|
|
5
5
|
*/
|
|
6
6
|
export * from "./actions";
|
|
7
|
-
export { OrderRepository,
|
|
7
|
+
export { OrderRepository, orderRepository, } from "./repository/orders.repository";
|
|
8
8
|
export { GET as getOrderTrackingHandler } from "./api/track/[trackingId]/route";
|
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
* Server-only entry point for order repositories.
|
|
5
5
|
*/
|
|
6
6
|
export * from "./actions";
|
|
7
|
-
export { OrderRepository,
|
|
7
|
+
export { OrderRepository, orderRepository, } from "./repository/orders.repository";
|
|
8
8
|
// API route handler — re-export for consumer app router stubs
|
|
9
9
|
export { GET as getOrderTrackingHandler } from "./api/track/[trackingId]/route";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState } from "react";
|
|
4
|
-
import { Button, Div, FormField, Heading, Modal, Row, Select, SideDrawer, Stack, Text, Tabs, TabsContent, TabsList, TabsTrigger, DynamicSelect, useToast, } from "../../../ui";
|
|
4
|
+
import { Button, ConfirmDeleteModal, Div, FormField, Heading, Modal, Row, Select, SideDrawer, Stack, Text, Tabs, TabsContent, TabsList, TabsTrigger, DynamicSelect, useToast, } from "../../../ui";
|
|
5
5
|
import { apiClient } from "../../../http";
|
|
6
6
|
const CONDITION_OPTIONS = [
|
|
7
7
|
{ value: "new", label: "New" },
|
|
@@ -24,6 +24,7 @@ export function GroupSettingsPanel({ productId, productSlug, groupId, isGroupPar
|
|
|
24
24
|
});
|
|
25
25
|
const [linkTarget, setLinkTarget] = useState(null);
|
|
26
26
|
const [children, setChildren] = useState(null);
|
|
27
|
+
const [confirmAction, setConfirmAction] = useState(null);
|
|
27
28
|
if (isAuction)
|
|
28
29
|
return null;
|
|
29
30
|
const groupEndpoint = `${storeProductsEndpoint}/${productId}/group`;
|
|
@@ -71,38 +72,48 @@ export function GroupSettingsPanel({ productId, productSlug, groupId, isGroupPar
|
|
|
71
72
|
setLoading(false);
|
|
72
73
|
}
|
|
73
74
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
75
|
+
function requestDissolveGroup() {
|
|
76
|
+
setConfirmAction({
|
|
77
|
+
title: "Dissolve Group",
|
|
78
|
+
message: "Dissolve this group? All members will be unlinked. This cannot be undone.",
|
|
79
|
+
onConfirm: async () => {
|
|
80
|
+
setConfirmAction(null);
|
|
81
|
+
setLoading(true);
|
|
82
|
+
try {
|
|
83
|
+
await apiClient.delete(groupEndpoint);
|
|
84
|
+
showToast("Group dissolved.", "success");
|
|
85
|
+
onGroupChanged();
|
|
86
|
+
}
|
|
87
|
+
catch (e) {
|
|
88
|
+
showToast(e?.message ?? "Failed to dissolve group.", "error");
|
|
89
|
+
}
|
|
90
|
+
finally {
|
|
91
|
+
setLoading(false);
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
});
|
|
89
95
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
96
|
+
function requestUnlinkChild(childId) {
|
|
97
|
+
setConfirmAction({
|
|
98
|
+
title: "Remove from Group",
|
|
99
|
+
message: "Remove this listing from the group?",
|
|
100
|
+
onConfirm: async () => {
|
|
101
|
+
setConfirmAction(null);
|
|
102
|
+
setLoading(true);
|
|
103
|
+
try {
|
|
104
|
+
await apiClient.delete(`${childrenEndpoint}/${childId}`);
|
|
105
|
+
showToast("Listing removed from group.", "success");
|
|
106
|
+
setChildren((prev) => prev?.filter((c) => c.id !== childId) ?? null);
|
|
107
|
+
onGroupChanged();
|
|
108
|
+
}
|
|
109
|
+
catch (e) {
|
|
110
|
+
showToast(e?.message ?? "Failed to unlink.", "error");
|
|
111
|
+
}
|
|
112
|
+
finally {
|
|
113
|
+
setLoading(false);
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
});
|
|
106
117
|
}
|
|
107
118
|
async function addCreateChild() {
|
|
108
119
|
if (!createForm.title || !createForm.price) {
|
|
@@ -152,21 +163,26 @@ export function GroupSettingsPanel({ productId, productSlug, groupId, isGroupPar
|
|
|
152
163
|
setLoading(false);
|
|
153
164
|
}
|
|
154
165
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
166
|
+
function requestLeaveGroup() {
|
|
167
|
+
setConfirmAction({
|
|
168
|
+
title: "Leave Group",
|
|
169
|
+
message: "Leave this group? This listing will become standalone.",
|
|
170
|
+
onConfirm: async () => {
|
|
171
|
+
setConfirmAction(null);
|
|
172
|
+
setLoading(true);
|
|
173
|
+
try {
|
|
174
|
+
await apiClient.delete(`${storeProductsEndpoint}/${productId}/group/leave`);
|
|
175
|
+
showToast("Left the group.", "success");
|
|
176
|
+
onGroupChanged();
|
|
177
|
+
}
|
|
178
|
+
catch (e) {
|
|
179
|
+
showToast(e?.message ?? "Failed to leave group.", "error");
|
|
180
|
+
}
|
|
181
|
+
finally {
|
|
182
|
+
setLoading(false);
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
});
|
|
170
186
|
}
|
|
171
187
|
async function loadLinkOptions(query, page) {
|
|
172
188
|
const params = new URLSearchParams({ page: String(page), pageSize: "25" });
|
|
@@ -185,7 +201,7 @@ export function GroupSettingsPanel({ productId, productSlug, groupId, isGroupPar
|
|
|
185
201
|
loadChildren();
|
|
186
202
|
}, className: "w-full flex items-center justify-between py-2 text-left group", "aria-expanded": open, children: [_jsx(Heading, { level: 3, className: "text-sm font-semibold text-zinc-700 dark:text-zinc-300", children: "Group Settings" }), _jsx("span", { className: "text-xs text-zinc-400 dark:text-zinc-400 group-hover:text-zinc-600 dark:group-hover:text-zinc-300 transition-colors", children: open ? "▲" : "▼" })] }), open && (_jsxs(Stack, { gap: "sm", className: "mt-3 p-4 rounded-lg border border-zinc-200 dark:border-zinc-700 bg-zinc-50/50 dark:bg-zinc-800/30", children: [!groupId && !groupParentSlug && (_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400", children: "Group related listings together \u2014 e.g. a set, bundle, or multi-part item. Parts can be sold individually but shown together." }), _jsx(Button, { type: "button", variant: "secondary", size: "sm", onClick: startGroup, isLoading: loading, children: "Start a group" })] })), isGroupParent && groupId && (_jsxs(Stack, { gap: "md", children: [_jsxs(Row, { align: "start", gap: "sm", className: "flex-wrap", children: [_jsx("div", { className: "flex-1 min-w-[200px]", children: _jsx(FormField, { name: "groupTitle", label: "Group title", type: "text", value: editTitle, onChange: setEditTitle, placeholder: "e.g. Human Toy Complete Set" }) }), _jsx("div", { className: "pt-6", children: _jsx(Button, { type: "button", variant: "secondary", size: "sm", onClick: saveTitle, isLoading: loading, children: "Save title" }) })] }), _jsxs(Div, { children: [_jsxs(Text, { className: "text-xs font-semibold text-zinc-600 dark:text-zinc-400 mb-2", children: ["Members (", childSlugsCount + 1, " \u2014 including this listing)"] }), loading && !children ? (_jsx(Text, { className: "text-xs text-zinc-400 dark:text-zinc-400", children: "Loading\u2026" })) : (_jsxs("div", { className: "divide-y divide-zinc-100 dark:divide-zinc-800", children: [_jsxs(Row, { align: "center", gap: "sm", className: "py-2", children: [_jsx("span", { className: "rounded bg-[var(--appkit-color-primary,#6366f1)]/10 text-[var(--appkit-color-primary,#6366f1)] text-[10px] font-semibold px-1.5 py-0.5", children: "Parent" }), _jsx(Text, { className: "text-sm text-zinc-800 dark:text-zinc-200 flex-1", children: productSlug })] }), (children ?? []).filter((c) => c.id !== productId).map((child) => (_jsxs(Row, { align: "center", gap: "sm", className: "py-2", children: [child.images?.[0] ? (
|
|
187
203
|
// eslint-disable-next-line @next/next/no-img-element
|
|
188
|
-
_jsx("img", { src: child.images[0], alt: child.title, className: "w-8 h-8 rounded-full object-cover border border-zinc-200 dark:border-zinc-700" })) : (_jsx("div", { className: "w-8 h-8 rounded-full bg-zinc-100 dark:bg-zinc-800" })), _jsx(Text, { className: "text-sm text-zinc-800 dark:text-zinc-200 flex-1 truncate", children: child.title }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () =>
|
|
204
|
+
_jsx("img", { src: child.images[0], alt: child.title, className: "w-8 h-8 rounded-full object-cover border border-zinc-200 dark:border-zinc-700" })) : (_jsx("div", { className: "w-8 h-8 rounded-full bg-zinc-100 dark:bg-zinc-800" })), _jsx(Text, { className: "text-sm text-zinc-800 dark:text-zinc-200 flex-1 truncate", children: child.title }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => requestUnlinkChild(child.id), isLoading: loading, className: "text-red-500 hover:text-red-600 text-xs", children: "Unlink" })] }, child.id)))] }))] }), _jsxs(Row, { gap: "sm", className: "flex-wrap", children: [_jsx(Button, { type: "button", variant: "secondary", size: "sm", onClick: () => setShowAddModal(true), children: "Add child listing" }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: requestDissolveGroup, isLoading: loading, className: "text-red-500 hover:text-red-600", children: "Dissolve group" })] })] })), !isGroupParent && groupParentSlug && (_jsxs(Stack, { gap: "xs", children: [_jsxs(Text, { className: "text-sm text-zinc-600 dark:text-zinc-400", children: ["Part of:", " ", _jsx("span", { className: "font-medium text-zinc-800 dark:text-zinc-200", children: groupTitle ?? groupParentSlug })] }), _jsxs(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400", children: ["Parent listing: ", _jsx("code", { className: "font-mono", children: groupParentSlug })] }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: requestLeaveGroup, isLoading: loading, className: "text-red-500 hover:text-red-600 w-fit", children: "Leave group" })] }))] })), (groupChildSlugs?.length ?? 0) >= 4 ? (_jsx(SideDrawer, { isOpen: showAddModal, onClose: () => setShowAddModal(false), title: "Add child listing", children: _jsx(AddChildContent, { addTab: addTab, setAddTab: setAddTab, createForm: createForm, setCreateForm: setCreateForm, productSlug: productSlug, linkTarget: linkTarget, setLinkTarget: setLinkTarget, loadLinkOptions: loadLinkOptions, onAddCreate: addCreateChild, onAddLink: addLinkChild, loading: loading }) })) : (_jsx(Modal, { open: showAddModal, onClose: () => setShowAddModal(false), title: "Add child listing", size: "lg", children: _jsx(AddChildContent, { addTab: addTab, setAddTab: setAddTab, createForm: createForm, setCreateForm: setCreateForm, productSlug: productSlug, linkTarget: linkTarget, setLinkTarget: setLinkTarget, loadLinkOptions: loadLinkOptions, onAddCreate: addCreateChild, onAddLink: addLinkChild, loading: loading }) })), confirmAction && (_jsx(ConfirmDeleteModal, { isOpen: true, title: confirmAction.title, message: confirmAction.message, onConfirm: confirmAction.onConfirm, onClose: () => setConfirmAction(null), isDeleting: loading }))] }));
|
|
189
205
|
}
|
|
190
206
|
function AddChildContent({ addTab, setAddTab, createForm, setCreateForm, productSlug, linkTarget, setLinkTarget, loadLinkOptions, onAddCreate, onAddLink, loading, }) {
|
|
191
207
|
return (_jsx(Stack, { gap: "md", children: _jsxs(Tabs, { value: addTab, onChange: (v) => setAddTab(v), children: [_jsxs(TabsList, { children: [_jsx(TabsTrigger, { value: "create", children: "Create new child" }), _jsx(TabsTrigger, { value: "link", children: "Link existing" })] }), _jsx(TabsContent, { value: "create", children: _jsxs(Stack, { gap: "sm", className: "mt-4", children: [_jsx(FormField, { name: "childTitle", label: "Title *", type: "text", value: createForm.title, onChange: (v) => setCreateForm({ ...createForm, title: v }), placeholder: `${productSlug}-part` }), _jsx(FormField, { name: "childPrice", label: "Price (\u20B9) *", type: "number", value: createForm.price, onChange: (v) => setCreateForm({ ...createForm, price: v }), placeholder: "0" }), _jsx(Select, { label: "Condition", value: createForm.condition, onValueChange: (v) => setCreateForm({ ...createForm, condition: v }), options: CONDITION_OPTIONS }), _jsx(Text, { className: "text-xs text-zinc-400 dark:text-zinc-400/80", children: "Other fields (category, brand, shipping, return policy) are inherited from this parent listing. Need more control? Edit the full listing after saving." }), _jsx(Button, { type: "button", onClick: onAddCreate, isLoading: loading, disabled: !createForm.title || !createForm.price, children: "Create and link child" })] }) }), _jsx(TabsContent, { value: "link", children: _jsxs(Stack, { gap: "sm", className: "mt-4", children: [_jsx(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400", children: "Search your existing products or pre-orders. Auctions cannot be linked." }), _jsx(DynamicSelect, { value: linkTarget, onChange: (v) => setLinkTarget(v), loadOptions: loadLinkOptions, placeholder: "Search listings\u2026", searchPlaceholder: "Type title or slug\u2026", noResultsText: "No matching listings found", ariaLabel: "Listing to link" }), _jsx(Button, { type: "button", onClick: onAddLink, isLoading: loading, disabled: !linkTarget, children: "Link to group" })] }) })] }) }));
|
|
@@ -256,6 +256,4 @@ export declare class ProductRepository extends BaseRepository<ProductDocument> {
|
|
|
256
256
|
/** Cloud Functions: returns refs for draft products older than `cutoff` (for 30-day prune). */
|
|
257
257
|
getStaleDraftRefs(cutoff: Date): Promise<DocumentReference[]>;
|
|
258
258
|
}
|
|
259
|
-
export declare class ProductsRepository extends ProductRepository {
|
|
260
|
-
}
|
|
261
259
|
export declare const productRepository: ProductRepository;
|
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
* Server-only entry point — repositories and API route handlers.
|
|
5
5
|
*/
|
|
6
6
|
export * from "./actions";
|
|
7
|
-
export { ProductRepository,
|
|
7
|
+
export { ProductRepository, productRepository, } from "./repository/products.repository";
|
|
8
8
|
export { GET, POST } from "./api/route";
|
|
9
9
|
export { GET as productItemGET, PATCH as productItemPATCH, DELETE as productItemDELETE, } from "./api/[id]/route";
|
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
* Server-only entry point — repositories and API route handlers.
|
|
5
5
|
*/
|
|
6
6
|
export * from "./actions";
|
|
7
|
-
export { ProductRepository,
|
|
7
|
+
export { ProductRepository, productRepository, } from "./repository/products.repository";
|
|
8
8
|
export { GET, POST } from "./api/route";
|
|
9
9
|
export { GET as productItemGET, PATCH as productItemPATCH, DELETE as productItemDELETE, } from "./api/[id]/route";
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState } from "react";
|
|
4
4
|
import { Pencil, Trash2, ToggleLeft, ToggleRight } from "lucide-react";
|
|
5
|
-
import { BaseListingCard, Div, Row, Span, Text } from "../../../ui";
|
|
5
|
+
import { BaseListingCard, ConfirmDeleteModal, Div, Row, Span, Text } from "../../../ui";
|
|
6
6
|
import { useLongPress } from "../../../react/hooks/useLongPress";
|
|
7
7
|
const TYPE_COLORS = {
|
|
8
8
|
percentage: { card: "bg-purple-50 border-purple-200 dark:bg-purple-950/30 dark:border-purple-800", code: "bg-purple-100 dark:bg-purple-900/40 text-purple-800 dark:text-purple-200 border-purple-300 dark:border-purple-700" },
|
|
@@ -91,6 +91,7 @@ export function CouponCard({ coupon, labels: labelsProp, onCopy, className = "",
|
|
|
91
91
|
const [copied, setCopied] = useState(false);
|
|
92
92
|
const [claiming, setClaiming] = useState(false);
|
|
93
93
|
const [busy, setBusy] = useState(null);
|
|
94
|
+
const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false);
|
|
94
95
|
const longPress = useLongPress(() => onSelect?.(n.id, !isSelected));
|
|
95
96
|
const expiry = formatDateSafe(n.expiresAt);
|
|
96
97
|
const discountLabel = formatDiscount(n, labels);
|
|
@@ -144,11 +145,10 @@ export function CouponCard({ coupon, labels: labelsProp, onCopy, className = "",
|
|
|
144
145
|
setBusy(null);
|
|
145
146
|
}
|
|
146
147
|
};
|
|
147
|
-
const
|
|
148
|
+
const handleDeleteConfirmed = async () => {
|
|
148
149
|
if (!onDelete)
|
|
149
150
|
return;
|
|
150
|
-
|
|
151
|
-
return;
|
|
151
|
+
setDeleteConfirmOpen(false);
|
|
152
152
|
setBusy("delete");
|
|
153
153
|
try {
|
|
154
154
|
await onDelete(n.id);
|
|
@@ -161,5 +161,5 @@ export function CouponCard({ coupon, labels: labelsProp, onCopy, className = "",
|
|
|
161
161
|
? "bg-success-surface text-success"
|
|
162
162
|
: "bg-zinc-200 text-zinc-600 dark:bg-zinc-700 dark:text-zinc-300"}`, children: n.isActive ? labels.active : labels.inactive }), n.scope && (_jsx(Span, { className: "inline-flex items-center rounded-full bg-zinc-200/70 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-zinc-700 dark:bg-zinc-700/60 dark:text-zinc-200", children: n.scope }))] })), _jsxs(Div, { className: "mb-3", children: [_jsx(Text, { className: "text-2xl font-extrabold tracking-tight leading-none", children: discountLabel }), n.name && (_jsx(Text, { className: "text-sm mt-0.5 font-medium opacity-80", children: n.name }))] }), n.description && (_jsx(Text, { className: "text-xs opacity-60 mb-3", children: n.description })), _jsxs("div", { className: `flex items-center gap-2 rounded-lg border border-dashed px-3 py-2 ${colors.code}`, children: [_jsx("span", { className: "flex-1 font-mono text-sm font-bold tracking-widest uppercase select-all", children: n.code || "—" }), n.code && (_jsx("button", { type: "button", onClick: handleCopy, className: "shrink-0 rounded-md px-2.5 py-1 text-xs font-semibold transition-all hover:opacity-80 active:scale-95 bg-white/60 dark:bg-black/20 border border-current/20", "aria-label": "Copy coupon code", children: copied ? labels.copied : labels.copy }))] }), showClaim && n.code && n.isActive && (_jsx("div", { className: "mt-2", children: _jsx("button", { type: "button", onClick: handleClaim, disabled: claiming, className: "w-full rounded-md px-3 py-1.5 text-xs font-semibold transition-all active:scale-95 bg-[var(--appkit-color-primary-700)] text-[var(--appkit-color-text-on-primary,#fff)] hover:bg-[var(--appkit-color-primary-800)] disabled:opacity-50", "aria-label": `Claim coupon ${n.code} and apply at checkout`, children: claiming ? labels.claiming : labels.claim }) })), _jsxs(Row, { wrap: true, gap: "sm", className: "text-xs opacity-60 mt-2", children: [n.minOrderAmount ? (_jsxs(Span, { children: [labels.minOrder, ": \u20B9", (n.minOrderAmount / 100).toFixed(0)] })) : null, expiry && (_jsxs(Span, { children: [labels.expires, ": ", expiry] })), n.usageLimit ? (_jsxs(Span, { children: [n.usageCount, "/", n.usageLimit, " ", labels.used] })) : n.usageCount > 0 ? (_jsxs(Span, { children: [n.usageCount, " ", labels.used] })) : null] }), hasAdminActions && (_jsxs(Row, { gap: "xs", justify: "end", className: "mt-auto pt-3", children: [onEdit && (_jsx("button", { type: "button", onClick: () => onEdit(n.id), title: labels.edit, "aria-label": labels.edit, className: "rounded-lg p-1.5 text-zinc-500 dark:text-zinc-400 hover:bg-white/60 dark:hover:bg-black/30 hover:text-zinc-800 dark:hover:text-zinc-200 transition-colors", children: _jsx(Pencil, { className: "h-4 w-4" }) })), onToggleActive && (_jsx("button", { type: "button", onClick: handleToggle, disabled: busy === "toggle", title: n.isActive ? labels.deactivate : labels.activate, "aria-label": n.isActive ? labels.deactivate : labels.activate, className: "rounded-lg p-1.5 text-zinc-500 dark:text-zinc-400 hover:bg-white/60 dark:hover:bg-black/30 transition-colors disabled:opacity-50", children: n.isActive
|
|
163
163
|
? _jsx(ToggleRight, { className: "h-4 w-4 text-emerald-600 dark:text-emerald-400" })
|
|
164
|
-
: _jsx(ToggleLeft, { className: "h-4 w-4" }) })), onDelete && (_jsx("button", { type: "button", onClick:
|
|
164
|
+
: _jsx(ToggleLeft, { className: "h-4 w-4" }) })), onDelete && (_jsx("button", { type: "button", onClick: () => setDeleteConfirmOpen(true), disabled: busy === "delete", title: labels.delete, "aria-label": labels.delete, className: "rounded-lg p-1.5 text-zinc-500 dark:text-zinc-400 hover:bg-red-50 dark:hover:bg-red-950/40 hover:text-red-600 dark:hover:text-red-400 transition-colors disabled:opacity-50", children: _jsx(Trash2, { className: "h-4 w-4" }) }))] })), deleteConfirmOpen && (_jsx(ConfirmDeleteModal, { isOpen: true, title: "Delete Coupon", message: labels.deleteConfirm, onConfirm: handleDeleteConfirmed, onClose: () => setDeleteConfirmOpen(false), isDeleting: busy === "delete" }))] }));
|
|
165
165
|
}
|
|
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
3
3
|
import { useState, useCallback, useMemo } from "react";
|
|
4
4
|
import { Search, SlidersHorizontal, X } from "lucide-react";
|
|
5
5
|
import { useUrlTable } from "../../../react/hooks/useUrlTable";
|
|
6
|
-
import { Pagination, SortDropdown, Div, Text, Heading } from "../../../ui";
|
|
6
|
+
import { ListingFilterDrawer, Pagination, SortDropdown, Div, Text, Heading } from "../../../ui";
|
|
7
7
|
import { usePromotions } from "../hooks/usePromotions";
|
|
8
8
|
import { CouponCard } from "./CouponCard";
|
|
9
9
|
import { TABLE_KEYS } from "../../../constants/table-keys";
|
|
@@ -34,6 +34,7 @@ export function CouponsIndexListing({ initialCoupons, storeSlug, storeId, }) {
|
|
|
34
34
|
get: (key) => pendingFilters[key] ?? "",
|
|
35
35
|
set: (key, value) => setPendingFilters((p) => ({ ...p, [key]: value })),
|
|
36
36
|
}), [pendingFilters]);
|
|
37
|
+
const activeFilterCount = FILTER_KEYS.filter((k) => !!table.get(k)).length;
|
|
37
38
|
const openFilters = useCallback(() => {
|
|
38
39
|
setPendingFilters(Object.fromEntries(FILTER_KEYS.map((k) => [k, table.get(k)])));
|
|
39
40
|
setFilterOpen(true);
|
|
@@ -97,5 +98,5 @@ export function CouponsIndexListing({ initialCoupons, storeSlug, storeId, }) {
|
|
|
97
98
|
minOrder: "Min. order",
|
|
98
99
|
off: "OFF",
|
|
99
100
|
freeShipping: "Free Shipping",
|
|
100
|
-
} }, coupon.id))) })), totalPages > 1 && (_jsx("div", { className: "mt-8 flex justify-center", children: _jsx(Pagination, { currentPage: table.getNumber("page", 1), totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), !isLoading && total > 0 && (_jsx(Div, { className: "mt-4 text-center", children: _jsxs(Text, { className: "text-xs text-zinc-400 dark:text-zinc-400", children: [total, " coupon", total !== 1 ? "s" : "", " available"] }) }))] }),
|
|
101
|
+
} }, coupon.id))) })), totalPages > 1 && (_jsx("div", { className: "mt-8 flex justify-center", children: _jsx(Pagination, { currentPage: table.getNumber("page", 1), totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), !isLoading && total > 0 && (_jsx(Div, { className: "mt-4 text-center", children: _jsxs(Text, { className: "text-xs text-zinc-400 dark:text-zinc-400", children: [total, " coupon", total !== 1 ? "s" : "", " available"] }) }))] }), _jsxs(ListingFilterDrawer, { open: filterOpen, onClose: () => setFilterOpen(false), onApply: applyFilters, onClear: clearPending, activeCount: activeFilterCount, children: [_jsxs(_Fragment, { children: [_jsx(Heading, { level: 6, className: "text-xs font-semibold uppercase tracking-wider text-zinc-500 dark:text-zinc-400 mb-3", children: "Discount Type" }), _jsxs("div", { className: "space-y-2", children: [COUPON_TYPES.map((t) => (_jsxs("label", { className: "flex items-center gap-2 cursor-pointer text-sm text-zinc-700 dark:text-zinc-300", children: [_jsx("input", { type: "radio", name: "coupon-type", value: t.value, checked: pendingTable.get(TABLE_KEYS.TYPE) === t.value, onChange: () => { pendingTable.set(TABLE_KEYS.TYPE, t.value); }, className: "accent-primary" }), t.label] }, t.value))), pendingTable.get(TABLE_KEYS.TYPE) && (_jsx("button", { type: "button", onClick: () => { pendingTable.set(TABLE_KEYS.TYPE, ""); }, className: "text-xs text-zinc-400 hover:text-zinc-600 dark:hover:text-zinc-300 underline", children: "Clear type" }))] })] }), _jsxs(_Fragment, { children: [_jsx(Heading, { level: 6, className: "text-xs font-semibold uppercase tracking-wider text-zinc-500 dark:text-zinc-400 mb-3", children: "Valid Date Range" }), _jsxs("div", { className: "space-y-3", children: [_jsxs(_Fragment, { children: [_jsx("label", { className: "block text-xs text-zinc-500 dark:text-zinc-400 mb-1", children: "From date" }), _jsx("input", { type: "date", value: pendingTable.get(TABLE_KEYS.DATE_FROM) || "", onChange: (e) => { pendingTable.set(TABLE_KEYS.DATE_FROM, e.target.value); }, className: "w-full rounded-lg border border-zinc-300 dark:border-slate-600 bg-white dark:bg-slate-800 px-3 py-2 text-sm text-zinc-900 dark:text-zinc-100 outline-none focus:ring-2 focus:ring-primary" })] }), _jsxs(_Fragment, { children: [_jsx("label", { className: "block text-xs text-zinc-500 dark:text-zinc-400 mb-1", children: "To date" }), _jsx("input", { type: "date", value: pendingTable.get(TABLE_KEYS.DATE_TO) || "", onChange: (e) => { pendingTable.set(TABLE_KEYS.DATE_TO, e.target.value); }, className: "w-full rounded-lg border border-zinc-300 dark:border-slate-600 bg-white dark:bg-slate-800 px-3 py-2 text-sm text-zinc-900 dark:text-zinc-100 outline-none focus:ring-2 focus:ring-primary" })] })] })] })] })] }));
|
|
101
102
|
}
|
|
@@ -1,9 +1,8 @@
|
|
|
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 { useState, useCallback, useMemo } from "react";
|
|
4
|
-
import { X } from "lucide-react";
|
|
5
4
|
import { useUrlTable } from "../../../react/hooks/useUrlTable";
|
|
6
|
-
import { ListingToolbar, Pagination, Text } from "../../../ui";
|
|
5
|
+
import { ListingFilterDrawer, ListingToolbar, Pagination, Text } from "../../../ui";
|
|
7
6
|
import { ReviewCard } from "./ReviewsList";
|
|
8
7
|
import { ReviewFilters, REVIEW_PUBLIC_SORT_OPTIONS } from "./ReviewFilters";
|
|
9
8
|
import { useReviews } from "../hooks/useReviews";
|
|
@@ -103,5 +102,5 @@ export function ReviewsIndexListing({ initialData, variant = "public", }) {
|
|
|
103
102
|
value: opt.value,
|
|
104
103
|
label: SORT_OPTION_LABELS[opt.key] ?? opt.key,
|
|
105
104
|
}));
|
|
106
|
-
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search reviews by product name...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: sort, sortOptions: sortOptions, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, view: view, onViewChange: handleViewToggle, onResetAll: resetAll, hasActiveState: hasActiveState }), 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: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsx("div", { className: "py-6", children: isLoading ? (_jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6", children: Array.from({ length: 6 }).map((_, i) => (_jsx("div", { className: "rounded-xl border border-zinc-100 dark:border-slate-700 overflow-hidden animate-pulse", children: _jsxs("div", { className: "p-4 space-y-3", children: [_jsx("div", { className: "h-4 bg-zinc-200 dark:bg-slate-700 rounded w-3/4" }), _jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-full" }), _jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-2/3" })] }) }, i))) })) : reviews.length === 0 ? (_jsx(Text, { className: "py-12 text-center text-sm text-zinc-500 dark:text-zinc-400", children: "No reviews found." })) : view === "list" ? (_jsx("div", { className: "flex flex-col divide-y divide-zinc-100 dark:divide-zinc-800 rounded-xl border border-zinc-100 dark:border-zinc-800", children: reviews.map((review) => (_jsx(ReviewCard, { review: review }, review.id))) })) : (_jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6", children: reviews.map((review) => (_jsx(ReviewCard, { review: review }, review.id))) })) }),
|
|
105
|
+
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search reviews by product name...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: sort, sortOptions: sortOptions, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, view: view, onViewChange: handleViewToggle, onResetAll: resetAll, hasActiveState: hasActiveState }), 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: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsx("div", { className: "py-6", children: isLoading ? (_jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6", children: Array.from({ length: 6 }).map((_, i) => (_jsx("div", { className: "rounded-xl border border-zinc-100 dark:border-slate-700 overflow-hidden animate-pulse", children: _jsxs("div", { className: "p-4 space-y-3", children: [_jsx("div", { className: "h-4 bg-zinc-200 dark:bg-slate-700 rounded w-3/4" }), _jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-full" }), _jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-2/3" })] }) }, i))) })) : reviews.length === 0 ? (_jsx(Text, { className: "py-12 text-center text-sm text-zinc-500 dark:text-zinc-400", children: "No reviews found." })) : view === "list" ? (_jsx("div", { className: "flex flex-col divide-y divide-zinc-100 dark:divide-zinc-800 rounded-xl border border-zinc-100 dark:border-zinc-800", children: reviews.map((review) => (_jsx(ReviewCard, { review: review }, review.id))) })) : (_jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6", children: reviews.map((review) => (_jsx(ReviewCard, { review: review }, review.id))) })) }), _jsx(ListingFilterDrawer, { open: filterOpen, onClose: () => setFilterOpen(false), onApply: applyFilters, onClear: clearFilters, activeCount: activeFilterCount, children: _jsx(ReviewFilters, { table: pendingTable, variant: variant }) })] }));
|
|
107
106
|
}
|
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
* Server-only entry point — repositories and API route handlers.
|
|
5
5
|
*/
|
|
6
6
|
export * from "./actions";
|
|
7
|
-
export { ReviewRepository,
|
|
7
|
+
export { ReviewRepository, reviewRepository, } from "./repository/reviews.repository";
|
|
8
8
|
export { GET as reviewsGET, GET, POST } from "./api/route";
|
|
9
9
|
export { reviewItemGET, reviewItemPATCH, reviewItemDELETE, } from "./api/[id]/route";
|
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
* Server-only entry point — repositories and API route handlers.
|
|
5
5
|
*/
|
|
6
6
|
export * from "./actions";
|
|
7
|
-
export { ReviewRepository,
|
|
7
|
+
export { ReviewRepository, reviewRepository, } from "./repository/reviews.repository";
|
|
8
8
|
export { GET as reviewsGET, GET, POST } from "./api/route";
|
|
9
9
|
export { reviewItemGET, reviewItemPATCH, reviewItemDELETE, } from "./api/[id]/route";
|