@mohasinac/appkit 4.1.3 → 4.3.0
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/auth/actions.d.ts +0 -1
- package/dist/_internal/server/features/auth/actions.js +0 -1
- package/dist/_internal/server/features/bundles/data.d.ts +22 -4
- package/dist/_internal/server/features/bundles/data.js +41 -4
- package/dist/_internal/server/features/bundles/index.d.ts +1 -1
- package/dist/_internal/server/features/bundles/index.js +1 -1
- package/dist/_internal/server/features/checkout/actions.d.ts +15 -0
- package/dist/_internal/server/features/checkout/actions.js +249 -257
- package/dist/_internal/server/features/checkout/prize-bundle-gates.d.ts +5 -16
- package/dist/_internal/server/features/checkout/prize-bundle-gates.js +5 -30
- package/dist/_internal/server/features/orders/adapters.js +1 -14
- package/dist/_internal/server/features/products/actions.js +8 -3
- package/dist/_internal/server/features/products/service.d.ts +15 -0
- package/dist/_internal/server/features/products/service.js +41 -0
- package/dist/_internal/server/functions/firestore.d.ts +3 -1
- package/dist/_internal/server/functions/firestore.js +17 -1
- package/dist/_internal/server/functions/scheduled.d.ts +3 -5
- package/dist/_internal/server/functions/scheduled.js +14 -30
- package/dist/_internal/server/jobs/core/bundleStockSync.js +25 -7
- package/dist/_internal/server/jobs/core/countersReconcile.js +7 -1
- package/dist/_internal/server/jobs/core/index.d.ts +4 -4
- package/dist/_internal/server/jobs/core/index.js +4 -4
- package/dist/_internal/server/jobs/core/jobRunners.js +10 -0
- package/dist/_internal/server/jobs/core/newsletterExport.d.ts +15 -0
- package/dist/_internal/server/jobs/core/newsletterExport.js +43 -0
- package/dist/_internal/server/jobs/core/onOrderStatusChange.d.ts +2 -0
- package/dist/_internal/server/jobs/core/onOrderStatusChange.js +1 -0
- package/dist/_internal/server/jobs/core/onPrizeDrawPaymentConfirmed.d.ts +19 -0
- package/dist/_internal/server/jobs/core/onPrizeDrawPaymentConfirmed.js +30 -0
- package/dist/_internal/server/jobs/core/paymentWindowTimeout.d.ts +2 -2
- package/dist/_internal/server/jobs/core/paymentWindowTimeout.js +3 -2
- package/dist/_internal/server/jobs/core/pendingOrderTimeout.js +1 -0
- package/dist/_internal/server/jobs/core/prizeDrawAssignWinner.d.ts +20 -0
- package/dist/_internal/server/jobs/core/prizeDrawAssignWinner.js +113 -0
- package/dist/_internal/server/jobs/core/prizeDrawExpiryReveal.d.ts +14 -0
- package/dist/_internal/server/jobs/core/prizeDrawExpiryReveal.js +74 -0
- package/dist/_internal/server/jobs/core/prizeDrawSoldOutReveal.d.ts +22 -0
- package/dist/_internal/server/jobs/core/prizeDrawSoldOutReveal.js +68 -0
- package/dist/_internal/server/jobs/core/revenueRollup.d.ts +13 -0
- package/dist/_internal/server/jobs/core/revenueRollup.js +22 -0
- package/dist/_internal/server/jobs/core/whatsappNotify.d.ts +35 -0
- package/dist/_internal/server/jobs/core/whatsappNotify.js +82 -0
- package/dist/_internal/server/jobs/handlers/index.d.ts +4 -4
- package/dist/_internal/server/jobs/handlers/index.js +9 -6
- package/dist/_internal/server/jobs/handlers/onPrizeDrawPaymentConfirmed.d.ts +3 -0
- package/dist/_internal/server/jobs/handlers/onPrizeDrawPaymentConfirmed.js +11 -0
- package/dist/_internal/server/jobs/handlers/prizeDrawExpiryReveal.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/prizeDrawExpiryReveal.js +2 -0
- package/dist/_internal/server/jobs/handlers/prizeDrawSoldOutReveal.d.ts +3 -0
- package/dist/_internal/server/jobs/handlers/prizeDrawSoldOutReveal.js +4 -0
- package/dist/_internal/server/jobs/handlers/revenueRollup.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/revenueRollup.js +2 -0
- package/dist/_internal/shared/actions/action-registry.js +40 -10
- package/dist/_internal/shared/checkout/rules/_defaults.js +1 -2
- package/dist/_internal/shared/checkout/rules/_limits.d.ts +3 -0
- package/dist/_internal/shared/checkout/rules/_limits.js +3 -0
- package/dist/_internal/shared/checkout/rules/_registry.d.ts +2 -1
- package/dist/_internal/shared/checkout/rules/_registry.js +2 -2
- package/dist/_internal/shared/checkout/rules/classified.rule.js +0 -1
- package/dist/_internal/shared/checkout/rules/index.d.ts +1 -1
- package/dist/_internal/shared/checkout/rules/index.js +1 -1
- package/dist/_internal/shared/checkout/rules/prize-draw.rule.js +20 -1
- package/dist/_internal/shared/checkout/rules/types.d.ts +17 -7
- package/dist/_internal/shared/features/categories/bundle-copy.d.ts +2 -0
- package/dist/_internal/shared/features/categories/bundle-copy.js +2 -0
- package/dist/_internal/shared/features/categories/bundle-pricing.d.ts +23 -0
- package/dist/_internal/shared/features/categories/bundle-pricing.js +25 -0
- package/dist/_internal/shared/features/checkout/config.d.ts +7 -0
- package/dist/_internal/shared/features/checkout/config.js +7 -0
- package/dist/_internal/shared/fees/calculator.d.ts +26 -0
- package/dist/_internal/shared/fees/calculator.js +25 -0
- package/dist/_internal/shared/listing-types/prize-draw/config.js +1 -1
- package/dist/constants/api-endpoints.d.ts +6 -9
- package/dist/constants/api-endpoints.js +5 -5
- package/dist/constants/field-names.d.ts +17 -1
- package/dist/constants/field-names.js +17 -1
- package/dist/features/about/components/FeesView.js +18 -0
- package/dist/features/account/hooks/useNotifications.js +5 -0
- package/dist/features/admin/actions/admin-actions.js +6 -0
- package/dist/features/admin/actions/notification-actions.d.ts +4 -1
- package/dist/features/admin/actions/notification-actions.js +57 -12
- package/dist/features/admin/components/AdminAdsView.js +1 -1
- package/dist/features/admin/components/AdminBlogEditorView.js +1 -1
- package/dist/features/admin/components/AdminNewsletterView.js +42 -11
- package/dist/features/admin/components/AdminPrizeDrawsView.js +3 -3
- package/dist/features/admin/components/AdminSiteSettingsView.js +115 -11
- package/dist/features/admin/components/AdminTesterChecklistView.js +74 -22
- package/dist/features/admin/hooks/useAdminListing.d.ts +4 -0
- package/dist/features/admin/hooks/useAdminListing.js +4 -2
- package/dist/features/admin/repository/analytics-rollup.repository.d.ts +25 -0
- package/dist/features/admin/repository/analytics-rollup.repository.js +31 -0
- package/dist/features/admin/repository/site-settings.repository.js +8 -1
- package/dist/features/admin/schemas/firestore.d.ts +54 -5
- package/dist/features/admin/schemas/firestore.js +63 -26
- package/dist/features/auctions/actions/bid-actions.js +3 -1
- package/dist/features/auctions/components/AuctionBidsTable.d.ts +6 -1
- package/dist/features/auctions/components/AuctionBidsTable.js +8 -4
- package/dist/features/auctions/components/AuctionDetailPageView.js +10 -13
- package/dist/features/auctions/components/CollapsibleBidHistory.d.ts +23 -2
- package/dist/features/auctions/components/CollapsibleBidHistory.js +21 -3
- package/dist/features/auctions/components/LiveBidPrice.d.ts +19 -0
- package/dist/features/auctions/components/LiveBidPrice.js +17 -0
- package/dist/features/auctions/components/PlaceBidFormClient.d.ts +1 -1
- package/dist/features/auctions/components/PlaceBidFormClient.js +16 -1
- package/dist/features/auctions/hooks/useBids.d.ts +33 -0
- package/dist/features/auctions/hooks/useBids.js +32 -0
- package/dist/features/auctions/hooks/useLiveAuctionBid.d.ts +20 -0
- package/dist/features/auctions/hooks/useLiveAuctionBid.js +23 -0
- package/dist/features/auth/checkout-value-otp.d.ts +7 -10
- package/dist/features/auth/checkout-value-otp.js +14 -10
- package/dist/features/auth/server.d.ts +0 -1
- package/dist/features/auth/server.js +0 -1
- package/dist/features/blog/api/[slug]/route.d.ts +5 -0
- package/dist/features/blog/api/[slug]/route.js +12 -3
- package/dist/features/blog/components/BlogPostView.d.ts +2 -0
- package/dist/features/blog/components/BlogPostView.js +5 -4
- package/dist/features/blog/hooks/useBlog.d.ts +2 -0
- package/dist/features/blog/hooks/useBlog.js +2 -0
- package/dist/features/blog/repository/blog.repository.d.ts +7 -0
- package/dist/features/blog/repository/blog.repository.js +44 -0
- package/dist/features/blog/schemas/firestore.d.ts +3 -0
- package/dist/features/blog/schemas/firestore.js +1 -0
- package/dist/features/blog/types/index.d.ts +2 -0
- package/dist/features/cart/components/index.d.ts +0 -2
- package/dist/features/cart/components/index.js +0 -1
- package/dist/features/cart/hooks/useCartCount.js +22 -5
- package/dist/features/cart/hooks/useGuestCart.js +11 -1
- package/dist/features/cart/utils/guest-cart.d.ts +4 -0
- package/dist/features/cart/utils/guest-cart.js +10 -0
- package/dist/features/cart/utils/pending-ops.d.ts +17 -0
- package/dist/features/cart/utils/pending-ops.js +46 -5
- package/dist/features/categories/components/BrandDetailPageView.js +6 -2
- package/dist/features/categories/components/BundleDetailView.js +4 -2
- package/dist/features/categories/components/CategoryDetailPageView.js +10 -2
- package/dist/features/categories/schemas/firestore.d.ts +7 -0
- package/dist/features/checkout/actions/index.d.ts +0 -1
- package/dist/features/checkout/actions/index.js +0 -1
- package/dist/features/checkout/schemas/firestore.d.ts +2 -2
- package/dist/features/events/actions/event-actions.d.ts +2 -0
- package/dist/features/events/actions/event-actions.js +5 -0
- package/dist/features/events/components/EventCard.js +1 -2
- package/dist/features/events/components/RelatedEventsCarousel.d.ts +15 -0
- package/dist/features/events/components/RelatedEventsCarousel.js +13 -0
- package/dist/features/events/components/index.d.ts +2 -0
- package/dist/features/events/components/index.js +1 -0
- package/dist/features/events/repository/events.repository.d.ts +6 -0
- package/dist/features/events/repository/events.repository.js +21 -0
- package/dist/features/history/repository/user-history.repository.d.ts +1 -1
- package/dist/features/history/utils/guest-history.d.ts +1 -1
- package/dist/features/homepage/components/FeaturedBundlesSection.js +4 -2
- package/dist/features/homepage/components/FeaturedProductsSection.js +2 -2
- package/dist/features/homepage/components/SectionCarousel.js +1 -1
- package/dist/features/homepage/components/WelcomeSection.js +1 -1
- package/dist/features/layout/AppLayoutShell.js +1 -1
- package/dist/features/layout/BackToTop.d.ts +9 -5
- package/dist/features/layout/BackToTop.js +17 -21
- package/dist/features/layout/NavbarLayout.js +1 -1
- package/dist/features/layout/TitleBarLayout.js +2 -2
- package/dist/features/media/MediaVideo.d.ts +7 -0
- package/dist/features/media/MediaVideo.js +36 -5
- package/dist/features/orders/actions/refund-actions.js +1 -0
- package/dist/features/orders/components/OrdersList.js +1 -5
- package/dist/features/orders/schemas/firestore.d.ts +16 -8
- package/dist/features/orders/schemas/index.d.ts +3 -32
- package/dist/features/orders/schemas/index.js +1 -4
- package/dist/features/orders/types/index.d.ts +6 -9
- package/dist/features/pre-orders/components/MarketplacePreorderCard.js +1 -1
- package/dist/features/pre-orders/components/PreOrderDetailPageView.js +2 -1
- package/dist/features/products/actions/product-actions.js +13 -10
- package/dist/features/products/components/MarketplaceBundleCard.d.ts +1 -1
- package/dist/features/products/components/MarketplaceBundleCard.js +7 -4
- package/dist/features/products/components/PrizeDrawDetailPageView.js +1 -1
- package/dist/features/products/components/PrizeDrawItemsEditor.js +2 -2
- package/dist/features/products/components/PrizeDrawWinnerMappingView.d.ts +14 -0
- package/dist/features/products/components/PrizeDrawWinnerMappingView.js +37 -0
- package/dist/features/products/components/PrizeRevealModal.d.ts +10 -23
- package/dist/features/products/components/PrizeRevealModal.js +18 -147
- package/dist/features/products/components/ProductDetailPageView.js +72 -30
- package/dist/features/products/components/ProductForm.js +7 -14
- package/dist/features/products/components/ProductGalleryClient.d.ts +10 -1
- package/dist/features/products/components/ProductGalleryClient.js +29 -10
- package/dist/features/products/components/ProductsIndexListing.js +2 -1
- package/dist/features/products/components/RelatedProductsCarousel.js +2 -2
- package/dist/features/products/components/ShowGroupSection.js +2 -2
- package/dist/features/products/components/SublistingCarouselSection.js +1 -1
- package/dist/features/products/components/index.d.ts +4 -2
- package/dist/features/products/components/index.js +1 -0
- package/dist/features/products/constants/action-defs.d.ts +28 -4
- package/dist/features/products/constants/action-defs.js +76 -3
- package/dist/features/products/repository/products.repository.d.ts +6 -0
- package/dist/features/products/repository/products.repository.js +20 -1
- package/dist/features/products/schemas/firestore.d.ts +26 -2
- package/dist/features/products/schemas/index.d.ts +11 -3
- package/dist/features/products/schemas/index.js +10 -2
- package/dist/features/products/types/index.d.ts +2 -1
- package/dist/features/reviews/actions/review-actions.js +1 -0
- package/dist/features/reviews/components/ReviewDetailPageView.js +8 -1
- package/dist/features/reviews/components/ReviewDetailShell.js +2 -1
- package/dist/features/reviews/components/ReviewsList.js +2 -1
- package/dist/features/reviews/schemas/firestore.d.ts +3 -0
- package/dist/features/reviews/schemas/index.d.ts +6 -0
- package/dist/features/reviews/schemas/index.js +2 -0
- package/dist/features/reviews/types/index.d.ts +3 -0
- package/dist/features/scams/actions/scam-actions.d.ts +3 -0
- package/dist/features/scams/actions/scam-actions.js +3 -2
- package/dist/features/scams/components/ScamProfileView.d.ts +2 -1
- package/dist/features/scams/components/ScamProfileView.js +2 -2
- package/dist/features/scams/repository/scammer.repository.d.ts +6 -0
- package/dist/features/scams/repository/scammer.repository.js +24 -0
- package/dist/features/seller/actions/seller-actions.js +8 -0
- package/dist/features/seller/components/SellerOrdersView.js +1 -1
- package/dist/features/seller/components/SellerPrizeDrawsView.js +4 -0
- package/dist/features/stores/components/StoreDetailLayoutView.js +6 -6
- package/dist/features/stores/components/StoreNavTabs.d.ts +10 -1
- package/dist/features/stores/components/StoreNavTabs.js +21 -9
- package/dist/features/tester/actions/index.d.ts +1 -0
- package/dist/features/tester/actions/index.js +1 -0
- package/dist/features/tester/actions/leaderboard-actions.d.ts +3 -0
- package/dist/features/tester/actions/leaderboard-actions.js +5 -0
- package/dist/features/tester/components/AdminTesterFeedbackIssuesView.js +26 -4
- package/dist/features/tester/components/AdminTesterFeedbackListView.js +13 -0
- package/dist/features/tester/components/BugHunterLeaderboardView.d.ts +23 -0
- package/dist/features/tester/components/BugHunterLeaderboardView.js +18 -0
- package/dist/features/tester/components/index.d.ts +2 -0
- package/dist/features/tester/components/index.js +1 -0
- package/dist/features/tester/repository/tester-checklist-item.repository.d.ts +12 -1
- package/dist/features/tester/repository/tester-checklist-item.repository.js +80 -0
- package/dist/features/tester/repository/tester-checklist-response.repository.d.ts +4 -0
- package/dist/features/tester/repository/tester-checklist-response.repository.js +4 -0
- package/dist/features/tester/schemas/firestore.d.ts +22 -1
- package/dist/features/tester/schemas/firestore.js +8 -0
- package/dist/features/tester/seed-data/categories-tester-seed-data.js +42 -7
- package/dist/features/tester/seed-data/events-tester-seed-data.js +23 -0
- package/dist/features/tester/seed-data/orders-tester-seed-data.js +69 -31
- package/dist/features/tester/seed-data/products-tester-seed-data.js +152 -8
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +249 -14
- package/dist/features/whatsapp-bot/helpers/whatsapp.d.ts +10 -1
- package/dist/features/whatsapp-bot/helpers/whatsapp.js +47 -0
- package/dist/features/whatsapp-bot/types/index.d.ts +19 -0
- package/dist/features/wishlist/hooks/useGuestWishlist.js +11 -1
- package/dist/features/wishlist/hooks/useWishlistCount.js +16 -2
- package/dist/features/wishlist/utils/guest-wishlist.d.ts +5 -0
- package/dist/features/wishlist/utils/guest-wishlist.js +11 -0
- package/dist/index.d.ts +23 -24
- package/dist/index.js +29 -60
- package/dist/jobs.d.ts +1 -1
- package/dist/jobs.js +2 -2
- package/dist/next/routing/route-map.d.ts +4 -0
- package/dist/next/routing/route-map.js +2 -0
- package/dist/repositories/index.d.ts +2 -0
- package/dist/repositories/index.js +1 -0
- package/dist/schemas/registry.d.ts +6 -26
- package/dist/seed/bids-seed-data.js +14 -2
- package/dist/seed/blog-posts-seed-data.js +2 -1
- package/dist/seed/categories-seed-data.js +11 -2
- package/dist/seed/events-seed-data.js +11 -11
- package/dist/seed/faq-seed-data.js +5 -1
- package/dist/seed/products-auctions-seed-data.js +2 -2
- package/dist/seed/products-preorders-seed-data.d.ts +2 -1
- package/dist/seed/products-prize-draws-seed-data.js +56 -10
- package/dist/seed/products-standard-seed-data.js +82 -3
- package/dist/seed/scammers-seed-data.js +75 -2
- package/dist/seed/site-settings-seed-data.js +12 -2
- package/dist/server-entry.d.ts +0 -1
- package/dist/server-entry.js +0 -1
- package/dist/server.d.ts +6 -22
- package/dist/server.js +12 -59
- package/dist/styles.css +1 -1
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/BaseListingCard.js +1 -1
- package/dist/ui/components/ImageLightbox.d.ts +9 -0
- package/dist/ui/components/ImageLightbox.js +6 -3
- package/dist/ui/components/ListingToolbar.d.ts +18 -1
- package/dist/ui/components/ListingToolbar.js +5 -2
- package/dist/ui/components/StickyToolbar.js +13 -8
- package/package.json +1 -1
|
@@ -1,46 +1,42 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import Link from "next/link";
|
|
4
|
+
import { usePathname } from "next/navigation";
|
|
4
5
|
import { useEffect, useState } from "react";
|
|
5
6
|
import { ChevronUp, X } from "lucide-react";
|
|
6
7
|
import { Button, Div } from "../../ui";
|
|
7
|
-
import { normalizeError } from "../../errors/normalize";
|
|
8
|
-
const DISMISSED_KEY = "appkit:back-to-top-dismissed";
|
|
9
8
|
/**
|
|
10
9
|
* BackToTop — floating scroll-to-top button.
|
|
11
10
|
* Appears after the page has scrolled past `threshold` pixels. A small
|
|
12
|
-
* dismiss control
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
11
|
+
* dismiss control hides it for the current page view only — dismissal is
|
|
12
|
+
* plain in-memory component state, reset on every route change (via
|
|
13
|
+
* `usePathname()`) so navigating anywhere brings it back. Deliberately NOT
|
|
14
|
+
* persisted to storage: an earlier version wrote the dismissal to
|
|
15
|
+
* `sessionStorage`, and because this component is mounted once inside the
|
|
16
|
+
* persistent `AppLayoutShell` (it never remounts on client-side navigation),
|
|
17
|
+
* that meant one click hid the button for the rest of the browser tab
|
|
18
|
+
* session with no way to bring it back — reported as "the button is gone
|
|
19
|
+
* forever." See CLAUDE.md Recurrent Root Cause Patterns.
|
|
17
20
|
*/
|
|
18
21
|
export function BackToTop({ threshold = 400, ariaLabel = "Back to top", className = "", }) {
|
|
22
|
+
const pathname = usePathname();
|
|
19
23
|
const [visible, setVisible] = useState(false);
|
|
20
24
|
const [dismissed, setDismissed] = useState(false);
|
|
21
25
|
useEffect(() => {
|
|
22
|
-
try {
|
|
23
|
-
setDismissed(window.sessionStorage.getItem(DISMISSED_KEY) === "1");
|
|
24
|
-
}
|
|
25
|
-
catch (_err) {
|
|
26
|
-
void normalizeError(_err);
|
|
27
|
-
// sessionStorage unavailable — never dismissed for this render.
|
|
28
|
-
}
|
|
29
26
|
const handleScroll = () => setVisible(window.scrollY > threshold);
|
|
30
27
|
window.addEventListener("scroll", handleScroll, { passive: true });
|
|
31
28
|
return () => window.removeEventListener("scroll", handleScroll);
|
|
32
29
|
}, [threshold]);
|
|
30
|
+
// Reset dismissal on every route change — the component itself never
|
|
31
|
+
// unmounts (it lives inside the persistent app shell), so without this
|
|
32
|
+
// a single dismiss would otherwise hide the button for the whole session.
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
setDismissed(false);
|
|
35
|
+
}, [pathname]);
|
|
33
36
|
if (!visible || dismissed)
|
|
34
37
|
return null;
|
|
35
38
|
const handleDismiss = () => {
|
|
36
39
|
setDismissed(true);
|
|
37
|
-
try {
|
|
38
|
-
window.sessionStorage.setItem(DISMISSED_KEY, "1");
|
|
39
|
-
}
|
|
40
|
-
catch (_err) {
|
|
41
|
-
void normalizeError(_err);
|
|
42
|
-
// sessionStorage unavailable — dismissal still applies for this render.
|
|
43
|
-
}
|
|
44
40
|
};
|
|
45
41
|
return (_jsxs(Div, { layout: "flex", align: "center", gap: "1", className: `fixed bottom-[calc(var(--keyboard-inset-height,0px)+var(--bottom-nav-height,4rem))] right-4 z-[var(--appkit-z-bottom-nav)] ${className}`, children: [_jsx(Button, { type: "button", variant: "outline", size: "sm", "aria-label": ariaLabel, onClick: () => window.scrollTo({ top: 0, behavior: "smooth" }), className: "flex items-center justify-center w-10 h-10 rounded-full bg-[var(--appkit-color-surface)] border border-[var(--appkit-color-border)] shadow-md text-[var(--appkit-color-text-muted)] hover:bg-[var(--appkit-color-surface)] dark:hover:bg-slate-700 transition-colors", children: _jsx(ChevronUp, { className: "w-5 h-5", "aria-hidden": "true" }) }), _jsx(Button, { type: "button", variant: "outline", size: "sm", "aria-label": "Hide back-to-top button", onClick: handleDismiss, className: "flex items-center justify-center w-6 h-6 rounded-full bg-[var(--appkit-color-surface)] border border-[var(--appkit-color-border)] shadow-md text-[var(--appkit-color-text-faint)] hover:text-[var(--appkit-color-text)] transition-colors", children: _jsx(X, { className: "w-3 h-3", "aria-hidden": "true" }) })] }));
|
|
46
42
|
}
|
|
@@ -11,7 +11,7 @@ function DefaultNavItem({ item, isActive, }) {
|
|
|
11
11
|
const activeClasses = item.highlighted
|
|
12
12
|
? "border border-primary-400/40 dark:border-secondary-400/30 text-primary-700 dark:text-secondary-400 bg-primary-50/80 dark:bg-secondary-900/30 px-[var(--appkit-space-3)] transition-colors duration-150"
|
|
13
13
|
: isActive
|
|
14
|
-
? "bg-primary-50 bg-[var(--appkit-color-surface-elevated)] text-primary-800 dark:text-white font-semibold px-[var(--appkit-space-3)] border-b-2 border-primary-500 dark:border-secondary-400 rounded-none pb-[6px] transition-colors duration-150"
|
|
14
|
+
? "bg-primary-50 dark:bg-[var(--appkit-color-surface-elevated)] text-primary-800 dark:text-white font-semibold px-[var(--appkit-space-3)] border-b-2 border-primary-500 dark:border-secondary-400 rounded-none pb-[6px] transition-colors duration-150"
|
|
15
15
|
: "text-[var(--appkit-color-text-muted)] hover:bg-[var(--appkit-color-surface)] hover:bg-[var(--appkit-color-surface-elevated)] hover:text-zinc-950 dark:hover:text-white transition-colors duration-150 px-[var(--appkit-space-3)]";
|
|
16
16
|
return (_jsxs(Link, { href: item.href, "aria-current": isActive ? "page" : undefined, className: `flex items-center gap-[var(--appkit-space-1-5)] py-[var(--appkit-space-2)] text-[length:var(--appkit-text-sm)] rounded-lg font-medium transition-colors duration-150 whitespace-nowrap ${activeClasses}`, children: [item.icon, _jsx(Span, { children: item.label })] }));
|
|
17
17
|
}
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import Link from "next/link";
|
|
3
3
|
import { Avatar, BlockHeader, Button, Div, Row, Section, SiteLogo, SiteMark, Span } from "../../ui";
|
|
4
4
|
/** Shared icon-button class for all action buttons in the title bar. */
|
|
5
|
-
const iconBtn = "flex items-center justify-center w-9 h-9 rounded-lg text-[var(--appkit-color-text-muted)] hover:bg-primary-50 hover:text-primary-700 hover:bg-[var(--appkit-color-surface-elevated)] dark:hover:text-secondary-400 transition-colors";
|
|
5
|
+
const iconBtn = "flex items-center justify-center w-9 h-9 rounded-lg text-[var(--appkit-color-text-muted)] hover:bg-primary-50 hover:text-primary-700 dark:hover:bg-[var(--appkit-color-surface-elevated)] dark:hover:text-secondary-400 transition-colors";
|
|
6
6
|
/** Badge counter class for wishlist/cart counts. */
|
|
7
7
|
const countBadge = "absolute -top-0.5 -right-0.5 flex items-center justify-center w-4 h-4 rounded-full bg-error-surface text-white text-[10px] font-bold leading-none";
|
|
8
8
|
/**
|
|
@@ -30,7 +30,7 @@ export function TitleBarLayout({ onToggleSidebar, sidebarOpen, onSearchToggle, s
|
|
|
30
30
|
const wishlistEl = wishlistHref ? (_jsxs(Link, { href: wishlistHref, "aria-label": `Wishlist${wishlistCount > 0 ? `, ${wishlistCount} items` : ""}`, className: `relative ${iconBtn}`, "data-tour": "nav-wishlist", children: [_jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4.318 6.318a4.5 4.5 0 0 0 0 6.364L12 20.364l7.682-7.682a4.5 4.5 0 0 0-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 0 0-6.364 0z" }) }), wishlistCount > 0 && (_jsx(Span, { className: countBadge, children: wishlistCount > 9 ? "9+" : wishlistCount }))] })) : null;
|
|
31
31
|
const cartEl = cartHref ? (_jsxs(Link, { href: cartHref, "aria-label": `Cart${cartCount > 0 ? `, ${cartCount} items` : ""}`, className: `relative ${iconBtn}`, "data-tour": "nav-cart", children: [_jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M2.25 3h1.386c.51 0 .955.343 1.087.835l.383 1.437M7.5 14.25a3 3 0 0 0-3 3h15.75m-12.75-3h11.218c1.121-2.3 2.1-4.684 2.924-7.138a60.114 60.114 0 0 0-16.536-1.84M7.5 14.25 5.106 5.272M6 20.25a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0zm12.75 0a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0z" }) }), cartCount > 0 && (_jsx(Span, { className: countBadge, children: cartCount > 9 ? "9+" : cartCount }))] })) : null;
|
|
32
32
|
const profileEl = profileHref ? (_jsx(Link, { href: profileHref, "aria-label": user ? `Profile — ${user.displayName ?? user.email}` : "Sign in", className: `relative ${iconBtn}`, "data-tour": "nav-profile", children: _jsx(Avatar, { src: user?.photoURL ?? undefined, name: user?.displayName ?? user?.email ?? undefined, alt: user?.displayName ?? "Profile", size: "sm" }) })) : null;
|
|
33
|
-
const authButtonsEl = !user && (loginHref || registerHref) ? (_jsxs(Row, { gap: "xs", className: "hidden lg:flex", children: [loginHref && (_jsx(Link, { href: loginHref, className: "px-[var(--appkit-space-3)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-sm)] font-medium text-[var(--appkit-color-text-muted)] hover:text-primary-700 dark:hover:text-secondary-400 transition-colors rounded-lg hover:bg-primary-50 hover:bg-[var(--appkit-color-surface-elevated)]", children: "Sign in" })), registerHref && (_jsx(Link, { href: registerHref, className: "px-[var(--appkit-space-3)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-sm)] font-semibold rounded-lg bg-primary text-white hover:bg-primary/90 transition-colors btn-glow shadow-sm", children: "Register" }))] })) : null;
|
|
33
|
+
const authButtonsEl = !user && (loginHref || registerHref) ? (_jsxs(Row, { gap: "xs", className: "hidden lg:flex", children: [loginHref && (_jsx(Link, { href: loginHref, className: "px-[var(--appkit-space-3)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-sm)] font-medium text-[var(--appkit-color-text-muted)] hover:text-primary-700 dark:hover:text-secondary-400 transition-colors rounded-lg hover:bg-primary-50 dark:hover:bg-[var(--appkit-color-surface-elevated)]", children: "Sign in" })), registerHref && (_jsx(Link, { href: registerHref, className: "px-[var(--appkit-space-3)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-sm)] font-semibold rounded-lg bg-primary text-white hover:bg-primary/90 transition-colors btn-glow shadow-sm", children: "Register" }))] })) : null;
|
|
34
34
|
const hasTb2 = !!(notificationsEl || wishlistEl || cartEl || profileEl);
|
|
35
35
|
// ── Render ───────────────────────────────────────────────────────────────────
|
|
36
36
|
return (_jsxs(BlockHeader, { id: id, className: `sticky top-0 z-50 bg-[var(--appkit-color-bg)]/95 backdrop-blur-md border-b border-[var(--appkit-color-border-subtle)] shadow-sm ${className}`, children: [promoStripText && (_jsx(Section, { color: "inverse", tone: "accent-banner", className: "text-[length:var(--appkit-text-xs)] text-center font-medium", padding: "y-2xs", children: promoStripText })), _jsxs(Div, { paddingX: "x-page", className: "container mx-auto max-w-[1920px]", children: [_jsxs(Row, { justify: "between", gap: "none", className: "relative h-14", children: [_jsx(Row, { gap: "3", children: _jsxs(Link, { href: logoHref, "aria-label": brandName, className: "flex items-center gap-[var(--appkit-space-2)] transition-opacity hover:opacity-80", children: [_jsx(Div, { className: "flex md:hidden", children: _jsx(SiteMark, { title: brandName, size: "sm" }) }), _jsx(SiteLogo, { title: brandName, size: "md" })] }) }), _jsx(Row, { className: "hidden md:flex absolute inset-y-0 left-1/2 -translate-x-1/2", align: "center", children: _jsx(Link, { href: logoHref, "aria-label": brandName, className: "flex items-center transition-opacity hover:opacity-80", children: _jsx(SiteMark, { src: siteLogoUrl, title: brandName, size: "lg" }) }) }), _jsxs(Row, { gap: "xs", children: [devSlot, compareEl, notificationSlot, notificationsEl && _jsx(Div, { className: "hidden lg:flex", children: notificationsEl }), wishlistEl && _jsx(Div, { className: "hidden lg:flex", children: wishlistEl }), cartEl && _jsx(Div, { className: "hidden lg:flex", children: cartEl }), user
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* client-side overlay covers `<video>` playback because there is no
|
|
6
6
|
* Vercel-Hobby-budget ffmpeg pipeline to bake the watermark into the frames.
|
|
7
7
|
*/
|
|
8
|
+
export type MediaVideoWatermarkPosition = "top-left" | "top-right" | "bottom-left" | "bottom-right" | "center" | "custom";
|
|
8
9
|
export interface MediaVideoWatermark {
|
|
9
10
|
/** `"text"` renders a `<Span>` overlay; `"image"` renders a `<MediaImage>` (proxied). */
|
|
10
11
|
type: "text" | "image";
|
|
@@ -16,6 +17,12 @@ export interface MediaVideoWatermark {
|
|
|
16
17
|
size?: number;
|
|
17
18
|
/** Percentage opacity (0–100). Default 20. */
|
|
18
19
|
opacity?: number;
|
|
20
|
+
/** Anchor preset — mirrors the server-side Sharp pipeline. Default `"center"`. */
|
|
21
|
+
position?: MediaVideoWatermarkPosition;
|
|
22
|
+
/** `position === "custom"` only: % of container width, from center. +right / -left. */
|
|
23
|
+
offsetX?: number;
|
|
24
|
+
/** `position === "custom"` only: % of container height, from center. +down / -up. */
|
|
25
|
+
offsetY?: number;
|
|
19
26
|
}
|
|
20
27
|
export interface MediaVideoProps {
|
|
21
28
|
/** Video URL. When undefined the fallback placeholder is rendered. */
|
|
@@ -48,6 +48,39 @@ export function MediaVideo({ src, thumbnailUrl, alt = "Video", controls = true,
|
|
|
48
48
|
}
|
|
49
49
|
return (_jsxs(_Fragment, { children: [_jsx("video", { ref: videoRef, src: resolvedSrc, poster: resolvedPoster, controls: controls, autoPlay: autoPlayMuted, muted: autoPlayMuted, loop: loop, playsInline: true, "aria-label": alt, className: `absolute inset-0 w-full h-full ${fitClass}` }), effectiveWatermark ? _jsx(MediaVideoWatermarkLayer, { config: effectiveWatermark }) : null] }));
|
|
50
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* Mirrors the server-side Sharp pipeline's 5 anchor presets + `"custom"`
|
|
53
|
+
* %-offset-from-center (`resolveCompositePlacement` in
|
|
54
|
+
* `src/app/api/media/_watermark.ts`) as CSS insets, so a video's live
|
|
55
|
+
* overlay and its poster-frame-as-image watermark land in the same spot.
|
|
56
|
+
* Default `"center"`, 0/0 offset — matches the server-side default too.
|
|
57
|
+
*/
|
|
58
|
+
function resolveWatermarkPlacementStyle(config) {
|
|
59
|
+
const position = config.position ?? "center";
|
|
60
|
+
const inset = "2%";
|
|
61
|
+
switch (position) {
|
|
62
|
+
case "top-left":
|
|
63
|
+
return { top: inset, left: inset };
|
|
64
|
+
case "top-right":
|
|
65
|
+
return { top: inset, right: inset };
|
|
66
|
+
case "bottom-left":
|
|
67
|
+
return { bottom: inset, left: inset };
|
|
68
|
+
case "bottom-right":
|
|
69
|
+
return { bottom: inset, right: inset };
|
|
70
|
+
case "custom": {
|
|
71
|
+
const offsetX = Math.max(-45, Math.min(45, config.offsetX ?? 0));
|
|
72
|
+
const offsetY = Math.max(-45, Math.min(45, config.offsetY ?? 0));
|
|
73
|
+
return {
|
|
74
|
+
top: `calc(50% + ${offsetY}%)`,
|
|
75
|
+
left: `calc(50% + ${offsetX}%)`,
|
|
76
|
+
transform: "translate(-50%, -50%)",
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
case "center":
|
|
80
|
+
default:
|
|
81
|
+
return { top: "50%", left: "50%", transform: "translate(-50%, -50%)" };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
51
84
|
/**
|
|
52
85
|
* Visual watermark overlay rendered on top of the playing `<video>`. Native
|
|
53
86
|
* video controls render in a higher layer (the browser shadow DOM), so this
|
|
@@ -62,16 +95,14 @@ function MediaVideoWatermarkLayer({ config }) {
|
|
|
62
95
|
const opacity = Math.max(0, Math.min(100, config.opacity ?? 20)) / 100;
|
|
63
96
|
if (widthPct === 0 || opacity === 0)
|
|
64
97
|
return null;
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
// with the video, just like the server-side Sharp pipeline scales images.
|
|
98
|
+
// Width is a percentage of the container so the watermark scales with the
|
|
99
|
+
// video, just like the server-side Sharp pipeline scales images.
|
|
68
100
|
const containerStyle = {
|
|
69
101
|
position: "absolute",
|
|
70
|
-
right: "2%",
|
|
71
|
-
bottom: "2%",
|
|
72
102
|
width: `${widthPct}%`,
|
|
73
103
|
pointerEvents: "none",
|
|
74
104
|
opacity,
|
|
105
|
+
...resolveWatermarkPlacementStyle(config),
|
|
75
106
|
};
|
|
76
107
|
if (config.type === "image" && config.imageUrl) {
|
|
77
108
|
return (_jsx(Div, { className: "z-10", style: containerStyle, "aria-hidden": "true", children: _jsx("img", { src: resolveMediaUrl(config.imageUrl), alt: "", className: "block w-full h-auto" }) }));
|
|
@@ -49,6 +49,7 @@ export async function issuePartialRefund(adminUid, orderId, deductFees, refundNo
|
|
|
49
49
|
: `Your full refund of ₹${grossRefund} is being processed.`,
|
|
50
50
|
relatedId: orderId,
|
|
51
51
|
relatedType: "order",
|
|
52
|
+
orderWhatsappAddonPaid: order.whatsappNotifyAddon === true,
|
|
52
53
|
});
|
|
53
54
|
serverLogger.info("Admin partial refund initiated", {
|
|
54
55
|
adminUid,
|
|
@@ -29,13 +29,9 @@ export function OrderCard({ order, onClick, labels = {}, renderActions }) {
|
|
|
29
29
|
it.prizeRevealStatus !== undefined &&
|
|
30
30
|
it.prizeRevealStatus !== "revealed");
|
|
31
31
|
const revealsRemaining = unrevealedPrizeDraws.length;
|
|
32
|
-
const earliestDeadline = unrevealedPrizeDraws
|
|
33
|
-
.map((it) => it.prizeRevealDeadline)
|
|
34
|
-
.filter((d) => !!d)
|
|
35
|
-
.sort()[0];
|
|
36
32
|
return (_jsxs(Div, { padding: "5", role: onClick ? "button" : undefined, tabIndex: onClick ? 0 : undefined, onKeyDown: onClick
|
|
37
33
|
? (e) => (e.key === "Enter" || e.key === " ") && onClick(order)
|
|
38
|
-
: undefined, onClick: onClick ? () => onClick(order) : undefined, className: `border border-neutral-200 ${onClick ? "cursor-pointer transition hover:shadow-md" : ""}`, rounded: "xl", surface: "default", children: [_jsxs(Row, { wrap: true, align: "start", justify: "between", gap: "3", children: [_jsxs(Div, { children: [_jsxs(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "xs", children: ["Order #", order.id.slice(-8).toUpperCase()] }), date && (_jsx(Text, { className: "mt-0.5", color: "faint", size: "xs", children: date }))] }), _jsx(Span, { size: "xs", weight: "semibold", className: `capitalize ${statusColor}`, rounded: "full", padding: "pill-md", children: labels[order.orderStatus] ?? order.orderStatus.replace(/_/g, " ") })] }), revealsRemaining > 0 && (
|
|
34
|
+
: undefined, onClick: onClick ? () => onClick(order) : undefined, className: `border border-neutral-200 ${onClick ? "cursor-pointer transition hover:shadow-md" : ""}`, rounded: "xl", surface: "default", children: [_jsxs(Row, { wrap: true, align: "start", justify: "between", gap: "3", children: [_jsxs(Div, { children: [_jsxs(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "xs", children: ["Order #", order.id.slice(-8).toUpperCase()] }), date && (_jsx(Text, { className: "mt-0.5", color: "faint", size: "xs", children: date }))] }), _jsx(Span, { size: "xs", weight: "semibold", className: `capitalize ${statusColor}`, rounded: "full", padding: "pill-md", children: labels[order.orderStatus] ?? order.orderStatus.replace(/_/g, " ") })] }), revealsRemaining > 0 && (_jsx(Row, { gap: "sm", className: "mt-2", children: _jsxs(Span, { size: "xs", weight: "semibold", className: CLS_PRIZE_BADGE, children: [revealsRemaining, " ", revealsRemaining === 1 ? "reveal" : "reveals", " pending"] }) })), _jsxs(Row, { wrap: true, gap: "3", className: "mt-4", children: [order.items.slice(0, 3).map((item, i) => (_jsxs(Row, { gap: "sm", children: [item.image && (_jsx(Div, { className: "relative h-10 w-10 flex-shrink-0", rounded: "lg", overflow: "hidden", children: _jsx(MediaImage, { src: item.image, alt: item.title, size: "thumbnail" }) })), _jsxs(Div, { children: [_jsx(Text, { truncate: true, size: "sm", weight: "medium", color: "primary", children: item.title }), _jsxs(Text, { size: "xs", color: "faint", children: ["\u00D7", item.quantity] })] })] }, i))), order.items.length > 3 && (_jsxs(Span, { size: "xs", className: "self-center", color: "faint", children: ["+", order.items.length - 3, " more"] }))] }), _jsxs(Row, { justify: "between", className: "mt-4 border-t border-neutral-100", padding: "t-sm", children: [_jsxs(Span, { size: "sm", className: "text-[var(--appkit-color-text-muted)]", children: [order.currency ?? "", " Total"] }), _jsx(Span, { weight: "semibold", className: "text-[var(--appkit-color-text)]", children: formatCurrency(order.total, order.currency) })] }), renderActions && (_jsx(Row, { justify: "end", gap: "sm", className: "mt-3", onClick: (e) => e.stopPropagation(), children: renderActions(order) }))] }));
|
|
39
35
|
}
|
|
40
36
|
export function OrdersList({ orders, isLoading, onOrderClick, totalPages = 1, currentPage = 1, onPageChange, emptyLabel = "No orders found", renderActions, }) {
|
|
41
37
|
if (isLoading) {
|
|
@@ -92,10 +92,6 @@ export interface OrderDocumentItem {
|
|
|
92
92
|
listingType?: ListingType;
|
|
93
93
|
/** SB8-F â€" per-item reveal status; flips through pending â†' open â†' revealed/closed. */
|
|
94
94
|
prizeRevealStatus?: "pending" | "open" | "closed" | "revealed";
|
|
95
|
-
/** SB8-F â€" ISO timestamp; deadline by which the buyer must claim the prize. */
|
|
96
|
-
prizeRevealDeadline?: string;
|
|
97
|
-
/** SB8-F â€" set after the reveal API picks a winner. */
|
|
98
|
-
revealedItemNumber?: number;
|
|
99
95
|
/** Set when the buyer cancels this specific line item post-order (partial cancellation). */
|
|
100
96
|
cancelledQuantity?: number;
|
|
101
97
|
cancelledAt?: Date;
|
|
@@ -183,6 +179,20 @@ export interface OrderDocument extends BaseDocument {
|
|
|
183
179
|
codRemainingAmount?: number;
|
|
184
180
|
/** COD handling fee charged to the buyer: max(codHandlingFeeMin, subtotal × codHandlingFeePercent / 100). Only set when paymentMethod === "cod". */
|
|
185
181
|
codHandlingFee?: number;
|
|
182
|
+
/** Buyer opted into WhatsApp order-update messages at checkout (unchecked by default). */
|
|
183
|
+
whatsappNotifyAddon?: boolean;
|
|
184
|
+
/** Fee charged for the WhatsApp addon, decimal rupees — snapshot of siteSettings.commissions.whatsappNotifyFee at order time. */
|
|
185
|
+
whatsappNotifyFee?: number;
|
|
186
|
+
/** Buyer opted into gift wrapping at checkout (unchecked by default). */
|
|
187
|
+
giftWrapAddon?: boolean;
|
|
188
|
+
/** Fee charged for gift wrap, decimal rupees — snapshot of siteSettings.commissions.giftWrapFee at order time. */
|
|
189
|
+
giftWrapFee?: number;
|
|
190
|
+
/** Optional buyer-written gift message, shown to the seller for fulfilment. Capped length. */
|
|
191
|
+
giftWrapMessage?: string;
|
|
192
|
+
/** Buyer opted into shipment protection (loss/damage insurance) at checkout (unchecked by default). */
|
|
193
|
+
shipmentProtectionAddon?: boolean;
|
|
194
|
+
/** Fee charged for shipment protection, decimal rupees — snapshot of siteSettings.commissions.shipmentProtectionFeePercent/Min at order time. */
|
|
195
|
+
shipmentProtectionFee?: number;
|
|
186
196
|
/** Order subtotal before GST — the amount GST is computed on. */
|
|
187
197
|
taxableAmount?: number;
|
|
188
198
|
/** Total GST charged (cgst + sgst, or igst — never both pairs at once). */
|
|
@@ -222,12 +232,10 @@ export interface OrderDocument extends BaseDocument {
|
|
|
222
232
|
images: string[];
|
|
223
233
|
wonAt: Date;
|
|
224
234
|
};
|
|
225
|
-
/** Deadline for the buyer to claim the won prize (typically 7 days). */
|
|
226
|
-
prizeRevealDeadline?: Date;
|
|
227
|
-
/** True once `prizeRevealDeadline` passes without a claim â€" auto-forfeit. */
|
|
228
|
-
prizeRevealExpired?: boolean;
|
|
229
235
|
/** Source product id when the order came from a prize-draw entry. */
|
|
230
236
|
prizeDrawProductId?: string;
|
|
237
|
+
/** Denormalized from the product at order-creation time — drives reveal copy on the order-detail page without an extra product fetch. */
|
|
238
|
+
prizeRevealMode?: "instant" | "scheduled";
|
|
231
239
|
/** True for prize-draw entries and bundle purchases that bypass refund. */
|
|
232
240
|
isNonRefundable?: boolean;
|
|
233
241
|
/** Set when the order came from a bundle â€" points back to `bundles/{id}`. */
|
|
@@ -90,8 +90,6 @@ export declare const orderDocumentItemSchema: z.ZodObject<{
|
|
|
90
90
|
totalPrice: z.ZodEffects<z.ZodNumber, number, number>;
|
|
91
91
|
listingType: z.ZodOptional<z.ZodEnum<["standard", "auction", "pre-order", "prize-draw", "classified", "digital-code", "live"]>>;
|
|
92
92
|
prizeRevealStatus: z.ZodOptional<z.ZodEnum<["pending", "open", "closed", "revealed"]>>;
|
|
93
|
-
prizeRevealDeadline: z.ZodOptional<z.ZodString>;
|
|
94
|
-
revealedItemNumber: z.ZodOptional<z.ZodNumber>;
|
|
95
93
|
}, "strip", z.ZodTypeAny, {
|
|
96
94
|
productId: string;
|
|
97
95
|
quantity: number;
|
|
@@ -100,8 +98,6 @@ export declare const orderDocumentItemSchema: z.ZodObject<{
|
|
|
100
98
|
totalPrice: number;
|
|
101
99
|
listingType?: "standard" | "auction" | "pre-order" | "prize-draw" | "classified" | "digital-code" | "live" | undefined;
|
|
102
100
|
prizeRevealStatus?: "closed" | "open" | "pending" | "revealed" | undefined;
|
|
103
|
-
prizeRevealDeadline?: string | undefined;
|
|
104
|
-
revealedItemNumber?: number | undefined;
|
|
105
101
|
}, {
|
|
106
102
|
productId: string;
|
|
107
103
|
quantity: number;
|
|
@@ -110,8 +106,6 @@ export declare const orderDocumentItemSchema: z.ZodObject<{
|
|
|
110
106
|
totalPrice: number;
|
|
111
107
|
listingType?: "standard" | "auction" | "pre-order" | "prize-draw" | "classified" | "digital-code" | "live" | undefined;
|
|
112
108
|
prizeRevealStatus?: "closed" | "open" | "pending" | "revealed" | undefined;
|
|
113
|
-
prizeRevealDeadline?: string | undefined;
|
|
114
|
-
revealedItemNumber?: number | undefined;
|
|
115
109
|
}>;
|
|
116
110
|
export declare const orderRefundEventSchema: z.ZodObject<{
|
|
117
111
|
refundId: z.ZodString;
|
|
@@ -255,8 +249,6 @@ export declare const orderFirestoreSchema: z.ZodObject<{
|
|
|
255
249
|
totalPrice: z.ZodEffects<z.ZodNumber, number, number>;
|
|
256
250
|
listingType: z.ZodOptional<z.ZodEnum<["standard", "auction", "pre-order", "prize-draw", "classified", "digital-code", "live"]>>;
|
|
257
251
|
prizeRevealStatus: z.ZodOptional<z.ZodEnum<["pending", "open", "closed", "revealed"]>>;
|
|
258
|
-
prizeRevealDeadline: z.ZodOptional<z.ZodString>;
|
|
259
|
-
revealedItemNumber: z.ZodOptional<z.ZodNumber>;
|
|
260
252
|
}, "strip", z.ZodTypeAny, {
|
|
261
253
|
productId: string;
|
|
262
254
|
quantity: number;
|
|
@@ -265,8 +257,6 @@ export declare const orderFirestoreSchema: z.ZodObject<{
|
|
|
265
257
|
totalPrice: number;
|
|
266
258
|
listingType?: "standard" | "auction" | "pre-order" | "prize-draw" | "classified" | "digital-code" | "live" | undefined;
|
|
267
259
|
prizeRevealStatus?: "closed" | "open" | "pending" | "revealed" | undefined;
|
|
268
|
-
prizeRevealDeadline?: string | undefined;
|
|
269
|
-
revealedItemNumber?: number | undefined;
|
|
270
260
|
}, {
|
|
271
261
|
productId: string;
|
|
272
262
|
quantity: number;
|
|
@@ -275,8 +265,6 @@ export declare const orderFirestoreSchema: z.ZodObject<{
|
|
|
275
265
|
totalPrice: number;
|
|
276
266
|
listingType?: "standard" | "auction" | "pre-order" | "prize-draw" | "classified" | "digital-code" | "live" | undefined;
|
|
277
267
|
prizeRevealStatus?: "closed" | "open" | "pending" | "revealed" | undefined;
|
|
278
|
-
prizeRevealDeadline?: string | undefined;
|
|
279
|
-
revealedItemNumber?: number | undefined;
|
|
280
268
|
}>, "many">>;
|
|
281
269
|
orderType: z.ZodOptional<z.ZodString>;
|
|
282
270
|
imageUrls: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
@@ -450,15 +438,8 @@ export declare const orderFirestoreSchema: z.ZodObject<{
|
|
|
450
438
|
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
451
439
|
}, z.ZodTypeAny, "passthrough">;
|
|
452
440
|
}>>;
|
|
453
|
-
prizeRevealDeadline: z.ZodOptional<z.ZodUnion<[z.ZodDate, z.ZodString, z.ZodObject<{
|
|
454
|
-
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
455
|
-
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
456
|
-
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
457
|
-
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
458
|
-
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
459
|
-
}, z.ZodTypeAny, "passthrough">>]>>;
|
|
460
|
-
prizeRevealExpired: z.ZodOptional<z.ZodBoolean>;
|
|
461
441
|
prizeDrawProductId: z.ZodOptional<z.ZodString>;
|
|
442
|
+
prizeRevealMode: z.ZodOptional<z.ZodEnum<["instant", "scheduled"]>>;
|
|
462
443
|
isNonRefundable: z.ZodOptional<z.ZodBoolean>;
|
|
463
444
|
bundleId: z.ZodOptional<z.ZodString>;
|
|
464
445
|
paymentBatchId: z.ZodOptional<z.ZodString>;
|
|
@@ -582,6 +563,7 @@ export declare const orderFirestoreSchema: z.ZodObject<{
|
|
|
582
563
|
paymentMethod?: string | undefined;
|
|
583
564
|
notes?: string | undefined;
|
|
584
565
|
storeName?: string | undefined;
|
|
566
|
+
prizeRevealMode?: "instant" | "scheduled" | undefined;
|
|
585
567
|
physicalLocation?: {
|
|
586
568
|
zone: string;
|
|
587
569
|
shelf: string;
|
|
@@ -595,8 +577,6 @@ export declare const orderFirestoreSchema: z.ZodObject<{
|
|
|
595
577
|
totalPrice: number;
|
|
596
578
|
listingType?: "standard" | "auction" | "pre-order" | "prize-draw" | "classified" | "digital-code" | "live" | undefined;
|
|
597
579
|
prizeRevealStatus?: "closed" | "open" | "pending" | "revealed" | undefined;
|
|
598
|
-
prizeRevealDeadline?: string | undefined;
|
|
599
|
-
revealedItemNumber?: number | undefined;
|
|
600
580
|
}[] | undefined;
|
|
601
581
|
payoutStatus?: "paid" | "eligible" | "requested" | undefined;
|
|
602
582
|
couponCode?: string | undefined;
|
|
@@ -668,10 +648,6 @@ export declare const orderFirestoreSchema: z.ZodObject<{
|
|
|
668
648
|
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
669
649
|
}, z.ZodTypeAny, "passthrough">;
|
|
670
650
|
} | undefined;
|
|
671
|
-
prizeRevealDeadline?: string | Date | z.objectOutputType<{
|
|
672
|
-
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
673
|
-
}, z.ZodTypeAny, "passthrough"> | undefined;
|
|
674
|
-
prizeRevealExpired?: boolean | undefined;
|
|
675
651
|
prizeDrawProductId?: string | undefined;
|
|
676
652
|
isNonRefundable?: boolean | undefined;
|
|
677
653
|
bundleId?: string | undefined;
|
|
@@ -735,6 +711,7 @@ export declare const orderFirestoreSchema: z.ZodObject<{
|
|
|
735
711
|
paymentMethod?: string | undefined;
|
|
736
712
|
notes?: string | undefined;
|
|
737
713
|
storeName?: string | undefined;
|
|
714
|
+
prizeRevealMode?: "instant" | "scheduled" | undefined;
|
|
738
715
|
physicalLocation?: {
|
|
739
716
|
zone: string;
|
|
740
717
|
shelf: string;
|
|
@@ -748,8 +725,6 @@ export declare const orderFirestoreSchema: z.ZodObject<{
|
|
|
748
725
|
totalPrice: number;
|
|
749
726
|
listingType?: "standard" | "auction" | "pre-order" | "prize-draw" | "classified" | "digital-code" | "live" | undefined;
|
|
750
727
|
prizeRevealStatus?: "closed" | "open" | "pending" | "revealed" | undefined;
|
|
751
|
-
prizeRevealDeadline?: string | undefined;
|
|
752
|
-
revealedItemNumber?: number | undefined;
|
|
753
728
|
}[] | undefined;
|
|
754
729
|
payoutStatus?: "paid" | "eligible" | "requested" | undefined;
|
|
755
730
|
couponCode?: string | undefined;
|
|
@@ -821,10 +796,6 @@ export declare const orderFirestoreSchema: z.ZodObject<{
|
|
|
821
796
|
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
822
797
|
}, z.ZodTypeAny, "passthrough">;
|
|
823
798
|
} | undefined;
|
|
824
|
-
prizeRevealDeadline?: string | Date | z.objectInputType<{
|
|
825
|
-
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
826
|
-
}, z.ZodTypeAny, "passthrough"> | undefined;
|
|
827
|
-
prizeRevealExpired?: boolean | undefined;
|
|
828
799
|
prizeDrawProductId?: string | undefined;
|
|
829
800
|
isNonRefundable?: boolean | undefined;
|
|
830
801
|
bundleId?: string | undefined;
|
|
@@ -51,8 +51,6 @@ export const orderDocumentItemSchema = z.object({
|
|
|
51
51
|
totalPrice: rupeesSchema,
|
|
52
52
|
listingType: orderListingTypeSchema.optional(),
|
|
53
53
|
prizeRevealStatus: z.enum(["pending", "open", "closed", "revealed"]).optional(),
|
|
54
|
-
prizeRevealDeadline: z.string().optional(),
|
|
55
|
-
revealedItemNumber: z.number().int().optional(),
|
|
56
54
|
});
|
|
57
55
|
export const orderRefundEventSchema = z.object({
|
|
58
56
|
refundId: z.string(),
|
|
@@ -152,9 +150,8 @@ export const orderFirestoreSchema = z.object({
|
|
|
152
150
|
payoutId: z.string().optional(),
|
|
153
151
|
offerId: z.string().optional(),
|
|
154
152
|
prizeWon: prizeWonSchema.optional(),
|
|
155
|
-
prizeRevealDeadline: firestoreDateSchema.optional(),
|
|
156
|
-
prizeRevealExpired: z.boolean().optional(),
|
|
157
153
|
prizeDrawProductId: z.string().optional(),
|
|
154
|
+
prizeRevealMode: z.enum(["instant", "scheduled"]).optional(),
|
|
158
155
|
isNonRefundable: z.boolean().optional(),
|
|
159
156
|
bundleId: z.string().optional(),
|
|
160
157
|
paymentBatchId: z.string().optional(),
|
|
@@ -32,10 +32,6 @@ export interface OrderItem {
|
|
|
32
32
|
* "X reveals pending" badge on user orders.
|
|
33
33
|
*/
|
|
34
34
|
prizeRevealStatus?: "pending" | "open" | "closed" | "revealed";
|
|
35
|
-
/** ISO timestamp — when the user must claim their reveal before forfeit. */
|
|
36
|
-
prizeRevealDeadline?: string;
|
|
37
|
-
/** Set after the reveal endpoint picks a winner — the prize item index. */
|
|
38
|
-
revealedItemNumber?: number;
|
|
39
35
|
/** Set when the buyer cancels this specific line item post-order (partial cancellation). */
|
|
40
36
|
cancelledQuantity?: number;
|
|
41
37
|
/**
|
|
@@ -85,9 +81,10 @@ export interface Order {
|
|
|
85
81
|
disputeRaised?: boolean;
|
|
86
82
|
disputeStatus?: "open" | "resolved";
|
|
87
83
|
/**
|
|
88
|
-
* Reveal-mode prize-draw fields (SB4-H/SB8-C). Set once the
|
|
89
|
-
*
|
|
90
|
-
*
|
|
84
|
+
* Reveal-mode prize-draw fields (SB4-H/SB8-C). Set once the winner has
|
|
85
|
+
* been automatically assigned to this order (on payment confirmation,
|
|
86
|
+
* draw expiry, or sellout — see assignPrizeDrawWinner); drives the
|
|
87
|
+
* PrizeRevealModal's "won" display on the order-detail page.
|
|
91
88
|
*/
|
|
92
89
|
prizeWon?: {
|
|
93
90
|
itemNumber: number;
|
|
@@ -95,10 +92,10 @@ export interface Order {
|
|
|
95
92
|
images: string[];
|
|
96
93
|
wonAt: string;
|
|
97
94
|
};
|
|
98
|
-
prizeRevealDeadline?: string;
|
|
99
|
-
prizeRevealExpired?: boolean;
|
|
100
95
|
/** Source prize-draw product id, when this order came from one. */
|
|
101
96
|
prizeDrawProductId?: string;
|
|
97
|
+
/** Denormalized from the product at order-creation time — drives PrizeRevealModal copy. */
|
|
98
|
+
prizeRevealMode?: "instant" | "scheduled";
|
|
102
99
|
}
|
|
103
100
|
export interface OrderListResponse {
|
|
104
101
|
items: Order[];
|
|
@@ -64,5 +64,5 @@ export function MarketplacePreorderCard({ product, className = "", variant = "gr
|
|
|
64
64
|
/* ── Compact list layout ── */
|
|
65
65
|
_jsxs(_Fragment, { children: [_jsxs(Row, { className: "min-w-0", align: "start", justify: "between", gap: "sm", children: [_jsx(TextLink, { href: detailHref, className: "min-w-0 flex-1", children: _jsx(Text, { size: "sm", weight: "medium", color: "primary", truncate: 2, children: product.title }) }), wishlistActions && (_jsx(Button, { type: "button", variant: "ghost", size: "sm", className: `shrink-0 p-[0.25rem] leading-none ${inWishlist ? "text-primary" : "text-[var(--appkit-color-text-faint)]"}`, onClick: handleWishlist, "aria-label": inWishlist ? mergedLabels.removeFromWishlist : mergedLabels.addToWishlist, children: inWishlist ? "♥" : "♡" }))] }), _jsxs(Row, { align: "center", gap: "sm", wrap: true, children: [_jsx(Text, { className: "text-primary", size: "sm", weight: "bold", children: formatCurrency(product.price, getDefaultCurrency()) }), shipDate && _jsx(PreorderBadge, { shipDate: shipDate })] }), _jsx(Button, { type: "button", variant: "primary", size: "sm", textSize: "xs", className: "self-start mt-0.5", onClick: handleNavigate, children: mergedLabels.reserveNow })] })) : (
|
|
66
66
|
/* ── Full grid layout ── */
|
|
67
|
-
_jsxs(_Fragment, { children: [_jsx(TextLink, { href: detailHref, children: _jsx(Text, { size: "sm", weight: "medium", color: "primary", truncate: 2, children: product.title }) }), product.description ? (_jsx(RichText, { html: normalizeRichTextHtml(product.description), proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "line-clamp-2 text-[length:var(--appkit-text-xs)] text-[var(--appkit-color-text-muted)]" })) : null, _jsxs(Row, { justify: "between", className: "mt-1", gap: "sm", children: [_jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: formatCurrency(product.price, getDefaultCurrency()) }), shipDate && _jsx(PreorderBadge, { shipDate: shipDate })] }), _jsxs(Row, { justify: "between", className: "mt-2", gap: "sm", children: [_jsx(Button, { type: "button", variant: "primary", size: "sm", textSize: "xs", className: "flex-1", onClick: handleNavigate, children: mergedLabels.reserveNow }), onAddToCart ? (_jsx(Button, { type: "button", variant: "outline", size: "sm", textSize: "xs", className: "flex-1", onClick: (e) => { e.stopPropagation(); onAddToCart(product); }, children: mergedLabels.addToCart })) : null, wishlistActions ? (_jsx(Button, { type: "button", variant: "ghost", size: "sm", className: `${inWishlist ? "text-primary" : "text-[var(--appkit-color-text-muted)]"}`, onClick: handleWishlist, "aria-label": inWishlist ? mergedLabels.removeFromWishlist : mergedLabels.addToWishlist, children: inWishlist ? "♥" : "♡" })) : null] })] })) })] }));
|
|
67
|
+
_jsxs(_Fragment, { children: [_jsx(TextLink, { href: detailHref, children: _jsx(Text, { size: "sm", weight: "medium", color: "primary", truncate: 2, children: product.title }) }), product.description ? (_jsx(RichText, { html: normalizeRichTextHtml(product.description), proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "line-clamp-2 text-[length:var(--appkit-text-xs)] text-[var(--appkit-color-text-muted)]" })) : null, _jsxs(Row, { justify: "between", className: "mt-1", gap: "sm", children: [_jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: formatCurrency(product.price, getDefaultCurrency()) }), shipDate && _jsx(PreorderBadge, { shipDate: shipDate })] }), _jsxs(Row, { justify: "between", className: "mt-auto pt-2", gap: "sm", children: [_jsx(Button, { type: "button", variant: "primary", size: "sm", textSize: "xs", className: "flex-1", onClick: handleNavigate, children: mergedLabels.reserveNow }), onAddToCart ? (_jsx(Button, { type: "button", variant: "outline", size: "sm", textSize: "xs", className: "flex-1", onClick: (e) => { e.stopPropagation(); onAddToCart(product); }, children: mergedLabels.addToCart })) : null, wishlistActions ? (_jsx(Button, { type: "button", variant: "ghost", size: "sm", className: `${inWishlist ? "text-primary" : "text-[var(--appkit-color-text-muted)]"}`, onClick: handleWishlist, "aria-label": inWishlist ? mergedLabels.removeFromWishlist : mergedLabels.addToWishlist, children: inWishlist ? "♥" : "♡" })) : null] })] })) })] }));
|
|
68
68
|
}
|
|
@@ -82,6 +82,7 @@ export async function PreOrderDetailPageView({ id, initialPreOrder, onReserveNow
|
|
|
82
82
|
: typeof p.mainImage === "string"
|
|
83
83
|
? [p.mainImage]
|
|
84
84
|
: [];
|
|
85
|
+
const productVideo = p.video;
|
|
85
86
|
const reservedCount = typeof p.preOrderCurrentCount === "number"
|
|
86
87
|
? p.preOrderCurrentCount
|
|
87
88
|
: typeof p.reservedCount === "number"
|
|
@@ -150,7 +151,7 @@ export async function PreOrderDetailPageView({ id, initialPreOrder, onReserveNow
|
|
|
150
151
|
price: typeof p.price === "number" ? p.price : undefined,
|
|
151
152
|
storeId: typeof p.storeId === "string" ? p.storeId : undefined,
|
|
152
153
|
storeName: typeof p.storeName === "string" ? p.storeName : undefined,
|
|
153
|
-
} }), _jsxs(Container, { size: "xl", padding: "y-lg", children: [_jsxs(Row, { className: "mb-4", align: "center", justify: "between", gap: "sm", wrap: true, children: [_jsxs(Nav, { "aria-label": "Breadcrumb", layout: "flex-wrap", gap: "2xs", textSize: "xs", color: "muted", children: [_jsx(Link, { href: String(ROUTES.HOME), className: CLS_BREADCRUMB_LINK, children: "Home" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.PRE_ORDERS), className: CLS_BREADCRUMB_LINK, children: "Pre-Orders" }), category && (_jsxs(_Fragment, { children: [_jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(category)), className: CLS_BREADCRUMB_LINK, children: categoryName || category })] })), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Span, { className: "truncate max-w-[200px]", color: "muted", children: title })] }), _jsx(ShareButton, { title: title })] }), _jsx(PreOrderDetailView, { renderGallery: () => (_jsx(ProductGalleryClient, { images: images, productName: title })), renderInfo: () => (_jsx(PreOrderInfoSection, { title: title, productionStatus: productionStatus, maxPerUser: maxPerUser, deliveryDate: deliveryDate, featured: featured, freeShipping: freeShipping, condition: condition, isCancellable: isCancellable, category: category, categoryName: categoryName, brand: brand, brandSlug: brandSlug, productFeatures: productFeatures, features: features, descriptionHtml: descriptionHtml, safeSeller: safeSeller, storeHref: storeHref })), renderSublistingSection: sublistingCategoryId
|
|
154
|
+
} }), _jsxs(Container, { size: "xl", padding: "y-lg", children: [_jsxs(Row, { className: "mb-4", align: "center", justify: "between", gap: "sm", wrap: true, children: [_jsxs(Nav, { "aria-label": "Breadcrumb", layout: "flex-wrap", gap: "2xs", textSize: "xs", color: "muted", children: [_jsx(Link, { href: String(ROUTES.HOME), className: CLS_BREADCRUMB_LINK, children: "Home" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.PRE_ORDERS), className: CLS_BREADCRUMB_LINK, children: "Pre-Orders" }), category && (_jsxs(_Fragment, { children: [_jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(category)), className: CLS_BREADCRUMB_LINK, children: categoryName || category })] })), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Span, { className: "truncate max-w-[200px]", color: "muted", children: title })] }), _jsx(ShareButton, { title: title })] }), _jsx(PreOrderDetailView, { renderGallery: () => (_jsx(ProductGalleryClient, { images: images, video: productVideo, productName: title })), renderInfo: () => (_jsx(PreOrderInfoSection, { title: title, productionStatus: productionStatus, maxPerUser: maxPerUser, deliveryDate: deliveryDate, featured: featured, freeShipping: freeShipping, condition: condition, isCancellable: isCancellable, category: category, categoryName: categoryName, brand: brand, brandSlug: brandSlug, productFeatures: productFeatures, features: features, descriptionHtml: descriptionHtml, safeSeller: safeSeller, storeHref: storeHref })), renderSublistingSection: sublistingCategoryId
|
|
154
155
|
? () => (_jsx(SublistingCarouselSection, { sublistingCategoryId: sublistingCategoryId, currentListingId: String(product.id) }))
|
|
155
156
|
: undefined, renderGroupSection: groupId
|
|
156
157
|
? () => (_jsx(ShowGroupSection, { groupId: groupId, currentSlug: String(p.slug ?? product.id), isParent: isGroupParent, groupTitle: groupTitle }))
|
|
@@ -101,16 +101,19 @@ export async function listPreOrders(params = {}) {
|
|
|
101
101
|
});
|
|
102
102
|
}
|
|
103
103
|
export async function getRelatedProducts(categoryId, excludeId, limit = 6) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
104
|
+
// `categoryId` here is really a category slug — products store their
|
|
105
|
+
// category FK in categorySlugs[] (array-contains), not a scalar
|
|
106
|
+
// "categoryId" field, which never existed on ProductDocument.
|
|
107
|
+
const docs = await productRepository.findByCategory(categoryId);
|
|
108
|
+
const items = docs
|
|
109
|
+
.filter((p) => p.id !== excludeId && p.status === ProductStatusValues.PUBLISHED)
|
|
110
|
+
.sort((a, b) => {
|
|
111
|
+
const aTime = a.createdAt instanceof Date ? a.createdAt.getTime() : 0;
|
|
112
|
+
const bTime = b.createdAt instanceof Date ? b.createdAt.getTime() : 0;
|
|
113
|
+
return bTime - aTime;
|
|
114
|
+
})
|
|
115
|
+
.slice(0, limit);
|
|
116
|
+
return { items, total: items.length, page: 1, pageSize: limit, totalPages: 1, hasMore: false };
|
|
114
117
|
}
|
|
115
118
|
export async function getStoreStorefrontProducts(storeId) {
|
|
116
119
|
const products = await productRepository.findByStore(storeId);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CategoryDocument } from "../../categories/schemas/firestore";
|
|
2
|
-
export type MarketplaceBundleCardData = Pick<CategoryDocument, "id" | "name" | "slug" | "bundleItemDetails" | "bundleProductIds" | "bundlePrice" | "bundleStockStatus" | "display">;
|
|
2
|
+
export type MarketplaceBundleCardData = Pick<CategoryDocument, "id" | "name" | "slug" | "bundleItemDetails" | "bundleProductIds" | "bundlePrice" | "bundleOriginalTotal" | "bundleStockStatus" | "display">;
|
|
3
3
|
export interface MarketplaceBundleCardLabels {
|
|
4
4
|
bundleBadge?: string;
|
|
5
5
|
inStockBadge?: string;
|
|
@@ -12,6 +12,8 @@ const CLS_STOCK_OK = "bg-success-surface text-white";
|
|
|
12
12
|
const CLS_VIEW_BTN = "mt-2 w-full cursor-pointer rounded-md bg-violet-600 py-[var(--appkit-space-1-5)] text-center text-[length:var(--appkit-text-xs)] font-semibold text-white transition-colors hover:bg-violet-700 active:scale-[0.98]";
|
|
13
13
|
import { BaseListingCard, Div, Row, Span, Stack, Text, TextLink } from "../../../ui";
|
|
14
14
|
import { MediaImage } from "../../media/MediaImage";
|
|
15
|
+
import { computeBundleDiscount } from "../../../_internal/shared/features/categories/bundle-pricing";
|
|
16
|
+
import { BUNDLE_COPY } from "../../../_internal/shared/features/categories/bundle-copy";
|
|
15
17
|
const DEFAULT_LABELS = {
|
|
16
18
|
bundleBadge: "Bundle",
|
|
17
19
|
inStockBadge: "In stock",
|
|
@@ -34,6 +36,7 @@ export function MarketplaceBundleCard({ bundle, className = "", variant = "grid"
|
|
|
34
36
|
const stock = bundle.bundleStockStatus ?? "in_stock";
|
|
35
37
|
const cover = bundle.display?.coverImage;
|
|
36
38
|
const price = bundle.bundlePrice;
|
|
39
|
+
const discount = computeBundleDiscount(bundle.bundlePrice, bundle.bundleOriginalTotal);
|
|
37
40
|
const collageTiles = (bundle.bundleItemDetails ?? [])
|
|
38
41
|
.filter((d) => Boolean(d.imageURL))
|
|
39
42
|
.slice(0, 4);
|
|
@@ -46,13 +49,13 @@ export function MarketplaceBundleCard({ bundle, className = "", variant = "grid"
|
|
|
46
49
|
}
|
|
47
50
|
router.push(String(detailHref));
|
|
48
51
|
}, [detailHref, onNavigate, router]);
|
|
49
|
-
return (_jsxs(BaseListingCard, { isSelected: isSelected, variant: variant, className: className, onMouseDown: !isSelected ? longPress.onMouseDown : undefined, onMouseUp: !isSelected ? longPress.onMouseUp : undefined, onMouseLeave: !isSelected ? longPress.onMouseLeave : undefined, onTouchStart: !isSelected ? longPress.onTouchStart : undefined, onTouchEnd: !isSelected ? longPress.onTouchEnd : undefined, onTouchCancel: !isSelected ? longPress.onTouchCancel : undefined, children: [_jsxs(BaseListingCard.Hero, { aspect: "square", variant: variant, children: [_jsx(TextLink, { href: String(detailHref), className: "absolute inset-0 block", children: showCollage ? (_jsx(Div, { layout: "grid", className: `h-full w-full gap-[0.125rem] bg-[var(--appkit-color-surface-muted)] ${collageTiles.length === 2 ? "grid-cols-2 grid-rows-1" : "grid-cols-2 grid-rows-2"}`, children: collageTiles.map((tile, i) => (_jsxs(Div, { className: "relative overflow-hidden bg-[var(--appkit-color-surface-muted)]", children: [_jsx(MediaImage, { src: tile.imageURL, alt: tile.title ?? `${bundle.name} item ${i + 1}`, size: "card" }), i === collageTiles.length - 1 && overflow > 0 && (_jsxs(Row, { textWeight: "semibold", textSize: "sm", surface: "overlay-md", className: "absolute inset-0 text-white", align: "center", justify: "center", children: ["+", overflow] }))] }, `${tile.productId}-${i}`))) })) : cover ? (_jsx(Div, { className: "absolute inset-0 bg-[var(--appkit-color-surface-muted)]", children: _jsx(MediaImage, { src: cover, alt: bundle.name, size: "card" }) })) : (_jsx(Row, { className: "absolute inset-0 bg-[var(--appkit-color-surface-muted)]", align: "center", justify: "center", children: _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "xs", children: "No image" }) })) }), _jsxs(Stack, { className: "absolute right-2 top-2", align: "end", gap: "xs", children: [_jsx(Span, { size: "xs", weight: "medium", className: CLS_BUNDLE_PILL, children: mergedLabels.bundleBadge }), _jsx(Span, { layout: "inline-flex", color: "inverse", className: `${stock === "out_of_stock" ? "bg-neutral-500 " : CLS_STOCK_OK}`, rounded: "full", padding: "pill-xs", size: "xs", weight: "medium", children: stock === "out_of_stock" ? mergedLabels.outOfStockBadge : mergedLabels.inStockBadge })] }), stock === "out_of_stock" && (_jsx(Row, { surface: "overlay-xs", className: "absolute inset-0", align: "center", justify: "center", children: _jsx(Span, { color: "inverse", size: "xs", weight: "bold", className: "bg-neutral-900/80 tracking-wider", rounded: "default", padding: "pill-md", transform: "uppercase", children: mergedLabels.outOfStockBadge }) })), onSelect && (_jsx(BaseListingCard.Checkbox, { selected: isSelected, onSelect: (event) => {
|
|
52
|
+
return (_jsxs(BaseListingCard, { isSelected: isSelected, variant: variant, className: className, onMouseDown: !isSelected ? longPress.onMouseDown : undefined, onMouseUp: !isSelected ? longPress.onMouseUp : undefined, onMouseLeave: !isSelected ? longPress.onMouseLeave : undefined, onTouchStart: !isSelected ? longPress.onTouchStart : undefined, onTouchEnd: !isSelected ? longPress.onTouchEnd : undefined, onTouchCancel: !isSelected ? longPress.onTouchCancel : undefined, children: [_jsxs(BaseListingCard.Hero, { aspect: "square", variant: variant, children: [_jsx(TextLink, { href: String(detailHref), className: "absolute inset-0 block", children: showCollage ? (_jsx(Div, { layout: "grid", className: `h-full w-full gap-[0.125rem] bg-[var(--appkit-color-surface-muted)] ${collageTiles.length === 2 ? "grid-cols-2 grid-rows-1" : "grid-cols-2 grid-rows-2"}`, children: collageTiles.map((tile, i) => (_jsxs(Div, { className: "relative overflow-hidden bg-[var(--appkit-color-surface-muted)]", children: [_jsx(MediaImage, { src: tile.imageURL, alt: tile.title ?? `${bundle.name} item ${i + 1}`, size: "card" }), i === collageTiles.length - 1 && overflow > 0 && (_jsxs(Row, { textWeight: "semibold", textSize: "sm", surface: "overlay-md", className: "absolute inset-0 text-white", align: "center", justify: "center", children: ["+", overflow] }))] }, `${tile.productId}-${i}`))) })) : cover ? (_jsx(Div, { className: "absolute inset-0 bg-[var(--appkit-color-surface-muted)]", children: _jsx(MediaImage, { src: cover, alt: bundle.name, size: "card" }) })) : (_jsx(Row, { className: "absolute inset-0 bg-[var(--appkit-color-surface-muted)]", align: "center", justify: "center", children: _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "xs", children: "No image" }) })) }), discount && (_jsx(Span, { size: "xs", weight: "bold", color: "inverse", className: "absolute left-2 top-2 bg-success-surface", rounded: "full", padding: "pill-xs", children: BUNDLE_COPY.detail.discountBadge(discount.percent) })), _jsxs(Stack, { className: "absolute right-2 top-2", align: "end", gap: "xs", children: [_jsx(Span, { size: "xs", weight: "medium", className: CLS_BUNDLE_PILL, children: mergedLabels.bundleBadge }), _jsx(Span, { layout: "inline-flex", color: "inverse", className: `${stock === "out_of_stock" ? "bg-neutral-500 " : CLS_STOCK_OK}`, rounded: "full", padding: "pill-xs", size: "xs", weight: "medium", children: stock === "out_of_stock" ? mergedLabels.outOfStockBadge : mergedLabels.inStockBadge })] }), stock === "out_of_stock" && (_jsx(Row, { surface: "overlay-xs", className: "absolute inset-0", align: "center", justify: "center", children: _jsx(Span, { color: "inverse", size: "xs", weight: "bold", className: "bg-neutral-900/80 tracking-wider", rounded: "default", padding: "pill-md", transform: "uppercase", children: mergedLabels.outOfStockBadge }) })), onSelect && (_jsx(BaseListingCard.Checkbox, { selected: isSelected, onSelect: (event) => {
|
|
50
53
|
event.stopPropagation();
|
|
51
54
|
onSelect(bundle.id, !isSelected);
|
|
52
55
|
}, className: selectable || isSelected
|
|
53
56
|
? "opacity-100"
|
|
54
|
-
: "opacity-0 group-hover:opacity-100 transition-opacity" }))] }), _jsxs(BaseListingCard.Info, { variant: variant, children: [_jsx(TextLink, { href: String(detailHref), children: _jsx(Text, { size: "sm", weight: "medium", color: "primary", truncate: 2, children: bundle.name }) }), _jsxs(Row, { justify: "between", className: "mt-1", gap: "sm", children: [_jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: price
|
|
55
|
-
|
|
56
|
-
|
|
57
|
+
: "opacity-0 group-hover:opacity-100 transition-opacity" }))] }), _jsxs(BaseListingCard.Info, { variant: variant, children: [_jsx(TextLink, { href: String(detailHref), children: _jsx(Text, { size: "sm", weight: "medium", color: "primary", truncate: 2, children: bundle.name }) }), _jsxs(Row, { justify: "between", className: "mt-1", gap: "sm", wrap: true, children: [_jsxs(Row, { gap: "xs", align: "center", children: [_jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: price
|
|
58
|
+
? formatCurrency(price, getDefaultCurrency())
|
|
59
|
+
: "—" }), discount && (_jsx(Text, { size: "xs", color: "muted", className: "line-through", children: formatCurrency(discount.originalTotal, getDefaultCurrency()) }))] }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "xs", children: mergedLabels.itemsLabel(memberCount) })] }), _jsx(Div, { className: CLS_VIEW_BTN, role: "button", tabIndex: 0, onClick: handleNavigate, onKeyDown: (e) => { if (e.key === "Enter" || e.key === " ")
|
|
57
60
|
handleNavigate(); }, children: "View Bundle" })] })] }));
|
|
58
61
|
}
|
|
@@ -112,7 +112,7 @@ export async function PrizeDrawDetailPageView({ id, initialPrizeDraw, currentUse
|
|
|
112
112
|
? String(ROUTES.PUBLIC.STORE_DETAIL(storeSlug))
|
|
113
113
|
: null;
|
|
114
114
|
const descriptionHtml = toDescriptionHtml(p.description);
|
|
115
|
-
return (_jsxs(Main, { children: [_jsx(HistoryTracker, { productId: String(p.id ?? p.slug ?? ""), productType: "
|
|
115
|
+
return (_jsxs(Main, { children: [_jsx(HistoryTracker, { productId: String(p.id ?? p.slug ?? ""), productType: "prize-draw", snapshot: {
|
|
116
116
|
title,
|
|
117
117
|
thumb,
|
|
118
118
|
price: pricePerEntry,
|