@mohasinac/appkit 2.7.57 → 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/client/features/layout/DashboardLayoutClient.d.ts +7 -1
- package/dist/_internal/client/features/layout/DashboardLayoutClient.js +8 -2
- 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 +4 -1
- package/dist/client.js +3 -1
- package/dist/configs/next.js +11 -0
- package/dist/configs/tailwind.d.ts +16 -3
- package/dist/configs/tailwind.js +18 -5
- package/dist/features/account/components/AddressBook.js +1 -1
- package/dist/features/account/components/AddressesIndexListing.js +3 -4
- package/dist/features/account/components/BuyerAccountGuideView.js +7 -7
- package/dist/features/account/components/BuyerAuctionsGuideView.js +7 -7
- package/dist/features/account/components/BuyerOrdersGuideView.js +6 -6
- package/dist/features/account/components/BuyerShoppingGuideView.js +7 -7
- package/dist/features/account/components/UserOffersPanel.js +1 -1
- package/dist/features/account/components/UserSidebar.js +14 -10
- package/dist/features/account/components/UserSupportView.js +4 -4
- package/dist/features/admin/components/AdminAdsView.js +1 -1
- package/dist/features/admin/components/AdminAllEventEntriesView.js +4 -10
- package/dist/features/admin/components/AdminAnalyticsGuideView.js +3 -3
- package/dist/features/admin/components/AdminBidsView.js +3 -9
- 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 +40 -59
- package/dist/features/admin/components/AdminCarouselView.js +8 -21
- package/dist/features/admin/components/AdminCartsView.js +3 -4
- package/dist/features/admin/components/AdminCatalogGuideView.js +5 -5
- package/dist/features/admin/components/AdminCategoriesView.js +4 -9
- package/dist/features/admin/components/AdminCategoryEditorView.js +1 -5
- package/dist/features/admin/components/AdminContactEditorView.js +2 -2
- package/dist/features/admin/components/AdminContactView.js +7 -25
- package/dist/features/admin/components/AdminContentGuideView.js +6 -6
- 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/AdminFeatureFlagsView.js +1 -1
- package/dist/features/admin/components/AdminFeaturesView.js +1 -1
- package/dist/features/admin/components/AdminGuideHubView.js +1 -1
- package/dist/features/admin/components/AdminHistoryView.js +1 -1
- package/dist/features/admin/components/AdminListingScaffold.js +1 -1
- package/dist/features/admin/components/AdminNewsletterView.js +5 -15
- package/dist/features/admin/components/AdminNotificationsView.js +3 -9
- package/dist/features/admin/components/AdminOrdersGuideView.js +5 -5
- package/dist/features/admin/components/AdminOrdersView.js +3 -9
- package/dist/features/admin/components/AdminPayoutsView.js +3 -9
- 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 +5 -10
- package/dist/features/admin/components/AdminReturnRequestsView.js +1 -1
- package/dist/features/admin/components/AdminReviewsView.js +3 -9
- package/dist/features/admin/components/AdminScammerEditorView.js +3 -3
- package/dist/features/admin/components/AdminScammersView.js +6 -12
- package/dist/features/admin/components/AdminSectionsView.js +1 -1
- package/dist/features/admin/components/AdminSessionsView.js +3 -9
- package/dist/features/admin/components/AdminSiteSettingsView.js +1 -1
- package/dist/features/admin/components/AdminStoreAddressesView.js +1 -1
- package/dist/features/admin/components/AdminStoresGuideView.js +6 -6
- package/dist/features/admin/components/AdminStoresView.js +3 -9
- package/dist/features/admin/components/AdminSublistingCategoriesView.js +3 -7
- package/dist/features/admin/components/AdminSublistingCategoryEditorView.js +17 -20
- package/dist/features/admin/components/AdminSupportTicketDetailView.js +4 -4
- package/dist/features/admin/components/AdminSupportTicketsView.js +9 -15
- package/dist/features/admin/components/AdminTeamGuideView.js +4 -4
- package/dist/features/admin/components/AdminTeamView.js +6 -15
- package/dist/features/admin/components/AdminTrustGuideView.js +5 -5
- package/dist/features/admin/components/AdminUserEditorView.js +1 -1
- package/dist/features/admin/components/AdminUsersGuideView.js +5 -5
- package/dist/features/admin/components/AdminUsersView.js +3 -9
- package/dist/features/admin/components/AdminViewCards.js +1 -1
- package/dist/features/admin/components/AdminWishlistsView.js +1 -1
- package/dist/features/admin/components/DataTable.js +1 -1
- package/dist/features/admin/hooks/useAdminListing.d.ts +58 -0
- package/dist/features/admin/hooks/useAdminListing.js +71 -0
- package/dist/features/auctions/components/AuctionBidsTable.js +1 -1
- package/dist/features/auctions/components/AuctionDetailPageView.js +2 -2
- package/dist/features/auctions/components/MarketplaceAuctionCard.js +2 -2
- package/dist/features/auctions/components/PlaceBidFormClient.js +1 -1
- package/dist/features/auth/components/VerifyEmailView.js +1 -1
- package/dist/features/auth/schemas/firestore.d.ts +1 -0
- package/dist/features/blog/components/BlogIndexListing.js +3 -6
- package/dist/features/blog/components/BlogListView.js +1 -1
- package/dist/features/blog/components/BlogPostView.js +1 -1
- package/dist/features/cart/components/CartDrawer.js +1 -1
- package/dist/features/categories/components/CategoriesIndexListing.js +3 -6
- package/dist/features/categories/components/CategoryDetailPageView.js +1 -1
- package/dist/features/checkout/components/CheckoutStepper.js +1 -1
- package/dist/features/collections/components/CollectionCard.js +1 -1
- package/dist/features/copilot/components/AdminCopilotView.js +2 -2
- package/dist/features/events/components/AdminEventsView.js +4 -4
- package/dist/features/events/components/EventPollWidget.js +2 -2
- package/dist/features/events/components/EventStatusBadge.js +4 -4
- package/dist/features/events/components/EventsIndexListing.js +3 -4
- package/dist/features/events/components/SpinWheelView.js +1 -1
- package/dist/features/faq/components/FAQAccordion.js +2 -2
- package/dist/features/forms/Input.js +3 -3
- package/dist/features/forms/Select.js +2 -2
- package/dist/features/forms/Textarea.js +2 -2
- package/dist/features/forms/utils.d.ts +1 -1
- package/dist/features/forms/utils.js +1 -1
- package/dist/features/homepage/components/AnnouncementBar.d.ts +4 -1
- package/dist/features/homepage/components/AnnouncementBar.js +51 -4
- package/dist/features/homepage/components/CharacterHotspotForm.js +2 -2
- 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/layout/BottomActions.js +2 -2
- package/dist/features/layout/ListingLayout.js +3 -3
- package/dist/features/layout/TitleBarLayout.js +1 -1
- package/dist/features/media/index.d.ts +2 -0
- package/dist/features/media/index.js +1 -0
- package/dist/features/media/modals/ImageCropModal.js +1 -1
- package/dist/features/media/modals/ImageEditor.d.ts +12 -0
- package/dist/features/media/modals/ImageEditor.js +73 -0
- package/dist/features/media/upload/ImageUpload.d.ts +5 -1
- package/dist/features/media/upload/ImageUpload.js +33 -8
- package/dist/features/media/upload/MediaUploadField.d.ts +5 -1
- package/dist/features/media/upload/MediaUploadField.js +26 -12
- package/dist/features/orders/components/OrdersList.js +7 -7
- package/dist/features/orders/components/RefundHistoryTable.js +2 -2
- package/dist/features/orders/components/RefundRequestView.js +3 -3
- 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/pre-orders/components/PreOrderActionsClient.js +1 -1
- package/dist/features/pre-orders/components/PreOrderBadge.js +4 -4
- package/dist/features/pre-orders/components/PreOrderDetailPageView.js +1 -1
- package/dist/features/pre-orders/components/PreorderCard.js +2 -2
- package/dist/features/products/components/CustomSectionsEditor.js +1 -1
- package/dist/features/products/components/GroupSettingsPanel.js +64 -48
- package/dist/features/products/components/MakeOfferButton.js +2 -2
- package/dist/features/products/components/PrizeDrawDetailPageView.js +2 -2
- package/dist/features/products/components/PrizeDrawItemsEditor.js +3 -3
- package/dist/features/products/components/PrizeRevealModal.js +3 -3
- package/dist/features/products/components/ProductDetailPageView.js +4 -4
- package/dist/features/products/components/ProductFeaturesSelector.js +1 -1
- package/dist/features/products/components/ProductGrid.js +3 -3
- 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 +7 -7
- package/dist/features/promotions/components/CouponsIndexListing.js +3 -2
- package/dist/features/promotions/components/PromotionsView.js +1 -1
- package/dist/features/reviews/components/ReviewDetailShell.js +2 -2
- package/dist/features/reviews/components/ReviewsIndexListing.js +3 -4
- package/dist/features/reviews/components/ReviewsList.js +1 -1
- 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 +11 -21
- package/dist/features/seller/components/SellerAnalyticsAlertsView.js +2 -2
- package/dist/features/seller/components/SellerAuctionsView.js +14 -23
- package/dist/features/seller/components/SellerBidsView.js +6 -6
- package/dist/features/seller/components/SellerBundlesView.js +13 -21
- package/dist/features/seller/components/SellerClassifiedView.js +14 -22
- package/dist/features/seller/components/SellerCouponEditorView.js +2 -2
- package/dist/features/seller/components/SellerCouponsView.js +14 -20
- package/dist/features/seller/components/SellerDigitalCodesView.js +13 -21
- package/dist/features/seller/components/SellerFeaturesView.js +1 -1
- package/dist/features/seller/components/SellerGroupedListingsView.js +6 -10
- package/dist/features/seller/components/SellerLiveView.js +15 -23
- package/dist/features/seller/components/SellerOffersPanel.js +2 -2
- package/dist/features/seller/components/SellerOffersView.js +4 -5
- package/dist/features/seller/components/SellerOrdersView.js +9 -23
- package/dist/features/seller/components/SellerPayoutMethodsView.js +13 -25
- package/dist/features/seller/components/SellerPayoutsView.js +4 -5
- package/dist/features/seller/components/SellerPreOrdersView.js +13 -22
- package/dist/features/seller/components/SellerPrizeDrawsView.js +13 -22
- package/dist/features/seller/components/SellerProductsCards.js +1 -1
- 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 +10 -23
- package/dist/features/seller/components/SellerReviewsView.js +1 -1
- package/dist/features/seller/components/SellerShippingConfigsView.js +14 -26
- package/dist/features/seller/components/SellerStoreCategoriesView.js +16 -35
- package/dist/features/seller/components/SellerTemplatesView.js +27 -43
- package/dist/features/seller/components/analytics/SellerTopProducts.js +1 -1
- package/dist/features/stores/components/StoreFinanceGuideView.js +2 -2
- package/dist/features/stores/components/StoreHeader.js +1 -1
- package/dist/features/stores/components/StoreListingsGuideView.js +3 -3
- package/dist/features/stores/components/StoreOrdersGuideView.js +4 -4
- package/dist/features/stores/components/StoreReviewsListing.js +3 -4
- package/dist/features/stores/components/StoreSettingsGuideView.js +2 -2
- package/dist/features/stores/components/StoresIndexListing.js +3 -4
- package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +4 -4
- package/dist/features/wishlist/components/WishlistPage.js +1 -1
- package/dist/index.d.ts +4 -6
- package/dist/index.js +6 -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 +286 -3
- package/dist/tailwind-utilities.css +1 -1
- package/dist/tokens/motion.d.ts +162 -0
- package/dist/tokens/motion.js +89 -0
- package/dist/ui/DataTable.js +3 -2
- package/dist/ui/components/Accordion.js +2 -1
- package/dist/ui/components/Card.d.ts +5 -2
- package/dist/ui/components/Card.js +29 -11
- package/dist/ui/components/Div.d.ts +2 -16
- package/dist/ui/components/Div.js +2 -16
- package/dist/ui/components/Layout.d.ts +9 -19
- package/dist/ui/components/Layout.js +9 -7
- package/dist/ui/components/ListingFilterDrawer.d.ts +10 -0
- package/dist/ui/components/ListingFilterDrawer.js +8 -0
- package/dist/ui/components/Modal.js +7 -4
- package/dist/ui/components/Motion.d.ts +75 -0
- package/dist/ui/components/Motion.js +122 -0
- package/dist/ui/components/Semantic.d.ts +81 -14
- package/dist/ui/components/Semantic.js +75 -10
- package/dist/ui/components/Semantic.style.css +261 -2
- package/dist/ui/components/SideDrawer.d.ts +1 -1
- package/dist/ui/components/SideDrawer.js +8 -6
- package/dist/ui/components/SideModal.d.ts +1 -1
- package/dist/ui/components/SideModal.js +11 -9
- package/dist/ui/components/StackedViewShell.js +6 -1
- package/dist/ui/components/Toast.js +5 -2
- package/dist/ui/components/Typography.d.ts +44 -3
- package/dist/ui/components/Typography.js +15 -30
- package/dist/ui/components/Typography.style.css +24 -0
- package/dist/ui/components/VacationBanner.js +2 -1
- package/dist/ui/components/surface-tokens.d.ts +70 -0
- package/dist/ui/components/surface-tokens.js +68 -0
- package/dist/ui/forms/FormShell.d.ts +6 -1
- package/dist/ui/forms/FormShell.js +32 -4
- package/dist/ui/index.d.ts +11 -2
- package/dist/ui/index.js +5 -2
- package/dist/validation/schemas.d.ts +28 -28
- package/package.json +3 -1
|
@@ -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" })] }) })] }) }));
|
|
@@ -67,11 +67,11 @@ export function MakeOfferButton({ productId, listedPrice, currency, minOfferPerc
|
|
|
67
67
|
});
|
|
68
68
|
}
|
|
69
69
|
if (state === "success") {
|
|
70
|
-
return (_jsxs(Div, { className: `rounded-xl border border-emerald-200 dark:border-emerald-800 bg-
|
|
70
|
+
return (_jsxs(Div, { className: `rounded-xl border border-emerald-200 dark:border-emerald-800 bg-success-surface p-4 text-center space-y-1 ${className}`, children: [_jsx(Span, { className: "text-lg", children: "\uD83C\uDF89" }), _jsx(Text, { className: "text-sm font-medium text-success", children: "Offer sent!" }), _jsx(Text, { className: "text-xs text-success", children: "The seller will review your offer and respond shortly." })] }));
|
|
71
71
|
}
|
|
72
72
|
if (state === "pending") {
|
|
73
73
|
return (_jsxs(Div, { className: `rounded-xl border border-amber-200 dark:border-amber-800 bg-amber-50 dark:bg-amber-900/20 p-4 text-center space-y-1 ${className}`, children: [_jsx(Span, { className: "text-lg", children: "\u23F3" }), _jsx(Text, { className: "text-sm font-medium text-amber-800 dark:text-amber-300", children: "Offer Pending" }), _jsx(Text, { className: "text-xs text-amber-700 dark:text-amber-400", children: "You already have an offer on this item. Check My Offers for updates." })] }));
|
|
74
74
|
}
|
|
75
75
|
const modalOpen = state === "confirm" || state === "loading" || state === "error";
|
|
76
|
-
return (_jsxs(_Fragment, { children: [_jsx(LoginRequiredModal, { isOpen: showLoginModal, onClose: () => setShowLoginModal(false), message: "You need to be signed in to make an offer. Please log in or create an account to continue." }), _jsx(Button, { variant: "ghost", size: "md", className: `w-full border border-zinc-300 dark:border-zinc-600 ${className}`, onClick: handleOpenConfirm, children: "Make Offer" }), _jsx(Modal, { isOpen: modalOpen, onClose: handleCancel, size: "md", title: "Make an offer", actions: _jsxs(_Fragment, { children: [_jsx(Button, { variant: "ghost", size: "sm", onClick: handleCancel, disabled: isPending, children: "Cancel" }), _jsx(Button, { variant: "primary", size: "sm", onClick: handleSubmit, disabled: isPending || offerAmount < minOffer, children: isPending ? "Sending…" : `Send offer of ${fmt(offerAmount)}` })] }), children: _jsxs(Stack, { gap: "md", children: [_jsxs(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400", children: ["Listed at ", fmt(listedPrice), " \u00B7 Minimum offer: ", fmt(minOffer)] }), _jsxs(Div, { className: "space-y-1", children: [_jsx(Text, { className: "text-xs font-medium text-zinc-600 dark:text-zinc-400", children: "Your offer amount" }), _jsx(Input, { type: "number", value: String(offerAmount), onChange: (e) => handleAmountChange(e.target.value), min: minOffer, max: listedPrice - 1, step: 1, "aria-label": "Offer amount" }), _jsxs(Text, { className: "text-xs text-zinc-400 dark:text-zinc-400", children: ["Must be between ", fmt(minOffer), " and ", fmt(listedPrice - 1)] })] }), _jsxs(Div, { className: "space-y-1", children: [_jsx(Text, { className: "text-xs font-medium text-zinc-600 dark:text-zinc-400", children: "Note to seller (optional)" }), _jsx(Input, { type: "text", value: buyerNote, onChange: (e) => setBuyerNote(e.target.value), placeholder: "E.g. Bundle deal, long-time fan\u2026", maxLength: 300, "aria-label": "Note to seller" })] }), _jsx(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400", children: "The seller will accept, decline, or suggest a counter price." }), state === "error" && (_jsx(Text, { className: "text-xs text-
|
|
76
|
+
return (_jsxs(_Fragment, { children: [_jsx(LoginRequiredModal, { isOpen: showLoginModal, onClose: () => setShowLoginModal(false), message: "You need to be signed in to make an offer. Please log in or create an account to continue." }), _jsx(Button, { variant: "ghost", size: "md", className: `w-full border border-zinc-300 dark:border-zinc-600 ${className}`, onClick: handleOpenConfirm, children: "Make Offer" }), _jsx(Modal, { isOpen: modalOpen, onClose: handleCancel, size: "md", title: "Make an offer", actions: _jsxs(_Fragment, { children: [_jsx(Button, { variant: "ghost", size: "sm", onClick: handleCancel, disabled: isPending, children: "Cancel" }), _jsx(Button, { variant: "primary", size: "sm", onClick: handleSubmit, disabled: isPending || offerAmount < minOffer, children: isPending ? "Sending…" : `Send offer of ${fmt(offerAmount)}` })] }), children: _jsxs(Stack, { gap: "md", children: [_jsxs(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400", children: ["Listed at ", fmt(listedPrice), " \u00B7 Minimum offer: ", fmt(minOffer)] }), _jsxs(Div, { className: "space-y-1", children: [_jsx(Text, { className: "text-xs font-medium text-zinc-600 dark:text-zinc-400", children: "Your offer amount" }), _jsx(Input, { type: "number", value: String(offerAmount), onChange: (e) => handleAmountChange(e.target.value), min: minOffer, max: listedPrice - 1, step: 1, "aria-label": "Offer amount" }), _jsxs(Text, { className: "text-xs text-zinc-400 dark:text-zinc-400", children: ["Must be between ", fmt(minOffer), " and ", fmt(listedPrice - 1)] })] }), _jsxs(Div, { className: "space-y-1", children: [_jsx(Text, { className: "text-xs font-medium text-zinc-600 dark:text-zinc-400", children: "Note to seller (optional)" }), _jsx(Input, { type: "text", value: buyerNote, onChange: (e) => setBuyerNote(e.target.value), placeholder: "E.g. Bundle deal, long-time fan\u2026", maxLength: 300, "aria-label": "Note to seller" })] }), _jsx(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400", children: "The seller will accept, decline, or suggest a counter price." }), state === "error" && (_jsx(Text, { className: "text-xs text-error", children: errorMsg }))] }) })] }));
|
|
77
77
|
}
|
|
@@ -33,12 +33,12 @@ function statusLabel(s) {
|
|
|
33
33
|
function statusClass(s) {
|
|
34
34
|
switch (s) {
|
|
35
35
|
case "open":
|
|
36
|
-
return "bg-
|
|
36
|
+
return "bg-success-surface text-success";
|
|
37
37
|
case "closed":
|
|
38
38
|
return "bg-zinc-200 text-zinc-700 dark:bg-zinc-700 dark:text-zinc-200";
|
|
39
39
|
case "pending":
|
|
40
40
|
default:
|
|
41
|
-
return "bg-
|
|
41
|
+
return "bg-warning-surface text-warning";
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
function stripIsWon(items) {
|
|
@@ -11,7 +11,7 @@ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
|
11
11
|
* Min 3 items, max 16 (mirrors PrizeDrawItem schema constraints).
|
|
12
12
|
*/
|
|
13
13
|
import { useCallback } from "react";
|
|
14
|
-
import { Button, Div, FormField, Heading, Row, Stack, Text } from "../../../ui";
|
|
14
|
+
import { Button, Div, FormField, Heading, Row, Span, Stack, Text } from "../../../ui";
|
|
15
15
|
import { ImageUpload } from "../../media";
|
|
16
16
|
const MIN_ITEMS = 3;
|
|
17
17
|
const MAX_ITEMS = 16;
|
|
@@ -84,7 +84,7 @@ export function PrizeDrawItemsEditor({ items, onChange, onUploadImage, onUploadV
|
|
|
84
84
|
const nextImgs = (cur.images ?? []).filter((_, i) => i !== slot);
|
|
85
85
|
update(index, { images: nextImgs });
|
|
86
86
|
};
|
|
87
|
-
return (_jsxs(Stack, { gap: "md", children: [_jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Heading, { level: 3, children: ["Prize Items (", items.length, ")"] }), _jsxs(Button, { type: "button", variant: "secondary", onClick: addItem, disabled: anyWon || items.length >= MAX_ITEMS, children: ["+ Add prize (", items.length, "/", MAX_ITEMS, ")"] })] }), _jsxs(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: ["Add between ", MIN_ITEMS, " and ", MAX_ITEMS, " prizes. Each entry will reveal exactly one of these. Items marked won during a prior reveal cannot be edited or removed."] }), anyWon ? (_jsxs(Div, { className: "rounded border border-red-400/40 bg-
|
|
87
|
+
return (_jsxs(Stack, { gap: "md", children: [_jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Heading, { level: 3, children: ["Prize Items (", items.length, ")"] }), _jsxs(Button, { type: "button", variant: "secondary", onClick: addItem, disabled: anyWon || items.length >= MAX_ITEMS, children: ["+ Add prize (", items.length, "/", MAX_ITEMS, ")"] })] }), _jsxs(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: ["Add between ", MIN_ITEMS, " and ", MAX_ITEMS, " prizes. Each entry will reveal exactly one of these. Items marked won during a prior reveal cannot be edited or removed."] }), anyWon ? (_jsxs(Div, { className: "rounded border border-red-400/40 bg-error-surface px-3 py-2 text-sm text-error", children: [_jsx(Span, { weight: "bold", children: "Draw locked." }), " At least one prize has been revealed \u2014 this listing can no longer be edited. To run a similar draw, clone it into a new prize-draw listing."] })) : warning ? (_jsx(Div, { className: "rounded border border-yellow-400/40 bg-yellow-50 px-3 py-2 text-sm text-yellow-900 dark:bg-yellow-900/30 dark:text-yellow-100", children: warning })) : null, _jsx(Stack, { gap: "md", children: items.map((it, index) => {
|
|
88
88
|
const locked = it.isWon || anyWon;
|
|
89
89
|
return (_jsxs(Div, { className: `relative rounded-lg border border-[var(--appkit-color-border)] p-4 ${locked ? "opacity-60" : ""}`, children: [locked ? (_jsx(Div, { className: "absolute inset-0 z-10 flex items-center justify-center bg-black/10 dark:bg-black/40 rounded-lg pointer-events-none", children: _jsx(Text, { className: "rounded bg-red-600 px-3 py-1 text-xs font-semibold uppercase tracking-wide text-white", children: "Won \u2014 locked" }) })) : null, _jsxs(Row, { justify: "between", align: "center", className: "mb-3", children: [_jsxs(Heading, { level: 4, children: ["Prize #", it.itemNumber] }), _jsxs(Row, { gap: "xs", children: [_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => move(index, -1), disabled: locked || index === 0, "aria-label": "Move up", children: "\u2191" }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => move(index, 1), disabled: locked || index === items.length - 1, "aria-label": "Move down", children: "\u2193" }), _jsx(Button, { type: "button", variant: "danger", size: "sm", onClick: () => removeItem(index), disabled: locked || items.length <= MIN_ITEMS, children: "Remove" })] })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(FormField, { name: `item-${index}-title`, label: "Title", type: "text", value: it.title, onChange: (v) => update(index, { title: v }), disabled: locked, placeholder: "e.g. PSA 9 Charizard Base Set Holo" }), _jsx(FormField, { name: `item-${index}-description`, label: "Description (optional)", type: "textarea", value: it.description ?? "", onChange: (v) => update(index, { description: v }), disabled: locked, placeholder: "What makes this prize special?" }), _jsxs(Row, { gap: "md", children: [_jsx(Div, { className: "flex-1", children: _jsx(FormField, { name: `item-${index}-condition`, label: "Condition", type: "select", value: it.condition, onChange: (v) => update(index, { condition: v }), disabled: locked, options: CONDITION_OPTIONS }) }), _jsx(Div, { className: "flex-1", children: _jsx(FormField, { name: `item-${index}-value`, label: "Estimated value (\u20B9)", type: "number", value: it.estimatedValue != null
|
|
90
90
|
? String(Math.round(it.estimatedValue / 100))
|
|
@@ -94,6 +94,6 @@ export function PrizeDrawItemsEditor({ items, onChange, onUploadImage, onUploadV
|
|
|
94
94
|
const existing = (it.images ?? [])[slot];
|
|
95
95
|
return (_jsxs(Div, { className: "w-40", children: [_jsx(ImageUpload, { currentImage: existing, label: existing ? "Replace" : `Image ${slot + 1}`, onUpload: (file) => onUploadImage(file, it.itemNumber), onChange: (url) => setImage(index, slot, url) }), existing && !locked ? (_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => removeImage(index, slot), children: "Remove" })) : null] }, slot));
|
|
96
96
|
}) })] }), onUploadVideo ? (_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { className: "text-sm font-medium", children: "Video (optional)" }), _jsx("input", { type: "file", accept: "video/*", disabled: locked, onChange: (e) => handleVideoFileChange(e, index, it.itemNumber) }), it.video?.url ? (_jsxs(Text, { className: "text-xs text-[var(--appkit-color-text-muted)] truncate", children: ["Current: ", it.video.url] })) : null] })) : null] })] }, `prize-item-${it.itemNumber}-${index}`));
|
|
97
|
-
}) }), items.length < MIN_ITEMS ? (_jsxs(Div, { className: "rounded border border-red-400/40 bg-
|
|
97
|
+
}) }), items.length < MIN_ITEMS ? (_jsxs(Div, { className: "rounded border border-red-400/40 bg-error-surface px-3 py-2 text-sm text-error", children: ["At least ", MIN_ITEMS, " prizes are required."] })) : null] }));
|
|
98
98
|
}
|
|
99
99
|
export default PrizeDrawItemsEditor;
|
|
@@ -15,7 +15,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
15
15
|
* nor LetItRip admins can influence the outcome.
|
|
16
16
|
*/
|
|
17
17
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
18
|
-
import { Button, Div, Heading, LoginRequiredModal, Modal, Stack, Text, useToast } from "../../../ui";
|
|
18
|
+
import { Button, Div, Heading, LoginRequiredModal, Modal, Span, Stack, Text, useToast } from "../../../ui";
|
|
19
19
|
import { isAuthError } from "../../../utils/auth-error";
|
|
20
20
|
import { PrizeDrawCollage } from "./PrizeDrawCollage";
|
|
21
21
|
const REVEAL_DURATION_MS = 3200; // total animation length
|
|
@@ -143,8 +143,8 @@ export function PrizeRevealModal({ open, onClose, items, orderId, productId, onR
|
|
|
143
143
|
}, REVEAL_DURATION_MS);
|
|
144
144
|
}, [onReveal, orderId, phase, productId, startAnimation]);
|
|
145
145
|
const winnerImg = winner?.images?.[0];
|
|
146
|
-
return (_jsxs(Modal, { isOpen: open, onClose: onClose, title: "Prize Reveal", size: "lg", children: [_jsx(LoginRequiredModal, { isOpen: showLoginModal, onClose: () => setShowLoginModal(false), message: "You need to be signed in to reveal your prize. Please log in or create an account to continue." }), _jsxs(Stack, { gap: "md", children: [phase === "refunded" ? (_jsxs(Div, { className: "rounded border border-yellow-400/40 bg-yellow-50 px-4 py-3 text-yellow-900 dark:bg-yellow-900/30 dark:text-yellow-100", children: [_jsx(Heading, { level: 3, className: "mb-1", children: "Pool exhausted \u2014 you've been refunded" }), _jsx(Text, { className: "text-sm", children: "Every prize in this draw was already claimed by the time your entry rolled. Your order has been marked refunded automatically." })] })) : null, phase === "error" ? (_jsxs(Div, { className: "rounded border border-red-400/40 bg-
|
|
146
|
+
return (_jsxs(Modal, { isOpen: open, onClose: onClose, title: "Prize Reveal", size: "lg", children: [_jsx(LoginRequiredModal, { isOpen: showLoginModal, onClose: () => setShowLoginModal(false), message: "You need to be signed in to reveal your prize. Please log in or create an account to continue." }), _jsxs(Stack, { gap: "md", children: [phase === "refunded" ? (_jsxs(Div, { className: "rounded border border-yellow-400/40 bg-yellow-50 px-4 py-3 text-yellow-900 dark:bg-yellow-900/30 dark:text-yellow-100", children: [_jsx(Heading, { level: 3, className: "mb-1", children: "Pool exhausted \u2014 you've been refunded" }), _jsx(Text, { className: "text-sm", children: "Every prize in this draw was already claimed by the time your entry rolled. Your order has been marked refunded automatically." })] })) : null, phase === "error" ? (_jsxs(Div, { className: "rounded border border-red-400/40 bg-error-surface px-4 py-3 text-error", children: [_jsx(Text, { className: "text-sm font-semibold", children: "Something went wrong" }), _jsx(Text, { className: "text-sm", children: errorMessage })] })) : null, _jsx(PrizeDrawCollage, { items: items, hideWonState: true, highlightItemNumber: highlight }), phase === "idle" ? (_jsx(Button, { type: "button", variant: "primary", size: "lg", onClick: handleRevealClick, children: "\u2728 Reveal my prize" })) : null, phase === "revealing" ? (_jsxs(Div, { className: "rounded bg-[var(--appkit-color-surface-muted)] p-4 text-center", children: [_jsx(Text, { className: "text-lg font-semibold", children: "Rolling\u2026" }), _jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: "The winner was locked by the server before this animation started. Hang tight \u2014 we're just making it look pretty." })] })) : null, phase === "won" && winner ? (_jsxs(Div, { className: "rounded-lg border-2 border-[var(--appkit-color-primary)] bg-[var(--appkit-color-surface)] p-4 text-center", children: [_jsx(Text, { className: "text-xs uppercase tracking-wider text-[var(--appkit-color-text-muted)]", children: "You won" }), _jsxs(Heading, { level: 2, className: "my-2", children: ["#", winner.itemNumber, " \u2014 ", winner.title] }), winnerImg ? (
|
|
147
147
|
/* eslint-disable-next-line @next/next/no-img-element */
|
|
148
|
-
_jsx("img", { src: winnerImg, alt: winner.title, className: "mx-auto max-h-64 rounded" })) : null, winner.estimatedValue != null ? (_jsxs(Text, { className: "mt-2 text-sm text-[var(--appkit-color-text-muted)]", children: ["Estimated value: \u20B9", (winner.estimatedValue / 100).toLocaleString("en-IN")] })) : null] })) : null, _jsxs(Div, { className: "rounded border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface-muted)] px-3 py-2 text-xs text-[var(--appkit-color-text-muted)]", children: [_jsx("
|
|
148
|
+
_jsx("img", { src: winnerImg, alt: winner.title, className: "mx-auto max-h-64 rounded" })) : null, winner.estimatedValue != null ? (_jsxs(Text, { className: "mt-2 text-sm text-[var(--appkit-color-text-muted)]", children: ["Estimated value: \u20B9", (winner.estimatedValue / 100).toLocaleString("en-IN")] })) : null] })) : null, _jsxs(Div, { className: "rounded border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface-muted)] px-3 py-2 text-xs text-[var(--appkit-color-text-muted)]", children: [_jsx(Span, { weight: "bold", className: "text-[var(--appkit-color-text)]", children: "Fairness guarantee:" }), " ", "Winners are picked by ", _jsx("code", { children: "crypto.randomInt" }), " running on LetItRip's server before the animation starts. The animation is theatrical \u2014 neither the store nor LetItRip staff can influence the outcome.", " ", effectiveRngUrl ? (_jsx("a", { href: effectiveRngUrl, target: "_blank", rel: "noreferrer noopener", className: "underline", children: "View RNG source code \u2192" })) : null] })] })] }));
|
|
149
149
|
}
|
|
150
150
|
export default PrizeRevealModal;
|
|
@@ -241,8 +241,8 @@ export async function ProductDetailPageView({ slug, initialProduct, renderOfferA
|
|
|
241
241
|
: condition === "used"
|
|
242
242
|
? "Used"
|
|
243
243
|
: condition })), _jsx(Heading, { level: 1, className: "text-xl font-bold leading-snug text-zinc-900 dark:text-zinc-50 sm:text-2xl", children: title || "Untitled Product" })] }), avgRating !== null ? (_jsxs(Row, { align: "center", gap: "sm", children: [_jsx(StarRating, { value: avgRating }), _jsxs(Span, { className: "text-sm text-zinc-600 dark:text-zinc-400", children: [avgRating.toFixed(1), reviewCount ? ` (${reviewCount} reviews)` : ""] }), _jsxs(Span, { className: `ml-auto rounded-full px-2.5 py-0.5 text-xs font-medium ${inStock
|
|
244
|
-
? "bg-
|
|
245
|
-
: "bg-
|
|
244
|
+
? "bg-success-surface text-success"
|
|
245
|
+
: "bg-error-surface text-error"}`, children: [inStock ? "✓ In Stock" : "✗ Out of Stock", inStock && effectiveStock !== null && effectiveStock <= 10
|
|
246
246
|
? ` — only ${effectiveStock} left`
|
|
247
247
|
: ""] })] })) : (_jsxs(Span, { className: `inline-flex w-fit items-center gap-1.5 rounded-full px-3 py-1 text-xs font-medium ${inStock
|
|
248
248
|
? "bg-emerald-50 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400"
|
|
@@ -262,7 +262,7 @@ export async function ProductDetailPageView({ slug, initialProduct, renderOfferA
|
|
|
262
262
|
codAvailable: "Cash on Delivery",
|
|
263
263
|
wishlistCount: (n) => `${n} wishlisted`,
|
|
264
264
|
categoryProductCount: (n, cat) => `${n} in ${cat}`,
|
|
265
|
-
} }), productFeatures && features.length > 0 && (_jsx(FeatureBadgeList, { productFeatureIds: features, features: productFeatures })), !productFeatures && features.length > 0 && (_jsxs(Div, { className: "rounded-xl border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/60 px-4 py-3", children: [_jsx(Text, { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-zinc-500 dark:text-zinc-400", children: "About this product" }), _jsx("ul", { className: "space-y-1.5", children: features.map((f, i) => (_jsxs("li", { className: "flex items-start gap-2 text-sm text-zinc-700 dark:text-zinc-300", children: [_jsx(Span, { className: "mt-0.5 flex-shrink-0 text-primary-500", children: "\u2022" }), f] }, i))) })] })), descriptionHtml && (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "text-sm leading-relaxed text-zinc-600 dark:text-zinc-400 line-clamp-4" })), safeSeller && (_jsx(Div, { className: "rounded-xl border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/60 p-3", children: _jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Div, { children: [_jsx(Text, { className: "text-[10px] uppercase tracking-wide text-zinc-400 dark:text-zinc-400 mb-0.5", children: "Sold by" }), _jsx(Text, { className: "text-sm font-semibold text-zinc-800 dark:text-zinc-200", children: safeSeller })] }), storeHref && (_jsx(Link, { href: storeHref, className: "shrink-0 rounded-lg bg-primary/10 dark:bg-primary/20 px-3 py-1.5 text-xs font-semibold text-primary-700 dark:text-primary-300 hover:bg-primary/20 dark:hover:bg-primary/30 transition-colors", children: "Visit Store \u2192" }))] }) }))] })), renderActions: () => (_jsxs(Div, { className: "rounded-xl border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/60 p-5 space-y-4", children: [formattedPrice && (_jsxs(Div, { children: [_jsxs(Row, { align: "baseline", gap: "sm", wrap: true, children: [_jsx(Text, { className: "text-2xl font-bold text-zinc-900 dark:text-zinc-50", children: formattedPrice }), formattedOriginal && discount && (_jsxs(_Fragment, { children: [_jsx(Span, { className: "text-sm text-zinc-400 line-through dark:text-zinc-400", children: formattedOriginal }), _jsxs(Span, { className: "rounded-full bg-red-500 px-2 py-0.5 text-xs font-bold text-white", children: ["-", discount, "%"] })] }))] }), inStock && effectiveStock !== null && effectiveStock <= 10 && (_jsxs(Text, { className: "mt-1 text-xs text-
|
|
265
|
+
} }), productFeatures && features.length > 0 && (_jsx(FeatureBadgeList, { productFeatureIds: features, features: productFeatures })), !productFeatures && features.length > 0 && (_jsxs(Div, { className: "rounded-xl border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/60 px-4 py-3", children: [_jsx(Text, { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-zinc-500 dark:text-zinc-400", children: "About this product" }), _jsx("ul", { className: "space-y-1.5", children: features.map((f, i) => (_jsxs("li", { className: "flex items-start gap-2 text-sm text-zinc-700 dark:text-zinc-300", children: [_jsx(Span, { className: "mt-0.5 flex-shrink-0 text-primary-500", children: "\u2022" }), f] }, i))) })] })), descriptionHtml && (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "text-sm leading-relaxed text-zinc-600 dark:text-zinc-400 line-clamp-4" })), safeSeller && (_jsx(Div, { className: "rounded-xl border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/60 p-3", children: _jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Div, { children: [_jsx(Text, { className: "text-[10px] uppercase tracking-wide text-zinc-400 dark:text-zinc-400 mb-0.5", children: "Sold by" }), _jsx(Text, { className: "text-sm font-semibold text-zinc-800 dark:text-zinc-200", children: safeSeller })] }), storeHref && (_jsx(Link, { href: storeHref, className: "shrink-0 rounded-lg bg-primary/10 dark:bg-primary/20 px-3 py-1.5 text-xs font-semibold text-primary-700 dark:text-primary-300 hover:bg-primary/20 dark:hover:bg-primary/30 transition-colors", children: "Visit Store \u2192" }))] }) }))] })), renderActions: () => (_jsxs(Div, { className: "rounded-xl border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/60 p-5 space-y-4", children: [formattedPrice && (_jsxs(Div, { children: [_jsxs(Row, { align: "baseline", gap: "sm", wrap: true, children: [_jsx(Text, { className: "text-2xl font-bold text-zinc-900 dark:text-zinc-50", children: formattedPrice }), formattedOriginal && discount && (_jsxs(_Fragment, { children: [_jsx(Span, { className: "text-sm text-zinc-400 line-through dark:text-zinc-400", children: formattedOriginal }), _jsxs(Span, { className: "rounded-full bg-red-500 px-2 py-0.5 text-xs font-bold text-white", children: ["-", discount, "%"] })] }))] }), inStock && effectiveStock !== null && effectiveStock <= 10 && (_jsxs(Text, { className: "mt-1 text-xs text-warning", children: ["Only ", effectiveStock, " left \u2014 order soon!"] }))] })), _jsxs(Stack, { gap: "sm", children: [renderPrimaryActions ? (renderPrimaryActions({
|
|
266
266
|
productId: product.id,
|
|
267
267
|
productSlug: product.slug ?? slug,
|
|
268
268
|
productTitle: product.title,
|
|
@@ -278,7 +278,7 @@ export async function ProductDetailPageView({ slug, initialProduct, renderOfferA
|
|
|
278
278
|
price,
|
|
279
279
|
currency,
|
|
280
280
|
minOfferPercent,
|
|
281
|
-
})] }), (shippingInfo || returnPolicy || freeShipping) && (_jsxs(Div, { className: "border-t border-zinc-200 dark:border-zinc-700 pt-4 space-y-2.5", children: [freeShipping && (_jsxs(Row, { align: "start", gap: "sm", children: [_jsx(Span, { className: "mt-0.5 flex-shrink-0 text-emerald-500", children: "\uD83D\uDE9A" }), _jsxs(Div, { children: [_jsx(Text, { className: "text-xs font-medium text-zinc-700 dark:text-zinc-300", children: "Free Delivery" }), shippingInfo && (_jsx(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400", children: shippingInfo }))] })] })), !freeShipping && shippingInfo && (_jsxs(Row, { align: "start", gap: "sm", children: [_jsx(Span, { className: "mt-0.5 flex-shrink-0 text-zinc-400", children: "\uD83D\uDCE6" }), _jsx(Text, { className: "text-xs text-zinc-600 dark:text-zinc-400", children: shippingInfo })] })), returnPolicy && (_jsxs(Row, { align: "start", gap: "sm", children: [_jsx(Span, { className: "mt-0.5 flex-shrink-0 text-zinc-400", children: "\u21BA" }), _jsx(Text, { className: "text-xs text-zinc-600 dark:text-zinc-400", children: returnPolicy })] }))] })), tags.length > 0 && (_jsxs(Div, { className: "border-t border-zinc-200 dark:border-zinc-700 pt-4", children: [_jsx(Text, { className: "mb-2 text-xs font-medium text-zinc-500 dark:text-zinc-400", children: "Tags" }), _jsx(Row, { wrap: true, gap: "xs", children: tags.map((tag) => (_jsx(Span, { className: "rounded-full bg-zinc-100 dark:bg-zinc-800 px-2.5 py-1 text-xs text-zinc-600 dark:text-zinc-300", children: tag }, tag))) })] })), _jsx(Div, { className: "border-t border-zinc-200 dark:border-zinc-700 pt-4", children: _jsx(Row, { wrap: true, gap: "sm",
|
|
281
|
+
})] }), (shippingInfo || returnPolicy || freeShipping) && (_jsxs(Div, { className: "border-t border-zinc-200 dark:border-zinc-700 pt-4 space-y-2.5", children: [freeShipping && (_jsxs(Row, { align: "start", gap: "sm", children: [_jsx(Span, { className: "mt-0.5 flex-shrink-0 text-emerald-500", children: "\uD83D\uDE9A" }), _jsxs(Div, { children: [_jsx(Text, { className: "text-xs font-medium text-zinc-700 dark:text-zinc-300", children: "Free Delivery" }), shippingInfo && (_jsx(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400", children: shippingInfo }))] })] })), !freeShipping && shippingInfo && (_jsxs(Row, { align: "start", gap: "sm", children: [_jsx(Span, { className: "mt-0.5 flex-shrink-0 text-zinc-400", children: "\uD83D\uDCE6" }), _jsx(Text, { className: "text-xs text-zinc-600 dark:text-zinc-400", children: shippingInfo })] })), returnPolicy && (_jsxs(Row, { align: "start", gap: "sm", children: [_jsx(Span, { className: "mt-0.5 flex-shrink-0 text-zinc-400", children: "\u21BA" }), _jsx(Text, { className: "text-xs text-zinc-600 dark:text-zinc-400", children: returnPolicy })] }))] })), tags.length > 0 && (_jsxs(Div, { className: "border-t border-zinc-200 dark:border-zinc-700 pt-4", children: [_jsx(Text, { className: "mb-2 text-xs font-medium text-zinc-500 dark:text-zinc-400", children: "Tags" }), _jsx(Row, { wrap: true, gap: "xs", children: tags.map((tag) => (_jsx(Span, { className: "rounded-full bg-zinc-100 dark:bg-zinc-800 px-2.5 py-1 text-xs text-zinc-600 dark:text-zinc-300", children: tag }, tag))) })] })), _jsx(Div, { className: "border-t border-zinc-200 dark:border-zinc-700 pt-4", children: _jsx(Row, { wrap: true, gap: "sm", justify: "center", className: "text-center", children: [
|
|
282
282
|
{ icon: "🔒", label: "Secure\nPayment" },
|
|
283
283
|
{ icon: "✓", label: "Verified\nSeller" },
|
|
284
284
|
{ icon: "⭐", label: "Quality\nGuarantee" },
|
|
@@ -12,7 +12,7 @@ const ROW_BASE_CLASS = "flex items-start gap-2 rounded-lg border px-3 py-2 text-
|
|
|
12
12
|
const ROW_CHECKED_CLASS = "border-primary bg-primary/5 dark:bg-primary/10";
|
|
13
13
|
const ROW_DISABLED_CLASS = "border-zinc-200 dark:border-slate-700 opacity-50 cursor-not-allowed";
|
|
14
14
|
const ROW_DEFAULT_CLASS = "border-zinc-200 dark:border-slate-700 hover:bg-zinc-50 dark:hover:bg-slate-800";
|
|
15
|
-
const OVER_LIMIT_BANNER_CLASS = "rounded-lg border border-red-200 dark:border-red-900/60 bg-
|
|
15
|
+
const OVER_LIMIT_BANNER_CLASS = "rounded-lg border border-red-200 dark:border-red-900/60 bg-error-surface px-3 py-2 text-xs text-error";
|
|
16
16
|
const EMPTY_STATE_CLASS = "rounded-lg border border-zinc-200 dark:border-slate-700 bg-white dark:bg-slate-900 px-3 py-4 text-xs text-zinc-500 dark:text-zinc-400 text-center";
|
|
17
17
|
function applyProductType(list, productType) {
|
|
18
18
|
if (productType === "all")
|
|
@@ -62,7 +62,7 @@ export function ProductCard({ product, href, onClick, onAddToWishlist, isWishlis
|
|
|
62
62
|
].join(" "), children: _jsx("svg", { className: "h-4 w-4", fill: isWishlisted ? "currentColor" : "none", stroke: "currentColor", strokeWidth: 2, viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M21 8.25c0-2.485-2.099-4.5-4.688-4.5-1.935 0-3.597 1.126-4.312 2.733-.715-1.607-2.377-2.733-4.313-2.733C5.1 3.75 3 5.765 3 8.25c0 7.22 9 12 9 12s9-4.78 9-12z" }) }) }))] }), _jsxs(Div, { className: "flex flex-1 flex-col p-3 pt-2.5", children: [_jsx(Text, { className: `${THEME_CONSTANTS.utilities.textClamp2} text-sm font-semibold text-zinc-900 dark:text-white leading-snug`, children: product.title }), ((Array.isArray(product.categoryNames) ? product.categoryNames[0] : product.categoryName) || product.brand) && (_jsxs(Row, { className: "mt-1 gap-1 flex-wrap", children: [(Array.isArray(product.categoryNames) ? product.categoryNames[0] : product.categoryName) && (_jsxs(Span, { className: "inline-flex items-center gap-0.5 rounded-full bg-zinc-100 dark:bg-zinc-800 px-1.5 py-0.5 text-[10px] text-zinc-600 dark:text-zinc-400 truncate max-w-[100px]", children: [_jsx("svg", { className: "h-2.5 w-2.5 flex-shrink-0", fill: "none", stroke: "currentColor", strokeWidth: 2, viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M3 7a2 2 0 012-2h14a2 2 0 012 2v10a2 2 0 01-2 2H5a2 2 0 01-2-2V7z" }) }), (Array.isArray(product.categoryNames) ? product.categoryNames[0] : product.categoryName)] })), product.brand && (_jsxs(Span, { className: "inline-flex items-center gap-0.5 rounded-full bg-zinc-100 dark:bg-zinc-800 px-1.5 py-0.5 text-[10px] text-zinc-600 dark:text-zinc-400 truncate max-w-[90px]", children: [_jsx("svg", { className: "h-2.5 w-2.5 flex-shrink-0", fill: "none", stroke: "currentColor", strokeWidth: 2, viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z" }) }), product.brand] }))] })), (() => {
|
|
63
63
|
const seller = safeDisplayName(product.storeName, "");
|
|
64
64
|
return seller ? (_jsxs(Text, { className: "mt-0.5 text-[11px] text-zinc-400 dark:text-zinc-400 truncate", children: ["by ", seller] })) : null;
|
|
65
|
-
})(), product.rating !== undefined && (_jsxs(Row, { className: "mt-1 gap-1
|
|
65
|
+
})(), product.rating !== undefined && (_jsxs(Row, { className: "mt-1 gap-1", children: [_jsx(Span, { className: "text-[11px] text-amber-400", children: "\u2605" }), _jsxs(Span, { className: "text-[11px] text-zinc-500 dark:text-zinc-400", children: [product.rating.toFixed(1), product.reviewCount ? ` (${product.reviewCount})` : ""] })] })), _jsxs(Div, { className: "mt-auto pt-2", children: [_jsxs(Row, { align: "baseline", className: "gap-2", children: [_jsx(Span, { className: "text-base font-bold text-primary dark:text-primary-400", children: formatCurrency(product.price, getDefaultCurrency()) }), product.originalPrice && product.originalPrice > product.price && (_jsx(Span, { className: "text-xs text-zinc-400 line-through dark:text-zinc-400", children: formatCurrency(product.originalPrice, getDefaultCurrency()) }))] }), (() => {
|
|
66
66
|
const stock = product.stockCount ?? product.stockQuantity ?? product.availableQuantity;
|
|
67
67
|
if (stock === undefined || isAuction)
|
|
68
68
|
return null;
|
|
@@ -71,7 +71,7 @@ export function ProductCard({ product, href, onClick, onAddToWishlist, isWishlis
|
|
|
71
71
|
}
|
|
72
72
|
const low = stock <= 5;
|
|
73
73
|
return (_jsx(Text, { className: `mt-1 text-[11px] font-medium ${low
|
|
74
|
-
? "text-
|
|
74
|
+
? "text-warning"
|
|
75
75
|
: "text-zinc-500 dark:text-zinc-400"}`, children: low ? `Only ${stock} left` : `${stock} in stock` }));
|
|
76
76
|
})(), featuresList && product.features && product.features.length > 0 && (_jsx(FeatureBadgeList, { productFeatureIds: product.features, features: featuresList, maxVisible: PRODUCT_FEATURE_CARD_MAX_VISIBLE, className: "mt-2" })), (onAddToCart || onBuyNow) && (_jsxs(Div, { className: "mt-2 grid gap-1.5", style: { gridTemplateColumns: onBuyNow && onAddToCart ? "1fr 1fr" : "1fr" }, children: [onBuyNow && (_jsxs(Button, { type: "button", variant: "primary", size: "sm", onClick: (e) => {
|
|
77
77
|
e.stopPropagation();
|
|
@@ -113,7 +113,7 @@ function ProductListRow({ product, onClick, onAddToWishlist, isWishlisted, }) {
|
|
|
113
113
|
}
|
|
114
114
|
const low = stock <= 5;
|
|
115
115
|
return (_jsx(Span, { className: `text-[11px] font-medium ${low
|
|
116
|
-
? "text-
|
|
116
|
+
? "text-warning"
|
|
117
117
|
: "text-zinc-400 dark:text-zinc-400"}`, children: low ? `Only ${stock} left` : `${stock} in stock` }));
|
|
118
118
|
})()] })] }), onAddToWishlist && (_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: (e) => {
|
|
119
119
|
e.stopPropagation();
|
|
@@ -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);
|
|
@@ -158,8 +158,8 @@ export function CouponCard({ coupon, labels: labelsProp, onCopy, className = "",
|
|
|
158
158
|
}
|
|
159
159
|
};
|
|
160
160
|
return (_jsxs(Div, { className: `group relative flex h-full flex-col rounded-xl border-2 p-4 ${colors.card} ${isSelected ? "ring-2 ring-primary" : ""} ${!n.isActive ? "opacity-70" : ""} ${className}`, onMouseDown: onSelect && !isSelected ? longPress.onMouseDown : undefined, onMouseUp: onSelect && !isSelected ? longPress.onMouseUp : undefined, onMouseLeave: onSelect && !isSelected ? longPress.onMouseLeave : undefined, onTouchStart: onSelect && !isSelected ? longPress.onTouchStart : undefined, onTouchEnd: onSelect && !isSelected ? longPress.onTouchEnd : undefined, children: [onSelect && (_jsx(BaseListingCard.Checkbox, { selected: isSelected, onSelect: (e) => { e.preventDefault(); onSelect(n.id, !isSelected); }, label: isSelected ? "Deselect coupon" : "Select coupon", position: "top-2 right-2", className: selectable || isSelected ? "opacity-100" : "opacity-0 group-hover:opacity-100 transition-opacity" })), (hasAdminActions || n.scope) && (_jsxs(Row, { gap: "xs", className: "mb-2 flex-wrap", children: [_jsx(Span, { className: `inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide ${n.isActive
|
|
161
|
-
? "bg-
|
|
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", className: "mt-auto pt-3
|
|
161
|
+
? "bg-success-surface text-success"
|
|
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
|
}
|
|
@@ -11,5 +11,5 @@ export function PromotionsHero({ labels, heroBannerClass = "bg-gradient-to-br fr
|
|
|
11
11
|
}
|
|
12
12
|
/** @deprecated Use PromotionsHero + per-tab content in your page instead. */
|
|
13
13
|
export function PromotionsView({ labels, hasContent, heroBannerClass = "bg-gradient-to-br from-rose-500 to-orange-500", renderCoupons, couponsCount = 0, renderDealsSection, renderFeaturedSection, }) {
|
|
14
|
-
return (_jsxs(Div, { className: "min-h-screen
|
|
14
|
+
return (_jsxs(Div, { surface: "default", className: "min-h-screen", children: [_jsx(PromotionsHero, { labels: labels, heroBannerClass: heroBannerClass }), _jsxs(Div, { className: "max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 py-12 space-y-12", children: [!hasContent && (_jsxs(Div, { className: "text-center py-16", children: [_jsx(Heading, { level: 2, className: "mb-2", children: labels.emptyDeals }), _jsx(Text, { variant: "secondary", children: labels.checkBack })] })), hasContent && (_jsxs(Div, { className: "space-y-12", children: [renderCoupons && (_jsxs(Section, { children: [_jsxs(Div, { className: "mb-6", children: [_jsx(Heading, { level: 2, children: labels.couponsTitle }), couponsCount > 0 && (_jsx(Text, { variant: "secondary", className: "mt-1", children: labels.couponsSubtitle }))] }), couponsCount > 0 ? (renderCoupons()) : (_jsx(Text, { variant: "secondary", size: "sm", children: labels.emptyCoupons }))] })), renderDealsSection?.(), renderFeaturedSection?.()] }))] })] }));
|
|
15
15
|
}
|
|
@@ -77,8 +77,8 @@ export function ReviewDetailShell({ review, storeHref }) {
|
|
|
77
77
|
? String(ROUTES.PUBLIC.PROFILE(reviewerProfileId))
|
|
78
78
|
: null;
|
|
79
79
|
const currentImage = lightboxIdx !== null ? images[lightboxIdx] : null;
|
|
80
|
-
return (_jsxs(_Fragment, { children: [_jsx("div", { className: "border-b border-neutral-200 dark:border-zinc-800 bg-white dark:bg-zinc-900 pb-8 pt-10", children: _jsxs("div", { className: "mx-auto max-w-3xl px-4", children: [_jsxs("div", { className: "mb-4 flex items-center gap-3", children: [_jsx(StarRating, { value: review.rating, size: "lg", readOnly: true }), _jsxs("span", { className: "text-2xl font-bold text-neutral-900 dark:text-white", children: [review.rating, ".0"] }), review.verified && (_jsx("span", { className: "inline-flex items-center gap-1 rounded-full bg-
|
|
81
|
-
? "border-emerald-200 bg-
|
|
80
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { className: "border-b border-neutral-200 dark:border-zinc-800 bg-white dark:bg-zinc-900 pb-8 pt-10", children: _jsxs("div", { className: "mx-auto max-w-3xl px-4", children: [_jsxs("div", { className: "mb-4 flex items-center gap-3", children: [_jsx(StarRating, { value: review.rating, size: "lg", readOnly: true }), _jsxs("span", { className: "text-2xl font-bold text-neutral-900 dark:text-white", children: [review.rating, ".0"] }), review.verified && (_jsx("span", { className: "inline-flex items-center gap-1 rounded-full bg-success-surface px-3 py-1 text-xs font-semibold text-success", children: "\u2713 Verified Purchase" })), review.featured && (_jsx("span", { className: "inline-flex items-center gap-1 rounded-full bg-yellow-100 px-3 py-1 text-xs font-semibold text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400", children: "\u2605 Featured" }))] }), review.title && (_jsx(Heading, { level: 1, className: "text-2xl font-bold text-neutral-900 dark:text-white mb-4 leading-snug", children: review.title })), _jsxs("div", { className: "flex items-center gap-3", children: [review.userAvatar ? (_jsx("div", { role: "img", "aria-label": displayName, className: "h-11 w-11 flex-shrink-0 rounded-full bg-center bg-cover ring-2 ring-white dark:ring-zinc-800", style: { backgroundImage: `url(${review.userAvatar})` } })) : (_jsx("div", { className: "flex h-11 w-11 flex-shrink-0 items-center justify-center rounded-full bg-primary/10 text-base font-bold text-primary ring-2 ring-white dark:ring-zinc-800", children: initials })), _jsxs("div", { className: "min-w-0", children: [reviewerHref ? (_jsx(Link, { href: reviewerHref, className: "text-sm font-semibold text-neutral-900 dark:text-white hover:text-primary transition-colors", children: displayName })) : (_jsx("span", { className: "text-sm font-semibold text-neutral-900 dark:text-white", children: review.isAnonymous ? "Anonymous" : displayName })), date && (_jsx(Text, { className: "text-xs text-zinc-400 dark:text-zinc-400 mt-0.5", children: date }))] })] })] }) }), _jsxs("div", { className: "mx-auto max-w-3xl px-4 py-8 space-y-8", children: [review.comment && (_jsx(Section, { children: _jsx(RichText, { html: normalizeRichTextHtml(review.comment), proseClass: "prose prose-neutral dark:prose-invert max-w-none prose-p:leading-relaxed prose-headings:font-semibold prose-img:rounded-lg prose-a:text-primary", className: "text-neutral-700 dark:text-zinc-300" }) })), images.length > 0 && (_jsxs(Section, { children: [_jsxs(Heading, { level: 2, className: "text-sm font-semibold uppercase tracking-wide text-zinc-400 dark:text-zinc-400 mb-3", children: ["Photos (", images.length, ")"] }), _jsx("div", { className: "grid grid-cols-3 sm:grid-cols-4 gap-2", children: images.map((img, i) => (_jsxs("button", { type: "button", onClick: () => setLightboxIdx(i), "aria-label": `View photo ${i + 1}`, className: "group relative aspect-square overflow-hidden rounded-xl border border-neutral-200 dark:border-zinc-700 bg-neutral-100 dark:bg-zinc-800 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary", children: [_jsx("div", { className: "h-full w-full bg-center bg-cover transition-transform duration-300 group-hover:scale-105", style: { backgroundImage: `url(${img.thumbnailUrl ?? img.url})` } }), _jsx("div", { className: "absolute inset-0 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity bg-black/30", children: _jsx("span", { className: "text-white text-xl", children: "\uD83D\uDD0D" }) })] }, i))) })] })), review.video && (_jsxs(Section, { children: [_jsx(Heading, { level: 2, className: "text-sm font-semibold uppercase tracking-wide text-zinc-400 dark:text-zinc-400 mb-3", children: "Video" }), _jsx("div", { className: "overflow-hidden rounded-xl border border-neutral-200 dark:border-zinc-700 bg-black aspect-video", children: _jsx("video", { src: review.video.url, poster: review.video.thumbnailUrl, controls: true, className: "h-full w-full", preload: "metadata" }) })] })), _jsxs(Section, { className: "flex items-center gap-4 py-4 border-t border-neutral-100 dark:border-zinc-800", children: [_jsx("div", { className: "text-sm text-neutral-500 dark:text-zinc-400", children: helpfulCount > 0 && (_jsxs("span", { children: [_jsx(Span, { weight: "bold", className: "text-neutral-900 dark:text-white", children: helpfulCount }), " ", helpfulCount === 1 ? "person" : "people", " found this helpful"] })) }), _jsxs("button", { type: "button", onClick: handleVote, disabled: voted || voting, className: `ml-auto flex items-center gap-2 rounded-lg border px-4 py-2 text-sm font-medium transition-colors ${voted
|
|
81
|
+
? "border-emerald-200 bg-success-surface text-success dark:border-emerald-800 cursor-default"
|
|
82
82
|
: "border-neutral-300 dark:border-zinc-600 text-neutral-700 dark:text-zinc-200 hover:border-primary hover:text-primary dark:hover:border-primary dark:hover:text-primary disabled:opacity-50"}`, children: [_jsx("span", { "aria-hidden": "true", children: voted ? "✓" : "👍" }), voted ? "Marked helpful" : voting ? "Saving…" : "Helpful?"] })] }), _jsxs(Section, { className: "grid gap-3 sm:grid-cols-3", children: [productHref && (_jsxs(Link, { href: productHref, className: CLS_RELATED_LINK, children: [_jsx("span", { className: "flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-lg bg-orange-100 dark:bg-orange-900/30 text-xl", children: "\uD83D\uDCE6" }), _jsxs("div", { className: "min-w-0", children: [_jsx(Text, { className: CLS_RELATED_LABEL, children: "Product" }), _jsx(Text, { className: CLS_RELATED_TITLE, children: review.productTitle ?? "View Product" })] })] })), sellerHref && (_jsxs(Link, { href: sellerHref, className: CLS_RELATED_LINK, children: [_jsx("span", { className: "flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-lg bg-blue-100 dark:bg-blue-900/30 text-xl", children: "\uD83C\uDFEA" }), _jsxs("div", { className: "min-w-0", children: [_jsx(Text, { className: CLS_RELATED_LABEL, children: "Seller" }), _jsx(Text, { className: CLS_RELATED_TITLE, children: "View Seller" })] })] })), reviewerHref ? (_jsxs(Link, { href: reviewerHref, className: CLS_RELATED_LINK, children: [_jsx("span", { className: "flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-lg bg-purple-100 dark:bg-purple-900/30 text-xl", children: "\uD83D\uDC64" }), _jsxs("div", { className: "min-w-0", children: [_jsx(Text, { className: CLS_RELATED_LABEL, children: "Reviewer" }), _jsx(Text, { className: CLS_RELATED_TITLE, children: displayName })] })] })) : (_jsxs("div", { className: "flex items-center gap-3 rounded-xl border border-neutral-200 dark:border-zinc-700 bg-white dark:bg-zinc-900 p-4", children: [_jsx("span", { className: "flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-lg bg-purple-100 dark:bg-purple-900/30 text-xl", children: "\uD83D\uDC64" }), _jsxs("div", { className: "min-w-0", children: [_jsx(Span, { className: "block text-xs text-zinc-400 dark:text-zinc-400 mb-0.5", children: "Reviewer" }), _jsx(Span, { className: "block text-sm font-medium text-neutral-900 dark:text-white truncate", children: "Anonymous" })] })] }))] })] }), lightboxIdx !== null && currentImage && (_jsxs("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/95", onClick: closeLightbox, role: "dialog", "aria-modal": "true", "aria-label": "Image lightbox", children: [_jsx("button", { type: "button", onClick: closeLightbox, "aria-label": "Close lightbox", className: "absolute top-4 right-4 z-10 flex h-10 w-10 items-center justify-center rounded-full bg-white/10 text-white hover:bg-white/20 transition-colors text-xl", children: "\u00D7" }), _jsxs("div", { className: "absolute top-4 left-1/2 -translate-x-1/2 text-white/70 text-sm", children: [lightboxIdx + 1, " / ", images.length] }), images.length > 1 && (_jsx("button", { type: "button", onClick: (e) => { e.stopPropagation(); prevImage(); }, "aria-label": "Previous image", className: "absolute left-4 top-1/2 -translate-y-1/2 z-10 flex h-12 w-12 items-center justify-center rounded-full bg-white/10 text-white hover:bg-white/25 transition-colors text-2xl", children: "\u2039" })), _jsx("div", { className: "max-h-[85vh] max-w-[85vw] flex items-center justify-center", onClick: (e) => e.stopPropagation(), children: _jsx("img", { src: currentImage.url, alt: `Review photo ${lightboxIdx + 1}`, className: "max-h-[85vh] max-w-[85vw] rounded-lg object-contain shadow-2xl" }) }), images.length > 1 && (_jsx("button", { type: "button", onClick: (e) => { e.stopPropagation(); nextImage(); }, "aria-label": "Next image", className: "absolute right-4 top-1/2 -translate-y-1/2 z-10 flex h-12 w-12 items-center justify-center rounded-full bg-white/10 text-white hover:bg-white/25 transition-colors text-2xl", children: "\u203A" })), images.length > 1 && (_jsx("div", { className: "absolute bottom-4 left-0 right-0 flex justify-center gap-2 px-4", children: images.map((img, i) => (_jsx("button", { type: "button", onClick: (e) => { e.stopPropagation(); setLightboxIdx(i); }, "aria-label": `Go to image ${i + 1}`, className: `h-12 w-12 flex-shrink-0 rounded-md bg-center bg-cover border-2 transition-all ${i === lightboxIdx
|
|
83
83
|
? "border-white scale-110"
|
|
84
84
|
: "border-transparent opacity-60 hover:opacity-100"}`, style: { backgroundImage: `url(${img.thumbnailUrl ?? img.url})` } }, i))) }))] }))] }));
|