@mohasinac/appkit 2.7.49 → 2.7.52
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/SidebarCollapseToggle.d.ts +19 -0
- package/dist/_internal/client/features/layout/SidebarCollapseToggle.js +15 -0
- package/dist/_internal/client/features/layout/index.d.ts +2 -0
- package/dist/_internal/client/features/layout/index.js +1 -0
- package/dist/_internal/server/features/checkout/actions.js +25 -2
- package/dist/_internal/server/features/promotions/actions.js +3 -1
- package/dist/_internal/server/jobs/core/__tests__/couponExpiry.test.d.ts +1 -0
- package/dist/_internal/server/jobs/core/__tests__/couponExpiry.test.js +65 -0
- package/dist/_internal/server/jobs/core/__tests__/listingProcessor.test.d.ts +1 -0
- package/dist/_internal/server/jobs/core/__tests__/listingProcessor.test.js +163 -0
- package/dist/_internal/server/jobs/core/__tests__/mediaTmpCleanup.test.d.ts +1 -0
- package/dist/_internal/server/jobs/core/__tests__/mediaTmpCleanup.test.js +115 -0
- package/dist/_internal/server/jobs/core/__tests__/pendingOrderTimeout.test.d.ts +1 -0
- package/dist/_internal/server/jobs/core/__tests__/pendingOrderTimeout.test.js +96 -0
- package/dist/_internal/shared/actions/action-registry.js +149 -0
- package/dist/client.d.ts +45 -6
- package/dist/client.js +27 -4
- package/dist/constants/api-endpoints.d.ts +42 -0
- package/dist/constants/api-endpoints.js +14 -0
- package/dist/features/_guide-cls.d.ts +13 -0
- package/dist/features/_guide-cls.js +14 -0
- package/dist/features/about/components/HelpPageView.js +29 -26
- package/dist/features/account/components/BuyerAccountGuideView.d.ts +1 -0
- package/dist/features/account/components/BuyerAccountGuideView.js +39 -0
- package/dist/features/account/components/BuyerAuctionsGuideView.d.ts +1 -0
- package/dist/features/account/components/BuyerAuctionsGuideView.js +38 -0
- package/dist/features/account/components/BuyerOrdersGuideView.d.ts +1 -0
- package/dist/features/account/components/BuyerOrdersGuideView.js +48 -0
- package/dist/features/account/components/BuyerShoppingGuideView.d.ts +1 -0
- package/dist/features/account/components/BuyerShoppingGuideView.js +38 -0
- package/dist/features/account/components/UserSidebar.js +2 -1
- package/dist/features/admin/components/AdminAnalyticsGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminAnalyticsGuideView.js +24 -0
- package/dist/features/admin/components/AdminBlogEditorView.js +102 -75
- package/dist/features/admin/components/AdminBundleEditorView.js +20 -14
- package/dist/features/admin/components/AdminBundlesView.d.ts +1 -3
- package/dist/features/admin/components/AdminBundlesView.js +173 -52
- package/dist/features/admin/components/AdminCatalogGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminCatalogGuideView.js +28 -0
- package/dist/features/admin/components/AdminContentGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminContentGuideView.js +36 -0
- package/dist/features/admin/components/AdminCouponEditorView.js +1 -1
- package/dist/features/admin/components/AdminGuideHubView.d.ts +5 -0
- package/dist/features/admin/components/AdminGuideHubView.js +79 -0
- package/dist/features/admin/components/AdminOrdersGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminOrdersGuideView.js +44 -0
- package/dist/features/admin/components/AdminProductsView.js +8 -3
- package/dist/features/admin/components/AdminSidebar.js +2 -1
- package/dist/features/admin/components/AdminSiteConfigGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminSiteConfigGuideView.js +21 -0
- package/dist/features/admin/components/AdminStoresGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminStoresGuideView.js +32 -0
- package/dist/features/admin/components/AdminTeamGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminTeamGuideView.js +33 -0
- package/dist/features/admin/components/AdminTrustGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminTrustGuideView.js +40 -0
- package/dist/features/admin/components/AdminUsersGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminUsersGuideView.js +38 -0
- package/dist/features/auth/repository/session.repository.js +16 -1
- package/dist/features/cart/hooks/useAddToCart.d.ts +2 -0
- package/dist/features/cart/hooks/useAddToCart.js +2 -0
- package/dist/features/cart/hooks/useGuestCart.d.ts +2 -0
- package/dist/features/cart/utils/guest-cart.d.ts +7 -0
- package/dist/features/cart/utils/pending-ops.d.ts +6 -0
- package/dist/features/categories/components/CategoryBundlesListing.js +3 -25
- package/dist/features/categories/components/CategoryProductsListing.js +6 -10
- package/dist/features/categories/components/index.d.ts +0 -2
- package/dist/features/categories/components/index.js +0 -1
- package/dist/features/categories/schemas/firestore.d.ts +9 -0
- package/dist/features/events/components/AdminEventEditorView.js +223 -269
- package/dist/features/events/components/EventRaffleWinnerView.d.ts +9 -1
- package/dist/features/events/components/EventRaffleWinnerView.js +3 -3
- package/dist/features/events/components/SpinWheelView.js +3 -3
- package/dist/features/grouped/repository/grouped-listings.repository.d.ts +9 -0
- package/dist/features/grouped/repository/grouped-listings.repository.js +12 -0
- package/dist/features/layout/BottomNavLayout.d.ts +5 -0
- package/dist/features/layout/BottomNavLayout.js +21 -0
- package/dist/features/layout/TitleBar.js +7 -1
- package/dist/features/layout/TitleBarLayout.d.ts +8 -2
- package/dist/features/layout/TitleBarLayout.js +11 -7
- package/dist/features/media/upload/MediaUploadField.js +1 -1
- package/dist/features/orders/schemas/index.d.ts +2 -2
- package/dist/features/pre-orders/components/PreOrdersIndexListing.js +12 -6
- package/dist/features/products/components/AuctionsIndexListing.js +3 -1
- package/dist/features/products/components/MarketplaceBundleCard.d.ts +21 -0
- package/dist/features/products/components/MarketplaceBundleCard.js +56 -0
- package/dist/features/products/components/PrizeDrawDetailPageView.js +1 -1
- package/dist/features/products/components/PrizeDrawEntryActions.d.ts +3 -1
- package/dist/features/products/components/PrizeDrawEntryActions.js +8 -12
- package/dist/features/products/components/ProductDetailActions.d.ts +5 -1
- package/dist/features/products/components/ProductDetailActions.js +3 -1
- package/dist/features/products/components/ProductDetailPageView.d.ts +2 -0
- package/dist/features/products/components/ProductDetailPageView.js +4 -0
- package/dist/features/products/components/ProductGrid.js +3 -3
- package/dist/features/products/components/ProductsIndexListing.js +18 -9
- package/dist/features/products/components/index.d.ts +2 -4
- package/dist/features/products/components/index.js +1 -2
- package/dist/features/promotions/components/CouponCard.d.ts +11 -1
- package/dist/features/promotions/components/CouponCard.js +36 -2
- package/dist/features/promotions/repository/claimed-coupons.repository.d.ts +27 -0
- package/dist/features/promotions/repository/claimed-coupons.repository.js +115 -0
- package/dist/features/promotions/schemas/firestore.d.ts +34 -0
- package/dist/features/promotions/schemas/firestore.js +14 -1
- package/dist/features/reviews/components/index.d.ts +0 -2
- package/dist/features/reviews/components/index.js +0 -1
- package/dist/features/seller/components/BrandInlineSelect.js +23 -3
- package/dist/features/seller/components/CategoryInlineSelect.js +20 -3
- package/dist/features/seller/components/SellerAddressesView.js +3 -2
- package/dist/features/seller/components/SellerAnalyticsAlertsView.d.ts +6 -0
- package/dist/features/seller/components/SellerAnalyticsAlertsView.js +124 -0
- package/dist/features/seller/components/SellerAnalyticsView.d.ts +2 -0
- package/dist/features/seller/components/SellerAnalyticsView.js +17 -9
- package/dist/features/seller/components/SellerBidsView.js +33 -3
- package/dist/features/seller/components/SellerBundlesView.d.ts +5 -0
- package/dist/features/seller/components/SellerBundlesView.js +104 -0
- package/dist/features/seller/components/SellerClassifiedView.d.ts +5 -0
- package/dist/features/seller/components/SellerClassifiedView.js +113 -0
- package/dist/features/seller/components/SellerDashboardView.js +19 -7
- package/dist/features/seller/components/SellerDigitalCodesView.d.ts +5 -0
- package/dist/features/seller/components/SellerDigitalCodesView.js +115 -0
- package/dist/features/seller/components/SellerGoogleReviewsView.d.ts +14 -0
- package/dist/features/seller/components/SellerGoogleReviewsView.js +95 -0
- package/dist/features/seller/components/SellerGroupedListingsView.d.ts +6 -0
- package/dist/features/seller/components/SellerGroupedListingsView.js +112 -0
- package/dist/features/seller/components/SellerLiveView.d.ts +5 -0
- package/dist/features/seller/components/SellerLiveView.js +113 -0
- package/dist/features/seller/components/SellerOrdersView.js +53 -2
- package/dist/features/seller/components/SellerPayoutMethodsView.d.ts +7 -0
- package/dist/features/seller/components/SellerPayoutMethodsView.js +120 -0
- package/dist/features/seller/components/SellerPayoutSettingsView.js +50 -12
- package/dist/features/seller/components/SellerProductShell.d.ts +3 -1
- package/dist/features/seller/components/SellerProductShell.js +35 -11
- package/dist/features/seller/components/SellerProductsCards.d.ts +19 -0
- package/dist/features/seller/components/SellerProductsCards.js +19 -0
- package/dist/features/seller/components/SellerProductsFilterDrawer.d.ts +14 -0
- package/dist/features/seller/components/SellerProductsFilterDrawer.js +22 -0
- package/dist/features/seller/components/SellerProductsView.d.ts +3 -1
- package/dist/features/seller/components/SellerProductsView.js +92 -49
- package/dist/features/seller/components/SellerReviewsView.js +67 -4
- package/dist/features/seller/components/SellerShippingConfigsView.d.ts +7 -0
- package/dist/features/seller/components/SellerShippingConfigsView.js +149 -0
- package/dist/features/seller/components/SellerShippingView.js +37 -10
- package/dist/features/seller/components/SellerSidebar.js +2 -1
- package/dist/features/seller/components/SellerStoreCategoriesView.d.ts +9 -0
- package/dist/features/seller/components/SellerStoreCategoriesView.js +122 -0
- package/dist/features/seller/components/SellerStorefrontView.d.ts +0 -7
- package/dist/features/seller/components/SellerStorefrontView.js +26 -29
- package/dist/features/seller/components/SellerTemplatesView.d.ts +10 -0
- package/dist/features/seller/components/SellerTemplatesView.js +265 -0
- package/dist/features/seller/components/index.d.ts +26 -0
- package/dist/features/seller/components/index.js +13 -0
- package/dist/features/seller/components/seller-products-styles.d.ts +7 -0
- package/dist/features/seller/components/seller-products-styles.js +14 -0
- package/dist/features/shell/FormShell.d.ts +7 -1
- package/dist/features/shell/FormShell.js +5 -2
- package/dist/features/store-extensions/index.d.ts +4 -0
- package/dist/features/store-extensions/index.js +4 -0
- package/dist/features/store-extensions/repository/rbac.repositories.d.ts +23 -0
- package/dist/features/store-extensions/repository/rbac.repositories.js +32 -0
- package/dist/features/store-extensions/repository/store-extensions.repositories.d.ts +91 -0
- package/dist/features/store-extensions/repository/store-extensions.repositories.js +127 -0
- package/dist/features/store-extensions/schemas/firestore.d.ts +244 -0
- package/dist/features/store-extensions/schemas/firestore.js +158 -0
- package/dist/features/store-extensions/schemas/rbac.d.ts +65 -0
- package/dist/features/store-extensions/schemas/rbac.js +43 -0
- package/dist/features/stores/api/[storeSlug]/reviews/route.js +49 -7
- package/dist/features/stores/components/InteractiveStoreCard.js +7 -3
- package/dist/features/stores/components/StoreCapabilitiesGuideView.d.ts +3 -0
- package/dist/features/stores/components/StoreCapabilitiesGuideView.js +101 -0
- package/dist/features/stores/components/StoreFinanceGuideView.d.ts +3 -0
- package/dist/features/stores/components/StoreFinanceGuideView.js +79 -0
- package/dist/features/stores/components/StoreGuideHubView.d.ts +5 -0
- package/dist/features/stores/components/StoreGuideHubView.js +89 -0
- package/dist/features/stores/components/StoreListingsGuideView.d.ts +3 -0
- package/dist/features/stores/components/StoreListingsGuideView.js +151 -0
- package/dist/features/stores/components/StoreOrdersGuideView.d.ts +3 -0
- package/dist/features/stores/components/StoreOrdersGuideView.js +122 -0
- package/dist/features/stores/components/StoreProductsListing.js +6 -10
- package/dist/features/stores/components/StoreReviewsListing.js +100 -23
- package/dist/features/stores/components/StoreSettingsGuideView.d.ts +3 -0
- package/dist/features/stores/components/StoreSettingsGuideView.js +56 -0
- package/dist/features/stores/components/index.d.ts +12 -1
- package/dist/features/stores/components/index.js +6 -1
- package/dist/features/stores/hooks/useStores.d.ts +5 -0
- package/dist/features/stores/hooks/useStores.js +10 -0
- package/dist/features/stores/manifest.js +1 -1
- package/dist/features/stores/schemas/firestore.d.ts +2 -0
- package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +13 -2
- package/dist/index.d.ts +60 -9
- package/dist/index.js +66 -15
- package/dist/next/routing/route-map.d.ts +134 -0
- package/dist/next/routing/route-map.js +59 -0
- package/dist/providers/db-firebase/__tests__/filter-aliases.test.d.ts +1 -0
- package/dist/providers/db-firebase/__tests__/filter-aliases.test.js +93 -0
- package/dist/providers/db-firebase/sieve.d.ts +49 -0
- package/dist/providers/db-firebase/sieve.js +61 -8
- package/dist/react/hooks/useFormStatePreservation.d.ts +17 -0
- package/dist/react/hooks/useFormStatePreservation.js +62 -0
- package/dist/react/hooks/useInlineRowEdit.d.ts +24 -0
- package/dist/react/hooks/useInlineRowEdit.js +68 -0
- package/dist/react/index.d.ts +4 -0
- package/dist/react/index.js +4 -0
- package/dist/repositories/index.d.ts +5 -0
- package/dist/repositories/index.js +7 -0
- package/dist/seed/actions/demo-seed-actions.d.ts +1 -1
- package/dist/seed/claimed-coupons-seed-data.d.ts +13 -0
- package/dist/seed/claimed-coupons-seed-data.js +79 -0
- package/dist/seed/index.d.ts +2 -0
- package/dist/seed/index.js +3 -0
- package/dist/seed/manifest.js +17 -0
- package/dist/seed/store-extensions-seed-data.d.ts +19 -0
- package/dist/seed/store-extensions-seed-data.js +421 -0
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/Button.style.css +20 -12
- package/dist/ui/components/ClaimCouponButton.d.ts +28 -0
- package/dist/ui/components/ClaimCouponButton.js +89 -0
- package/dist/ui/components/ListingToolbar.d.ts +8 -1
- package/dist/ui/components/ListingToolbar.js +4 -2
- package/dist/ui/components/QuickCreateModal.d.ts +15 -0
- package/dist/ui/components/QuickCreateModal.js +48 -0
- package/dist/ui/components/QuickCreateModal.style.css +84 -0
- package/dist/ui/components/StickyBottomBar.d.ts +22 -0
- package/dist/ui/components/StickyBottomBar.js +20 -0
- package/dist/ui/components/VacationBanner.d.ts +11 -0
- package/dist/ui/components/VacationBanner.js +16 -0
- package/dist/ui/components/__tests__/BulkActionBar.test.d.ts +1 -0
- package/dist/ui/components/__tests__/BulkActionBar.test.js +96 -0
- package/dist/ui/components/__tests__/ListingToolbar.test.d.ts +1 -0
- package/dist/ui/components/__tests__/ListingToolbar.test.js +125 -0
- package/dist/ui/index.d.ts +9 -1
- package/dist/ui/index.js +4 -0
- package/package.json +8 -2
|
@@ -5,9 +5,17 @@ export interface RaffleWinnerEvent {
|
|
|
5
5
|
raffleEntryCount?: number;
|
|
6
6
|
raffleTriggeredAt?: string | Date;
|
|
7
7
|
raffleGithubFunctionUrl?: string;
|
|
8
|
+
/**
|
|
9
|
+
* Plan §10 — when the prize is a coupon, the issuing code so the winner
|
|
10
|
+
* can claim it directly via the ClaimCouponButton. Pair with
|
|
11
|
+
* `currentUserIsWinner` so the CTA only renders for the actual winner.
|
|
12
|
+
*/
|
|
13
|
+
rafflePrizeCouponCode?: string;
|
|
8
14
|
}
|
|
9
15
|
export interface EventRaffleWinnerViewProps {
|
|
10
16
|
event: RaffleWinnerEvent;
|
|
17
|
+
/** When true (= page resolver matched the viewer to raffleWinnerUserId), show the Claim CTA. */
|
|
18
|
+
currentUserIsWinner?: boolean;
|
|
11
19
|
labels?: {
|
|
12
20
|
heading?: string;
|
|
13
21
|
winnerLabel?: string;
|
|
@@ -18,4 +26,4 @@ export interface EventRaffleWinnerViewProps {
|
|
|
18
26
|
anonymous?: string;
|
|
19
27
|
};
|
|
20
28
|
}
|
|
21
|
-
export declare function EventRaffleWinnerView({ event, labels }: EventRaffleWinnerViewProps): import("react/jsx-runtime").JSX.Element;
|
|
29
|
+
export declare function EventRaffleWinnerView({ event, currentUserIsWinner, labels }: EventRaffleWinnerViewProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Article, Div, Heading, Span, Text, TextLink } from "../../../ui";
|
|
2
|
+
import { Article, ClaimCouponButton, Div, Heading, Span, Text, TextLink } from "../../../ui";
|
|
3
3
|
const DEFAULT_LABELS = {
|
|
4
4
|
heading: "Raffle Winner",
|
|
5
5
|
winnerLabel: "Winner",
|
|
@@ -9,10 +9,10 @@ const DEFAULT_LABELS = {
|
|
|
9
9
|
notDrawnYet: "The raffle has not been drawn yet.",
|
|
10
10
|
anonymous: "Anonymous participant",
|
|
11
11
|
};
|
|
12
|
-
export function EventRaffleWinnerView({ event, labels }) {
|
|
12
|
+
export function EventRaffleWinnerView({ event, currentUserIsWinner, labels }) {
|
|
13
13
|
const l = { ...DEFAULT_LABELS, ...(labels ?? {}) };
|
|
14
14
|
if (!event.raffleWinnerUserId) {
|
|
15
15
|
return (_jsx(Div, { className: "rounded-xl border border-dashed border-zinc-300 dark:border-zinc-700 px-6 py-10 text-center", children: _jsx(Text, { className: "text-zinc-500 dark:text-zinc-400", children: l.notDrawnYet }) }));
|
|
16
16
|
}
|
|
17
|
-
return (_jsxs(Article, { className: "space-y-4", children: [_jsxs(Heading, { level: 2, className: "text-xl font-semibold text-zinc-900 dark:text-zinc-100", children: ["\uD83C\uDF9F\uFE0F ", l.heading] }), _jsxs(Div, { className: "rounded-2xl border border-amber-200 bg-amber-50/60 dark:border-amber-700 dark:bg-amber-900/20 p-6 space-y-3", children: [_jsxs(Div, { children: [_jsx(Text, { className: "text-xs uppercase tracking-wide text-amber-700 dark:text-amber-300", children: l.winnerLabel }), _jsx(Text, { className: "mt-1 text-2xl font-bold text-amber-900 dark:text-amber-100", children: event.raffleWinnerDisplayName?.trim() || l.anonymous })] }), event.rafflePrize ? (_jsxs(Div, { children: [_jsx(Text, { className: "text-xs uppercase tracking-wide text-amber-700 dark:text-amber-300", children: l.prizeLabel }), _jsx(Text, { className: "mt-1 text-base text-zinc-800 dark:text-zinc-100", children: event.rafflePrize })] })) : null, _jsxs(Div, { className: "flex flex-wrap gap-x-6 gap-y-2 pt-2 text-sm text-zinc-600 dark:text-zinc-300", children: [typeof event.raffleEntryCount === "number" ? (_jsxs(Span, { children: [l.poolLabel, ":", " ", _jsx(Span, { className: "font-medium text-zinc-900 dark:text-zinc-100", children: event.raffleEntryCount })] })) : null, event.raffleTriggeredAt ? (_jsx(Span, { children: new Date(event.raffleTriggeredAt).toLocaleString() })) : null] })] }), event.raffleGithubFunctionUrl ? (_jsxs(Div, { className: "rounded-lg border border-zinc-200 dark:border-zinc-700 p-4 text-sm", children: [_jsx(Text, { className: "font-medium text-zinc-700 dark:text-zinc-200", children: l.fairnessLabel }), _jsx(TextLink, { href: event.raffleGithubFunctionUrl, className: "mt-1 block break-all text-primary-600 dark:text-primary-400 hover:underline", children: event.raffleGithubFunctionUrl })] })) : null] }));
|
|
17
|
+
return (_jsxs(Article, { className: "space-y-4", children: [_jsxs(Heading, { level: 2, className: "text-xl font-semibold text-zinc-900 dark:text-zinc-100", children: ["\uD83C\uDF9F\uFE0F ", l.heading] }), _jsxs(Div, { className: "rounded-2xl border border-amber-200 bg-amber-50/60 dark:border-amber-700 dark:bg-amber-900/20 p-6 space-y-3", children: [_jsxs(Div, { children: [_jsx(Text, { className: "text-xs uppercase tracking-wide text-amber-700 dark:text-amber-300", children: l.winnerLabel }), _jsx(Text, { className: "mt-1 text-2xl font-bold text-amber-900 dark:text-amber-100", children: event.raffleWinnerDisplayName?.trim() || l.anonymous })] }), event.rafflePrize ? (_jsxs(Div, { children: [_jsx(Text, { className: "text-xs uppercase tracking-wide text-amber-700 dark:text-amber-300", children: l.prizeLabel }), _jsx(Text, { className: "mt-1 text-base text-zinc-800 dark:text-zinc-100", children: event.rafflePrize })] })) : null, currentUserIsWinner && event.rafflePrizeCouponCode ? (_jsx(Div, { className: "pt-2", children: _jsx(ClaimCouponButton, { couponCode: event.rafflePrizeCouponCode, source: "raffle", size: "sm" }) })) : null, _jsxs(Div, { className: "flex flex-wrap gap-x-6 gap-y-2 pt-2 text-sm text-zinc-600 dark:text-zinc-300", children: [typeof event.raffleEntryCount === "number" ? (_jsxs(Span, { children: [l.poolLabel, ":", " ", _jsx(Span, { className: "font-medium text-zinc-900 dark:text-zinc-100", children: event.raffleEntryCount })] })) : null, event.raffleTriggeredAt ? (_jsx(Span, { children: new Date(event.raffleTriggeredAt).toLocaleString() })) : null] })] }), event.raffleGithubFunctionUrl ? (_jsxs(Div, { className: "rounded-lg border border-zinc-200 dark:border-zinc-700 p-4 text-sm", children: [_jsx(Text, { className: "font-medium text-zinc-700 dark:text-zinc-200", children: l.fairnessLabel }), _jsx(TextLink, { href: event.raffleGithubFunctionUrl, className: "mt-1 block break-all text-primary-600 dark:text-primary-400 hover:underline", children: event.raffleGithubFunctionUrl })] })) : null] }));
|
|
18
18
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { useCallback, useMemo, useState } from "react";
|
|
4
|
-
import { Button, Div, Heading, LoginRequiredModal, Span, Text } from "../../../ui";
|
|
4
|
+
import { Button, ClaimCouponButton, Div, Heading, LoginRequiredModal, Span, Text } from "../../../ui";
|
|
5
5
|
import { isAuthError } from "../../../utils/auth-error";
|
|
6
6
|
const DEFAULT_LABELS = {
|
|
7
7
|
heading: "Spin the Wheel",
|
|
@@ -67,5 +67,5 @@ export function SpinWheelView({ eventId, prizes, alreadyUsed, initialPrizeId, in
|
|
|
67
67
|
0% { transform: rotate(0deg); }
|
|
68
68
|
100% { transform: rotate(2160deg); }
|
|
69
69
|
}
|
|
70
|
-
` })] }), wonPrize ? (_jsxs(Div, { className: "rounded-xl border border-emerald-300 bg-emerald-50 dark:border-emerald-700 dark:bg-emerald-900/30 p-4 text-center", children: [_jsx(Text, { className: "text-sm uppercase tracking-wide text-emerald-700 dark:text-emerald-300", children: l.wonHeadline }), _jsx(Text, { className: "mt-1 text-lg font-bold text-emerald-900 dark:text-emerald-100", children: wonPrize.label }), resultCoupon ? (_jsxs(Text, { className: "mt-2 text-sm text-emerald-800 dark:text-emerald-200", children: [l.couponHint, " ", _jsx(Span, { className: "font-mono font-semibold", children: resultCoupon })] })) : null] })) : null, error ? (_jsx(Text, { className: "text-sm text-rose-600 dark:text-rose-400 text-center", children: error })) : null, _jsx(Div, { className: "flex justify-center", children: _jsx(Button, { variant: "primary", size: "lg", onClick: handleSpin, disabled: disabled, children: spinning ? l.spinningButton : l.spinButton }) }), alreadyUsed ? (_jsx(Text, { className: "text-center text-sm text-zinc-500 dark:text-zinc-400", children: l.alreadySpun })) : !inWindow ? (_jsx(Text, { className: "text-center text-sm text-zinc-500 dark:text-zinc-400", children: l.outsideWindow })) : null] }));
|
|
70
|
+
` })] }), wonPrize ? (_jsxs(Div, { className: "rounded-xl border border-emerald-300 bg-emerald-50 dark:border-emerald-700 dark:bg-emerald-900/30 p-4 text-center", children: [_jsx(Text, { className: "text-sm uppercase tracking-wide text-emerald-700 dark:text-emerald-300", children: l.wonHeadline }), _jsx(Text, { className: "mt-1 text-lg font-bold text-emerald-900 dark:text-emerald-100", children: wonPrize.label }), resultCoupon ? (_jsxs(_Fragment, { children: [_jsxs(Text, { className: "mt-2 text-sm text-emerald-800 dark:text-emerald-200", children: [l.couponHint, " ", _jsx(Span, { className: "font-mono font-semibold", children: resultCoupon })] }), _jsx(Div, { className: "mt-3 flex justify-center", children: _jsx(ClaimCouponButton, { couponCode: resultCoupon, size: "sm", source: "spin" }) })] })) : null] })) : null, error ? (_jsx(Text, { className: "text-sm text-rose-600 dark:text-rose-400 text-center", children: error })) : null, _jsx(Div, { className: "flex justify-center", children: _jsx(Button, { variant: "primary", size: "lg", onClick: handleSpin, disabled: disabled, children: spinning ? l.spinningButton : l.spinButton }) }), alreadyUsed ? (_jsx(Text, { className: "text-center text-sm text-zinc-500 dark:text-zinc-400", children: l.alreadySpun })) : !inWindow ? (_jsx(Text, { className: "text-center text-sm text-zinc-500 dark:text-zinc-400", children: l.outsideWindow })) : null] }));
|
|
71
71
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { BaseRepository } from "../../../providers/db-firebase";
|
|
2
|
+
import { type GroupedListingDocument } from "../schemas/firestore";
|
|
3
|
+
export declare class GroupedListingsRepository extends BaseRepository<GroupedListingDocument> {
|
|
4
|
+
constructor();
|
|
5
|
+
listByStore(storeId: string): Promise<{
|
|
6
|
+
items: GroupedListingDocument[];
|
|
7
|
+
}>;
|
|
8
|
+
}
|
|
9
|
+
export declare const groupedListingsRepository: GroupedListingsRepository;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { BaseRepository } from "../../../providers/db-firebase";
|
|
2
|
+
import { GROUPED_LISTINGS_COLLECTION, } from "../schemas/firestore";
|
|
3
|
+
export class GroupedListingsRepository extends BaseRepository {
|
|
4
|
+
constructor() {
|
|
5
|
+
super(GROUPED_LISTINGS_COLLECTION);
|
|
6
|
+
}
|
|
7
|
+
async listByStore(storeId) {
|
|
8
|
+
const items = await this.findBy("storeId", storeId);
|
|
9
|
+
return { items };
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export const groupedListingsRepository = new GroupedListingsRepository();
|
|
@@ -10,5 +10,10 @@ export interface BottomNavLayoutProps {
|
|
|
10
10
|
*
|
|
11
11
|
* Provides the `nav + ul` container with correct z-index, background, height,
|
|
12
12
|
* and safe-area inset. Pass `li`-wrapped items as children.
|
|
13
|
+
*
|
|
14
|
+
* Mirrors `AppLayoutShell`'s `--header-height` contract: while mounted, writes
|
|
15
|
+
* `--bottom-nav-height: 4rem` to `:root` (sub-`lg` only — the nav itself is
|
|
16
|
+
* `lg:hidden`). Sticky CTAs read `var(--bottom-nav-height, 0px)` to position
|
|
17
|
+
* themselves above the navbar so they don't get clipped.
|
|
13
18
|
*/
|
|
14
19
|
export declare function BottomNavLayout({ ariaLabel, children, id, className, }: BottomNavLayoutProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,11 +1,32 @@
|
|
|
1
|
+
"use client";
|
|
1
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect } from "react";
|
|
2
4
|
import { Nav, Ul } from "../../ui";
|
|
3
5
|
/**
|
|
4
6
|
* BottomNavLayout — generic fixed-bottom navigation shell.
|
|
5
7
|
*
|
|
6
8
|
* Provides the `nav + ul` container with correct z-index, background, height,
|
|
7
9
|
* and safe-area inset. Pass `li`-wrapped items as children.
|
|
10
|
+
*
|
|
11
|
+
* Mirrors `AppLayoutShell`'s `--header-height` contract: while mounted, writes
|
|
12
|
+
* `--bottom-nav-height: 4rem` to `:root` (sub-`lg` only — the nav itself is
|
|
13
|
+
* `lg:hidden`). Sticky CTAs read `var(--bottom-nav-height, 0px)` to position
|
|
14
|
+
* themselves above the navbar so they don't get clipped.
|
|
8
15
|
*/
|
|
9
16
|
export function BottomNavLayout({ ariaLabel, children, id = "bottom-navbar", className, }) {
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
const root = document.documentElement;
|
|
19
|
+
const mql = typeof window !== "undefined" ? window.matchMedia("(max-width: 1023.98px)") : null;
|
|
20
|
+
const sync = () => {
|
|
21
|
+
// Only expose the var when the navbar is actually rendered (below lg).
|
|
22
|
+
root.style.setProperty("--bottom-nav-height", mql && mql.matches ? "4rem" : "0px");
|
|
23
|
+
};
|
|
24
|
+
sync();
|
|
25
|
+
mql?.addEventListener("change", sync);
|
|
26
|
+
return () => {
|
|
27
|
+
mql?.removeEventListener("change", sync);
|
|
28
|
+
root.style.setProperty("--bottom-nav-height", "0px");
|
|
29
|
+
};
|
|
30
|
+
}, []);
|
|
10
31
|
return (_jsx(Nav, { id: id, "aria-label": ariaLabel, className: `fixed bottom-0 left-0 right-0 lg:hidden z-40 bg-white/90 dark:bg-slate-950/90 backdrop-blur-md border-t border-zinc-200/80 dark:border-slate-800/80 shadow-2xl pb-safe${className ? ` ${className}` : ""}`, children: _jsx(Ul, { className: "flex items-stretch h-16", children: children }) }));
|
|
11
32
|
}
|
|
@@ -4,6 +4,7 @@ import { useWishlistCount } from "../wishlist/hooks/useWishlistCount";
|
|
|
4
4
|
import { useNotifications } from "../account/hooks/useNotifications";
|
|
5
5
|
import { useDashboardNav } from "./DashboardNavContext";
|
|
6
6
|
import { TitleBarLayout } from "./TitleBarLayout";
|
|
7
|
+
import { ROUTES } from "../../next/routing/route-map";
|
|
7
8
|
/**
|
|
8
9
|
* TitleBar � domain shell over TitleBarLayout.
|
|
9
10
|
*
|
|
@@ -18,7 +19,12 @@ export function TitleBar({ suppressDashboardNav, onBeforeToggleDashboardNav, use
|
|
|
18
19
|
const wishlistCount = useWishlistCount(userId);
|
|
19
20
|
const { unreadCount } = useNotifications(1);
|
|
20
21
|
const { hasNav: hasDashboardNav, toggleNav: toggleDashboardNav } = useDashboardNav();
|
|
21
|
-
return (_jsx(TitleBarLayout, { ...rest, cartCount: cartCount, wishlistCount: wishlistCount, unreadNotificationCount: rest.user ? unreadCount : 0,
|
|
22
|
+
return (_jsx(TitleBarLayout, { ...rest, cartCount: cartCount, wishlistCount: wishlistCount, unreadNotificationCount: rest.user ? unreadCount : 0,
|
|
23
|
+
// Default the bell to the user notifications page when the user is
|
|
24
|
+
// authenticated — consumers can override by passing their own
|
|
25
|
+
// `notificationsHref` through `...rest`.
|
|
26
|
+
notificationsHref: rest.notificationsHref ??
|
|
27
|
+
(rest.user ? String(ROUTES.USER.NOTIFICATIONS) : undefined), hasDashboardNav: suppressDashboardNav ? false : hasDashboardNav, onToggleDashboardNav: suppressDashboardNav
|
|
22
28
|
? undefined
|
|
23
29
|
: () => {
|
|
24
30
|
onBeforeToggleDashboardNav?.();
|
|
@@ -25,8 +25,14 @@ export interface TitleBarLayoutProps {
|
|
|
25
25
|
cartHref?: string;
|
|
26
26
|
cartCount?: number;
|
|
27
27
|
profileHref?: string;
|
|
28
|
-
/** Unread notification count
|
|
28
|
+
/** Unread notification count — shown on the dedicated bell icon only. */
|
|
29
29
|
unreadNotificationCount?: number;
|
|
30
|
+
/**
|
|
31
|
+
* Href for the dedicated notification bell icon rendered to the LEFT of the
|
|
32
|
+
* wishlist icon. When provided, the bell is rendered (with count badge) and
|
|
33
|
+
* navigates to the user notifications page by default.
|
|
34
|
+
*/
|
|
35
|
+
notificationsHref?: string;
|
|
30
36
|
loginHref?: string;
|
|
31
37
|
registerHref?: string;
|
|
32
38
|
user?: TitleBarUser | null;
|
|
@@ -58,4 +64,4 @@ export interface TitleBarLayoutProps {
|
|
|
58
64
|
*
|
|
59
65
|
* Receives all domain data as props — zero domain imports.
|
|
60
66
|
*/
|
|
61
|
-
export declare function TitleBarLayout({ onToggleSidebar, sidebarOpen, onSearchToggle, searchOpen: _searchOpen, brandName, brandShortName: _brandShortName, siteLogoUrl, logoHref, promotionsHref, compareHref, wishlistHref, wishlistCount, cartHref, cartCount, profileHref, unreadNotificationCount, loginHref, registerHref, user, notificationSlot, devSlot, navSlot, promoStripText, isDark, onToggleTheme, hasDashboardNav: _hasDashboardNav, onToggleDashboardNav: _onToggleDashboardNav, hideSidebarToggle, id, className, }: TitleBarLayoutProps): import("react/jsx-runtime").JSX.Element;
|
|
67
|
+
export declare function TitleBarLayout({ onToggleSidebar, sidebarOpen, onSearchToggle, searchOpen: _searchOpen, brandName, brandShortName: _brandShortName, siteLogoUrl, logoHref, promotionsHref, compareHref, wishlistHref, wishlistCount, cartHref, cartCount, profileHref, unreadNotificationCount, notificationsHref, loginHref, registerHref, user, notificationSlot, devSlot, navSlot, promoStripText, isDark, onToggleTheme, hasDashboardNav: _hasDashboardNav, onToggleDashboardNav: _onToggleDashboardNav, hideSidebarToggle, id, className, }: TitleBarLayoutProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -14,7 +14,7 @@ const countBadge = "absolute -top-0.5 -right-0.5 flex items-center justify-cente
|
|
|
14
14
|
*
|
|
15
15
|
* Receives all domain data as props — zero domain imports.
|
|
16
16
|
*/
|
|
17
|
-
export function TitleBarLayout({ onToggleSidebar, sidebarOpen, onSearchToggle, searchOpen: _searchOpen, brandName, brandShortName: _brandShortName, siteLogoUrl, logoHref, promotionsHref, compareHref, wishlistHref, wishlistCount = 0, cartHref, cartCount = 0, profileHref, unreadNotificationCount = 0, loginHref, registerHref, user, notificationSlot, devSlot, navSlot, promoStripText, isDark = false, onToggleTheme, hasDashboardNav: _hasDashboardNav, onToggleDashboardNav: _onToggleDashboardNav, hideSidebarToggle = false, id = "titlebar", className = "", }) {
|
|
17
|
+
export function TitleBarLayout({ onToggleSidebar, sidebarOpen, onSearchToggle, searchOpen: _searchOpen, brandName, brandShortName: _brandShortName, siteLogoUrl, logoHref, promotionsHref, compareHref, wishlistHref, wishlistCount = 0, cartHref, cartCount = 0, profileHref, unreadNotificationCount = 0, notificationsHref, loginHref, registerHref, user, notificationSlot, devSlot, navSlot, promoStripText, isDark = false, onToggleTheme, hasDashboardNav: _hasDashboardNav, onToggleDashboardNav: _onToggleDashboardNav, hideSidebarToggle = false, id = "titlebar", className = "", }) {
|
|
18
18
|
// ── Element builders ────────────────────────────────────────────────────────
|
|
19
19
|
const promotionsEl = promotionsHref ? (_jsxs(Link, { href: promotionsHref, "aria-label": "Today's deals", className: "flex items-center gap-1 px-3 py-1 rounded-full text-xs font-bold bg-primary-100 text-primary-700 dark:bg-secondary-900/40 dark:text-secondary-400 hover:bg-primary-200 dark:hover:bg-secondary-900/60 transition-colors border border-primary-200/60 dark:border-secondary-700/40", children: [_jsx("svg", { className: "w-3 h-3", viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true", children: _jsx("path", { d: "M21.41 11.58l-9-9C12.05 2.22 11.55 2 11 2H4c-1.1 0-2 .9-2 2v7c0 .55.22 1.05.59 1.42l9 9c.36.37.86.58 1.41.58.55 0 1.05-.21 1.41-.59l7-7c.37-.36.59-.86.59-1.41 0-.55-.23-1.06-.59-1.42zM5.5 7C4.67 7 4 6.33 4 5.5S4.67 4 5.5 4 7 4.67 7 5.5 6.33 7 5.5 7z" }) }), _jsx(Span, { className: "hidden sm:inline", children: "Today's Deals" })] })) : null;
|
|
20
20
|
const themeBtn = onToggleTheme ? (_jsx(Button, { type: "button", variant: "ghost", size: "sm", "aria-label": isDark ? "Switch to light mode" : "Switch to dark mode", onClick: onToggleTheme, className: iconBtn, children: isDark ? (_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: "M12 3v1m0 16v1m8.66-9h-1M4.34 12h-1m15.07-6.07-.71.71M6.34 17.66l-.71.71m12.73 0-.71-.71M6.34 6.34l-.71-.71M12 5a7 7 0 1 0 0 14A7 7 0 0 0 12 5z" }) })) : (_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: "M21 12.79A9 9 0 1 1 11.21 3a7 7 0 1 0 9.79 9.79z" }) })) })) : null;
|
|
@@ -22,15 +22,19 @@ export function TitleBarLayout({ onToggleSidebar, sidebarOpen, onSearchToggle, s
|
|
|
22
22
|
// Compare is always lg+ (desktop-only feature, less critical on mobile)
|
|
23
23
|
const compareEl = compareHref ? (_jsx(Link, { href: compareHref, "aria-label": "Compare items", className: `${iconBtn} hidden lg:flex`, 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: "M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" }) }) })) : null;
|
|
24
24
|
const searchBtn = (_jsx(Button, { type: "button", variant: "ghost", size: "sm", "aria-label": "Search", onClick: onSearchToggle, className: iconBtn, 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: "M21 21l-4.35-4.35M17 11A6 6 0 1 1 5 11a6 6 0 0 1 12 0z" }) }) }));
|
|
25
|
+
// Dedicated notification bell — rendered immediately before the wishlist
|
|
26
|
+
// icon (per layout request). Falls back to invisible when no href is set;
|
|
27
|
+
// the count badge mirrors the wishlist/cart pattern.
|
|
28
|
+
const notificationsEl = notificationsHref ? (_jsxs(Link, { href: notificationsHref, "aria-label": `Notifications${unreadNotificationCount > 0 ? `, ${unreadNotificationCount} unread` : ""}`, className: `relative ${iconBtn}`, 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: "M14.857 17.082a23.848 23.848 0 0 0 5.454-1.31A8.967 8.967 0 0 1 18 9.75V9A6 6 0 0 0 6 9v.75a8.967 8.967 0 0 1-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 0 1-5.714 0m5.714 0a3 3 0 1 1-5.714 0" }) }), unreadNotificationCount > 0 && (_jsx(Span, { className: countBadge, children: unreadNotificationCount > 99 ? "99+" : unreadNotificationCount }))] })) : null;
|
|
25
29
|
const wishlistEl = wishlistHref ? (_jsxs(Link, { href: wishlistHref, "aria-label": `Wishlist${wishlistCount > 0 ? `, ${wishlistCount} items` : ""}`, className: `relative ${iconBtn}`, 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;
|
|
26
30
|
const cartEl = cartHref ? (_jsxs(Link, { href: cartHref, "aria-label": `Cart${cartCount > 0 ? `, ${cartCount} items` : ""}`, className: `relative ${iconBtn}`, 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;
|
|
27
|
-
const profileEl = profileHref ? (
|
|
28
|
-
|
|
29
|
-
|
|
31
|
+
const profileEl = profileHref ? (_jsx(Link, { href: profileHref, "aria-label": user ? `Profile — ${user.displayName ?? user.email}` : "Sign in", className: `relative ${iconBtn}`, children: user?.photoURL ? (
|
|
32
|
+
// eslint-disable-next-line @next/next/no-img-element
|
|
33
|
+
_jsx("img", { src: user.photoURL, alt: user.displayName ?? "Profile", width: 28, height: 28, loading: "lazy", className: "w-7 h-7 rounded-full object-cover" })) : (_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: "M15.75 6a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0zM4.501 20.118a7.5 7.5 0 0 1 14.998 0A17.933 17.933 0 0 1 12 21.75c-2.676 0-5.216-.584-7.499-1.632z" }) })) })) : null;
|
|
30
34
|
const authButtonsEl = !user && (loginHref || registerHref) ? (_jsxs(Row, { gap: "xs", className: "hidden lg:flex items-center", children: [loginHref && (_jsx(Link, { href: loginHref, className: "px-3 py-1.5 text-sm font-medium text-zinc-700 dark:text-zinc-300 hover:text-primary-700 dark:hover:text-secondary-400 transition-colors rounded-lg hover:bg-primary-50 dark:hover:bg-slate-800", children: "Sign in" })), registerHref && (_jsx(Link, { href: registerHref, className: "px-3 py-1.5 text-sm font-semibold rounded-lg bg-primary text-white hover:bg-primary/90 transition-colors btn-glow shadow-sm", children: "Register" }))] })) : null;
|
|
31
|
-
const hasTb2 = !!(wishlistEl || cartEl || profileEl);
|
|
35
|
+
const hasTb2 = !!(notificationsEl || wishlistEl || cartEl || profileEl);
|
|
32
36
|
// ── Render ───────────────────────────────────────────────────────────────────
|
|
33
|
-
return (_jsxs(BlockHeader, { id: id, className: `sticky top-0 z-50 bg-white/95 dark:bg-slate-950/95 backdrop-blur-md border-b border-zinc-100 dark:border-slate-800 shadow-sm ${className}`, children: [promoStripText && (_jsx(Div, { className: "bg-gradient-to-r from-primary-700 to-secondary-600 dark:from-primary-700 dark:to-cobalt-700 text-white text-xs py-1 text-center font-medium", children: promoStripText })), _jsxs(Div, { className: "container mx-auto px-4 sm:px-6 lg:px-8 max-w-[1920px]", children: [_jsxs(Row, { justify: "between", gap: "none", className: "relative h-14", children: [_jsx(Row, { gap: "3", children: _jsx(Link, { href: logoHref, "aria-label": brandName, className: "flex items-center transition-opacity hover:opacity-80", children: _jsx(SiteLogo, { title: brandName, className: "h-7 md:h-9 lg:h-10" }) }) }), siteLogoUrl ? (_jsx(Div, { className: "absolute inset-y-0 left-1/2 -translate-x-1/2 flex items-center", children: _jsx(Link, { href: logoHref, "aria-label": brandName, className: "flex items-center transition-opacity hover:opacity-80", children: _jsx(SiteLogo, { src: siteLogoUrl, title: brandName, className: "h-7 md:h-9 lg:h-10" }) }) })) : (navSlot && _jsx(Div, { className: "hidden md:flex", children: navSlot })), _jsxs(Row, { gap: "xs", children: [devSlot, compareEl, notificationSlot, wishlistEl && _jsx(Div, { className: "hidden lg:flex", children: wishlistEl }), cartEl && _jsx(Div, { className: "hidden lg:flex", children: cartEl }), user
|
|
37
|
+
return (_jsxs(BlockHeader, { id: id, className: `sticky top-0 z-50 bg-white/95 dark:bg-slate-950/95 backdrop-blur-md border-b border-zinc-100 dark:border-slate-800 shadow-sm ${className}`, children: [promoStripText && (_jsx(Div, { className: "bg-gradient-to-r from-primary-700 to-secondary-600 dark:from-primary-700 dark:to-cobalt-700 text-white text-xs py-1 text-center font-medium", children: promoStripText })), _jsxs(Div, { className: "container mx-auto px-4 sm:px-6 lg:px-8 max-w-[1920px]", children: [_jsxs(Row, { justify: "between", gap: "none", className: "relative h-14", children: [_jsx(Row, { gap: "3", children: _jsx(Link, { href: logoHref, "aria-label": brandName, className: "flex items-center transition-opacity hover:opacity-80", children: _jsx(SiteLogo, { title: brandName, className: "h-7 md:h-9 lg:h-10" }) }) }), siteLogoUrl ? (_jsx(Div, { className: "absolute inset-y-0 left-1/2 -translate-x-1/2 flex items-center", children: _jsx(Link, { href: logoHref, "aria-label": brandName, className: "flex items-center transition-opacity hover:opacity-80", children: _jsx(SiteLogo, { src: siteLogoUrl, title: brandName, className: "h-7 md:h-9 lg:h-10" }) }) })) : (navSlot && _jsx(Div, { className: "hidden md:flex", children: navSlot })), _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
|
|
34
38
|
? profileEl && _jsx(Div, { className: "hidden lg:flex", children: profileEl })
|
|
35
|
-
: authButtonsEl ?? (profileEl && _jsx(Div, { className: "hidden lg:flex", children: profileEl })), searchBtn, promotionsEl, themeBtn, hamburgerBtn] })] }), hasTb2 && (_jsxs(Row, { as: "nav", "aria-label": "Account actions", justify: "end", gap: "xs", className: "flex lg:hidden h-10 border-t border-zinc-100 dark:border-slate-800 px-1", children: [wishlistEl, cartEl, profileEl] }))] })] }));
|
|
39
|
+
: authButtonsEl ?? (profileEl && _jsx(Div, { className: "hidden lg:flex", children: profileEl })), searchBtn, promotionsEl, themeBtn, hamburgerBtn] })] }), hasTb2 && (_jsxs(Row, { as: "nav", "aria-label": "Account actions", justify: "end", gap: "xs", className: "flex lg:hidden h-10 border-t border-zinc-100 dark:border-slate-800 px-1", children: [notificationsEl, wishlistEl, cartEl, profileEl] }))] })] }));
|
|
36
40
|
}
|
|
@@ -78,7 +78,7 @@ export function MediaUploadField({ label, value, onChange, onChangeField, onUplo
|
|
|
78
78
|
// SB-UNI-Z5 2026-05-13 — `kind` derives accept/maxSizeMB when not set
|
|
79
79
|
// explicitly; explicit props still win. "*" + 50 stay as the floor for
|
|
80
80
|
// back-compat with callers that pass neither.
|
|
81
|
-
kind, accept: acceptProp, maxSizeMB: maxSizeMBProp, disabled = false, helperText, captureSource = "both", captureMode = "photo", enableTrim = true, enableThumbnail = true, onThumbnailChange, showYoutube =
|
|
81
|
+
kind, accept: acceptProp, maxSizeMB: maxSizeMBProp, disabled = false, helperText, captureSource = "both", captureMode = "photo", enableTrim = true, enableThumbnail = true, onThumbnailChange, showYoutube = true, showExternal = true, onAbort, onStagedUrlsChange, isPersisted = false, }) {
|
|
82
82
|
const [isLoading, setIsLoading] = useState(false);
|
|
83
83
|
const [error, setError] = useState(null);
|
|
84
84
|
const [pendingVideoUrl, setPendingVideoUrl] = useState(null);
|
|
@@ -143,9 +143,9 @@ export declare const orderSchema: z.ZodObject<{
|
|
|
143
143
|
createdAt?: string | undefined;
|
|
144
144
|
updatedAt?: string | undefined;
|
|
145
145
|
notes?: string | undefined;
|
|
146
|
+
couponCode?: string | undefined;
|
|
146
147
|
tax?: number | undefined;
|
|
147
148
|
trackingNumber?: string | undefined;
|
|
148
|
-
couponCode?: string | undefined;
|
|
149
149
|
shippingCarrier?: string | undefined;
|
|
150
150
|
discount?: number | undefined;
|
|
151
151
|
paymentGateway?: string | undefined;
|
|
@@ -178,9 +178,9 @@ export declare const orderSchema: z.ZodObject<{
|
|
|
178
178
|
createdAt?: string | undefined;
|
|
179
179
|
updatedAt?: string | undefined;
|
|
180
180
|
notes?: string | undefined;
|
|
181
|
+
couponCode?: string | undefined;
|
|
181
182
|
tax?: number | undefined;
|
|
182
183
|
trackingNumber?: string | undefined;
|
|
183
|
-
couponCode?: string | undefined;
|
|
184
184
|
shippingCarrier?: string | undefined;
|
|
185
185
|
discount?: number | undefined;
|
|
186
186
|
paymentGateway?: string | undefined;
|
|
@@ -116,17 +116,22 @@ export function PreOrdersIndexListing({ initialData, categorySlug, brandName })
|
|
|
116
116
|
isWishlisted: (productId) => wishlistedIds.has(productId),
|
|
117
117
|
};
|
|
118
118
|
const handleAddToCart = useCallback((product) => {
|
|
119
|
-
|
|
119
|
+
const snapshot = {
|
|
120
120
|
productTitle: product.title,
|
|
121
121
|
productImage: product.mainImage,
|
|
122
122
|
price: product.price,
|
|
123
|
-
|
|
124
|
-
|
|
123
|
+
storeId: product.storeId,
|
|
124
|
+
storeName: product.storeName,
|
|
125
|
+
};
|
|
126
|
+
localCart.add(product.id, 1, snapshot);
|
|
127
|
+
pushCartOp({ op: "add", productId: product.id, quantity: 1, ...snapshot });
|
|
125
128
|
showToast("Added to cart", "success");
|
|
126
129
|
}, [localCart, showToast]);
|
|
127
130
|
const selection = useBulkSelection({ items: preOrders, keyExtractor: (p) => p.id });
|
|
128
131
|
const gridClass = "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-4";
|
|
129
|
-
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: filterActiveCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search pre-orders...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, onSearchKeyDown: handleSearchKeyDown, sortValue: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT, sortOptions: PREORDER_SORT_OPTIONS, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, view: view, onViewChange: handleViewToggle, onResetAll: resetAll, hasActiveState: hasActiveState, bulkMode: selection.isSelecting, bulkSelectedCount: selection.selectedCount, bulkTotalCount: preOrders.length, onBulkSelectAll: selection.toggleAll, onBulkClear: selection.clearSelection,
|
|
132
|
+
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: filterActiveCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search pre-orders...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, onSearchKeyDown: handleSearchKeyDown, sortValue: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT, sortOptions: PREORDER_SORT_OPTIONS, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, view: view, onViewChange: handleViewToggle, onResetAll: resetAll, hasActiveState: hasActiveState, bulkMode: selection.isSelecting, bulkSelectedCount: selection.selectedCount, bulkTotalCount: preOrders.length, onBulkSelectAll: selection.toggleAll, onBulkClear: selection.clearSelection, toggles: [
|
|
133
|
+
{ label: "Show closed", active: showClosed, onChange: (next) => table.set(TABLE_KEYS.SHOW_CLOSED, next ? "true" : "") },
|
|
134
|
+
] }), _jsx(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: [
|
|
130
135
|
{
|
|
131
136
|
id: ACTION_ID.ADD_TO_CART,
|
|
132
137
|
label: ACTION_META[ACTION_ID.ADD_TO_CART].label,
|
|
@@ -135,8 +140,9 @@ export function PreOrdersIndexListing({ initialData, categorySlug, brandName })
|
|
|
135
140
|
onClick: () => {
|
|
136
141
|
const selected = preOrders.filter((p) => selection.selectedIdSet.has(p.id));
|
|
137
142
|
selected.forEach((p) => {
|
|
138
|
-
|
|
139
|
-
|
|
143
|
+
const snapshot = { productTitle: p.title, productImage: p.mainImage, price: p.price, storeId: p.storeId, storeName: p.storeName };
|
|
144
|
+
localCart.add(p.id, 1, snapshot);
|
|
145
|
+
pushCartOp({ op: "add", productId: p.id, quantity: 1, ...snapshot });
|
|
140
146
|
});
|
|
141
147
|
showToast(`${selected.length} items added to cart`, "success");
|
|
142
148
|
selection.clearSelection();
|
|
@@ -111,7 +111,9 @@ export function AuctionsIndexListing({ initialData, categorySlug, brandName }) {
|
|
|
111
111
|
isWishlisted: (productId) => wishlistedIds.has(productId),
|
|
112
112
|
};
|
|
113
113
|
const gridClass = "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-3 gap-4";
|
|
114
|
-
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: filterActiveCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search auctions...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, onSearchKeyDown: handleSearchKeyDown, sortValue: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT, sortOptions: AUCTION_SORT_OPTIONS, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, view: view, onViewChange: handleViewToggle, onResetAll: resetAll, hasActiveState: hasActiveState, bulkMode: selection.isSelecting, bulkSelectedCount: selection.selectedCount, bulkTotalCount: auctions.length, onBulkSelectAll: selection.toggleAll, onBulkClear: selection.clearSelection,
|
|
114
|
+
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: filterActiveCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search auctions...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, onSearchKeyDown: handleSearchKeyDown, sortValue: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT, sortOptions: AUCTION_SORT_OPTIONS, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, view: view, onViewChange: handleViewToggle, onResetAll: resetAll, hasActiveState: hasActiveState, bulkMode: selection.isSelecting, bulkSelectedCount: selection.selectedCount, bulkTotalCount: auctions.length, onBulkSelectAll: selection.toggleAll, onBulkClear: selection.clearSelection, toggles: [
|
|
115
|
+
{ label: "Show ended", active: showEnded, onChange: (next) => table.set(TABLE_KEYS.SHOW_ENDED, next ? "true" : "") },
|
|
116
|
+
] }), _jsx(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: [
|
|
115
117
|
{
|
|
116
118
|
id: ACTION_ID.WATCH_AUCTION,
|
|
117
119
|
label: "Add to Watchlist",
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { CategoryDocument } from "../../categories/schemas/firestore";
|
|
2
|
+
export type MarketplaceBundleCardData = Pick<CategoryDocument, "id" | "name" | "slug" | "bundleItemDetails" | "bundleProductIds" | "bundlePriceInPaise" | "bundleStockStatus" | "display">;
|
|
3
|
+
export interface MarketplaceBundleCardLabels {
|
|
4
|
+
bundleBadge?: string;
|
|
5
|
+
inStockBadge?: string;
|
|
6
|
+
outOfStockBadge?: string;
|
|
7
|
+
itemsLabel?: (count: number) => string;
|
|
8
|
+
}
|
|
9
|
+
export interface MarketplaceBundleCardProps {
|
|
10
|
+
bundle: MarketplaceBundleCardData;
|
|
11
|
+
className?: string;
|
|
12
|
+
variant?: "grid" | "list";
|
|
13
|
+
selectable?: boolean;
|
|
14
|
+
isSelected?: boolean;
|
|
15
|
+
onSelect?: (id: string, selected: boolean) => void;
|
|
16
|
+
href?: string;
|
|
17
|
+
hrefBuilder?: (bundle: MarketplaceBundleCardData) => string;
|
|
18
|
+
onNavigate?: (href: string) => void;
|
|
19
|
+
labels?: MarketplaceBundleCardLabels;
|
|
20
|
+
}
|
|
21
|
+
export declare function MarketplaceBundleCard({ bundle, className, variant, selectable, isSelected, onSelect, href, hrefBuilder, onNavigate, labels, }: MarketplaceBundleCardProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useCallback } from "react";
|
|
4
|
+
import { useRouter } from "next/navigation";
|
|
5
|
+
import { useLongPress } from "../../../react/hooks/useLongPress";
|
|
6
|
+
import { ROUTES } from "../../../next";
|
|
7
|
+
import { formatCurrency } from "../../../utils";
|
|
8
|
+
import { getDefaultCurrency } from "../../../core/baseline-resolver";
|
|
9
|
+
import { BaseListingCard, Div, Row, Span, Text, TextLink, } from "../../../ui";
|
|
10
|
+
import { THEME_CONSTANTS } from "../../../tokens";
|
|
11
|
+
const DEFAULT_LABELS = {
|
|
12
|
+
bundleBadge: "Bundle",
|
|
13
|
+
inStockBadge: "In stock",
|
|
14
|
+
outOfStockBadge: "Out of stock",
|
|
15
|
+
itemsLabel: (count) => `${count} item${count !== 1 ? "s" : ""}`,
|
|
16
|
+
};
|
|
17
|
+
function resolveHref(bundle, href, hrefBuilder) {
|
|
18
|
+
if (href)
|
|
19
|
+
return href;
|
|
20
|
+
if (hrefBuilder)
|
|
21
|
+
return hrefBuilder(bundle);
|
|
22
|
+
return ROUTES.PUBLIC.BUNDLE_DETAIL(bundle.slug);
|
|
23
|
+
}
|
|
24
|
+
export function MarketplaceBundleCard({ bundle, className = "", variant = "grid", selectable = false, isSelected = false, onSelect, href, hrefBuilder, onNavigate, labels, }) {
|
|
25
|
+
const router = useRouter();
|
|
26
|
+
const mergedLabels = { ...DEFAULT_LABELS, ...labels };
|
|
27
|
+
const detailHref = resolveHref(bundle, href, hrefBuilder);
|
|
28
|
+
const longPress = useLongPress(() => onSelect?.(bundle.id, !isSelected));
|
|
29
|
+
const memberCount = bundle.bundleProductIds?.length ?? 0;
|
|
30
|
+
const stock = bundle.bundleStockStatus ?? "in_stock";
|
|
31
|
+
const cover = bundle.display?.coverImage;
|
|
32
|
+
const price = bundle.bundlePriceInPaise;
|
|
33
|
+
const collageTiles = (bundle.bundleItemDetails ?? [])
|
|
34
|
+
.filter((d) => Boolean(d.imageURL))
|
|
35
|
+
.slice(0, 4);
|
|
36
|
+
const showCollage = collageTiles.length >= 2;
|
|
37
|
+
const overflow = memberCount - collageTiles.length;
|
|
38
|
+
const handleNavigate = useCallback(() => {
|
|
39
|
+
if (onNavigate) {
|
|
40
|
+
onNavigate(String(detailHref));
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
router.push(String(detailHref));
|
|
44
|
+
}, [detailHref, onNavigate, router]);
|
|
45
|
+
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, children: [_jsxs(BaseListingCard.Hero, { aspect: "square", variant: variant, children: [_jsx(TextLink, { href: String(detailHref), className: "absolute inset-0 block", children: showCollage ? (_jsx(Div, { className: `grid h-full w-full gap-0.5 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("img", { src: tile.imageURL, alt: tile.title ?? `${bundle.name} item ${i + 1}`, loading: "lazy", className: "absolute inset-0 h-full w-full object-cover" }), i === collageTiles.length - 1 && overflow > 0 && (_jsxs(Div, { className: "absolute inset-0 flex items-center justify-center bg-black/55 text-sm font-semibold text-white", children: ["+", overflow] }))] }, `${tile.productId}-${i}`))) })) : cover ? (_jsx(Div, { className: "absolute inset-0 bg-[var(--appkit-color-surface-muted)]", children: _jsx("img", { src: cover, alt: bundle.name, loading: "lazy", className: "h-full w-full object-cover" }) })) : (_jsx(Div, { className: "absolute inset-0 flex items-center justify-center bg-[var(--appkit-color-surface-muted)]", children: _jsx(Text, { className: "text-xs text-[var(--appkit-color-text-muted)]", children: "No image" }) })) }), _jsxs(Div, { className: "absolute right-2 top-2 flex flex-col items-end gap-1", children: [_jsx(Span, { className: "inline-flex items-center rounded-full bg-violet-600 px-2 py-0.5 text-xs font-medium text-white", children: mergedLabels.bundleBadge }), _jsx(Span, { className: `inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${stock === "out_of_stock"
|
|
46
|
+
? "bg-zinc-500 text-white"
|
|
47
|
+
: "bg-emerald-600 text-white"}`, children: stock === "out_of_stock" ? mergedLabels.outOfStockBadge : mergedLabels.inStockBadge })] }), stock === "out_of_stock" && (_jsx(Div, { className: "absolute inset-0 flex items-center justify-center bg-black/40", children: _jsx(Span, { className: "rounded bg-zinc-900/80 px-3 py-1 text-xs font-bold uppercase tracking-wider text-white", children: mergedLabels.outOfStockBadge }) })), onSelect && (_jsx(BaseListingCard.Checkbox, { selected: isSelected, onSelect: (event) => {
|
|
48
|
+
event.stopPropagation();
|
|
49
|
+
onSelect(bundle.id, !isSelected);
|
|
50
|
+
}, className: selectable || isSelected
|
|
51
|
+
? "opacity-100"
|
|
52
|
+
: "opacity-0 group-hover:opacity-100 transition-opacity" }))] }), _jsxs(BaseListingCard.Info, { variant: variant, children: [_jsx(TextLink, { href: String(detailHref), children: _jsx(Text, { className: `${THEME_CONSTANTS.utilities.textClamp2} text-sm font-medium text-zinc-900 dark:text-zinc-100`, children: bundle.name }) }), _jsxs(Row, { justify: "between", className: "mt-1 gap-2", children: [_jsx(Text, { className: "text-sm font-semibold text-zinc-900 dark:text-zinc-100", children: price
|
|
53
|
+
? formatCurrency(price / 100, getDefaultCurrency())
|
|
54
|
+
: "—" }), _jsx(Text, { className: "text-xs text-[var(--appkit-color-text-muted)]", children: mergedLabels.itemsLabel(memberCount) })] }), _jsx("div", { className: "mt-2 w-full cursor-pointer rounded-md bg-violet-600 py-1.5 text-center text-xs font-semibold text-white transition-colors hover:bg-violet-700 active:scale-[0.98]", role: "button", tabIndex: 0, onClick: handleNavigate, onKeyDown: (e) => { if (e.key === "Enter" || e.key === " ")
|
|
55
|
+
handleNavigate(); }, children: "View Bundle" })] })] }));
|
|
56
|
+
}
|
|
@@ -115,5 +115,5 @@ export async function PrizeDrawDetailPageView({ id, initialPrizeDraw, currentUse
|
|
|
115
115
|
price: pricePerEntry,
|
|
116
116
|
storeId: typeof p.storeId === "string" ? p.storeId : undefined,
|
|
117
117
|
storeName: storeName ?? undefined,
|
|
118
|
-
} }), _jsxs(Container, { size: "xl", className: "px-4 py-6", children: [_jsxs("div", { className: "mb-4 flex items-center justify-between flex-wrap gap-2", children: [_jsxs("nav", { "aria-label": "Breadcrumb", className: "flex items-center gap-1.5 text-xs text-zinc-500 dark:text-zinc-400 flex-wrap", children: [_jsx(Link, { href: String(ROUTES.HOME), className: "hover:text-primary-600 transition-colors", children: "Home" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.PRIZE_DRAWS), className: "hover:text-primary-600 transition-colors", children: "Prize Draws" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Span, { className: "text-zinc-700 dark:text-zinc-300 truncate max-w-[200px]", children: title })] }), _jsx(ShareButton, { title: title })] }), _jsx(PreOrderDetailView, { renderGallery: () => (_jsx(PrizeDrawCollage, { items: items, hideWonState: true })), renderInfo: () => (_jsxs(Stack, { gap: "md", children: [_jsxs(Div, { children: [_jsxs(Row, { gap: "xs", className: "mb-2 flex-wrap", children: [_jsx(Span, { className: "inline-block rounded-full bg-fuchsia-100 dark:bg-fuchsia-900/30 px-2.5 py-0.5 text-xs font-semibold text-fuchsia-700 dark:text-fuchsia-300", children: "Prize Draw" }), _jsx(Span, { className: `inline-block rounded-full px-2.5 py-0.5 text-xs font-semibold ${statusClass(revealStatus)}`, children: statusLabel(revealStatus) }), maxPerUser !== null && (_jsxs(Span, { className: "inline-block rounded-full bg-zinc-100 dark:bg-zinc-800 px-2.5 py-0.5 text-xs font-medium text-zinc-700 dark:text-zinc-300", children: ["Limit: ", maxPerUser, " entries per customer"] })), maxPerUser !== null && userEntriesUsed !== null && (_jsxs(Span, { className: "inline-block rounded-full bg-fuchsia-100 dark:bg-fuchsia-900/30 px-2.5 py-0.5 text-xs font-semibold text-fuchsia-700 dark:text-fuchsia-300", children: ["You have used ", userEntriesUsed, "/", maxPerUser] }))] }), _jsx(Heading, { level: 1, className: "text-xl font-bold leading-snug text-zinc-900 dark:text-zinc-50 sm:text-2xl", children: title })] }), _jsx(Div, { className: "rounded-xl border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/60 p-4", children: _jsxs(Stack, { gap: "sm", children: [_jsxs(Row, { justify: "between", align: "center", children: [_jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: "Entries" }), _jsxs(Text, { className: "text-sm font-semibold", children: [current, " / ", max, " (", remaining, " left)"] })] }), revealStart && (_jsxs(Row, { justify: "between", align: "center", children: [_jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: "Reveal window opens" }), _jsx(Text, { className: "text-sm font-medium", children: revealStart.toLocaleString() })] })), revealEnd && (_jsxs(Row, { justify: "between", align: "center", children: [_jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: "Reveal window closes" }), _jsx(Text, { className: "text-sm font-medium", children: revealEnd.toLocaleString() })] }))] }) }), 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-500 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" }))] }) }))] })), renderTabs: () => (_jsx(ProductTabsShell, { descriptionContent: descriptionHtml ? (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm sm:prose max-w-none dark:prose-invert", className: "text-zinc-700 dark:text-zinc-300" })) : undefined })), renderBuyBar: () => (_jsx(Div, { id: "prize-draw-buy-bar", className: "rounded-xl border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/60 p-5", children: _jsx(PrizeDrawEntryActions, { productId: String(product.id), productSlug: slug, title: title, thumb: thumb, pricePerEntry: pricePerEntry, currency: currency, remainingEntries: remaining, revealStatus: revealStatus, prizeGithubFileUrl: githubUrl }) })) }), _jsxs(BuyBar, { children: [_jsx(Span, { className: "mr-auto text-sm font-bold text-zinc-900 dark:text-zinc-50", children: formatCurrency(pricePerEntry, currency) }), _jsx("a", { href: "#prize-draw-buy-bar", className: "appkit-button appkit-button--primary appkit-button--sm flex-1", children: _jsx("span", { className: "appkit-button__content", children: ACTIONS.PRIZE_DRAW["enter-draw"].label }) })] })] })] }));
|
|
118
|
+
} }), _jsxs(Container, { size: "xl", className: "px-4 py-6", children: [_jsxs("div", { className: "mb-4 flex items-center justify-between flex-wrap gap-2", children: [_jsxs("nav", { "aria-label": "Breadcrumb", className: "flex items-center gap-1.5 text-xs text-zinc-500 dark:text-zinc-400 flex-wrap", children: [_jsx(Link, { href: String(ROUTES.HOME), className: "hover:text-primary-600 transition-colors", children: "Home" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.PRIZE_DRAWS), className: "hover:text-primary-600 transition-colors", children: "Prize Draws" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Span, { className: "text-zinc-700 dark:text-zinc-300 truncate max-w-[200px]", children: title })] }), _jsx(ShareButton, { title: title })] }), _jsx(PreOrderDetailView, { renderGallery: () => (_jsx(PrizeDrawCollage, { items: items, hideWonState: true })), renderInfo: () => (_jsxs(Stack, { gap: "md", children: [_jsxs(Div, { children: [_jsxs(Row, { gap: "xs", className: "mb-2 flex-wrap", children: [_jsx(Span, { className: "inline-block rounded-full bg-fuchsia-100 dark:bg-fuchsia-900/30 px-2.5 py-0.5 text-xs font-semibold text-fuchsia-700 dark:text-fuchsia-300", children: "Prize Draw" }), _jsx(Span, { className: `inline-block rounded-full px-2.5 py-0.5 text-xs font-semibold ${statusClass(revealStatus)}`, children: statusLabel(revealStatus) }), maxPerUser !== null && (_jsxs(Span, { className: "inline-block rounded-full bg-zinc-100 dark:bg-zinc-800 px-2.5 py-0.5 text-xs font-medium text-zinc-700 dark:text-zinc-300", children: ["Limit: ", maxPerUser, " entries per customer"] })), maxPerUser !== null && userEntriesUsed !== null && (_jsxs(Span, { className: "inline-block rounded-full bg-fuchsia-100 dark:bg-fuchsia-900/30 px-2.5 py-0.5 text-xs font-semibold text-fuchsia-700 dark:text-fuchsia-300", children: ["You have used ", userEntriesUsed, "/", maxPerUser] }))] }), _jsx(Heading, { level: 1, className: "text-xl font-bold leading-snug text-zinc-900 dark:text-zinc-50 sm:text-2xl", children: title })] }), _jsx(Div, { className: "rounded-xl border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/60 p-4", children: _jsxs(Stack, { gap: "sm", children: [_jsxs(Row, { justify: "between", align: "center", children: [_jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: "Entries" }), _jsxs(Text, { className: "text-sm font-semibold", children: [current, " / ", max, " (", remaining, " left)"] })] }), revealStart && (_jsxs(Row, { justify: "between", align: "center", children: [_jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: "Reveal window opens" }), _jsx(Text, { className: "text-sm font-medium", children: revealStart.toLocaleString() })] })), revealEnd && (_jsxs(Row, { justify: "between", align: "center", children: [_jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: "Reveal window closes" }), _jsx(Text, { className: "text-sm font-medium", children: revealEnd.toLocaleString() })] }))] }) }), 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-500 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" }))] }) }))] })), renderTabs: () => (_jsx(ProductTabsShell, { descriptionContent: descriptionHtml ? (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm sm:prose max-w-none dark:prose-invert", className: "text-zinc-700 dark:text-zinc-300" })) : undefined })), renderBuyBar: () => (_jsx(Div, { id: "prize-draw-buy-bar", className: "rounded-xl border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/60 p-5", children: _jsx(PrizeDrawEntryActions, { productId: String(product.id), productSlug: slug, title: title, thumb: thumb, pricePerEntry: pricePerEntry, currency: currency, remainingEntries: remaining, revealStatus: revealStatus, prizeGithubFileUrl: githubUrl, storeId: typeof p.storeId === "string" ? p.storeId : undefined, storeName: storeName ?? undefined }) })) }), _jsxs(BuyBar, { children: [_jsx(Span, { className: "mr-auto text-sm font-bold text-zinc-900 dark:text-zinc-50", children: formatCurrency(pricePerEntry, currency) }), _jsx("a", { href: "#prize-draw-buy-bar", className: "appkit-button appkit-button--primary appkit-button--sm flex-1", children: _jsx("span", { className: "appkit-button__content", children: ACTIONS.PRIZE_DRAW["enter-draw"].label }) })] })] })] }));
|
|
119
119
|
}
|
|
@@ -8,6 +8,8 @@ export interface PrizeDrawEntryActionsProps {
|
|
|
8
8
|
remainingEntries: number;
|
|
9
9
|
revealStatus: "pending" | "open" | "closed" | undefined;
|
|
10
10
|
prizeGithubFileUrl?: string;
|
|
11
|
+
storeId?: string;
|
|
12
|
+
storeName?: string;
|
|
11
13
|
}
|
|
12
14
|
/**
|
|
13
15
|
* Client buy panel for a prize-draw detail page (SB4-G).
|
|
@@ -16,4 +18,4 @@ export interface PrizeDrawEntryActionsProps {
|
|
|
16
18
|
* - On confirm → add 1 entry to guest cart and route to /user/cart
|
|
17
19
|
* - "View RNG source" link → prizeGithubFileUrl
|
|
18
20
|
*/
|
|
19
|
-
export declare function PrizeDrawEntryActions({ productId, productSlug, title, thumb, pricePerEntry, currency, remainingEntries, revealStatus, prizeGithubFileUrl, }: PrizeDrawEntryActionsProps): import("react/jsx-runtime").JSX.Element;
|
|
21
|
+
export declare function PrizeDrawEntryActions({ productId, productSlug, title, thumb, pricePerEntry, currency, remainingEntries, revealStatus, prizeGithubFileUrl, storeId, storeName, }: PrizeDrawEntryActionsProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -18,7 +18,7 @@ import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
|
|
|
18
18
|
* - On confirm → add 1 entry to guest cart and route to /user/cart
|
|
19
19
|
* - "View RNG source" link → prizeGithubFileUrl
|
|
20
20
|
*/
|
|
21
|
-
export function PrizeDrawEntryActions({ productId, productSlug, title, thumb, pricePerEntry, currency, remainingEntries, revealStatus, prizeGithubFileUrl, }) {
|
|
21
|
+
export function PrizeDrawEntryActions({ productId, productSlug, title, thumb, pricePerEntry, currency, remainingEntries, revealStatus, prizeGithubFileUrl, storeId, storeName, }) {
|
|
22
22
|
const router = useRouter();
|
|
23
23
|
const cart = useGuestCart();
|
|
24
24
|
const { showToast } = useToast();
|
|
@@ -31,22 +31,18 @@ export function PrizeDrawEntryActions({ productId, productSlug, title, thumb, pr
|
|
|
31
31
|
requireAuth(ACTION_ID.ENTER_PRIZE_DRAW, () => setConsentOpen(true));
|
|
32
32
|
}, [closed, requireAuth]);
|
|
33
33
|
const handleConfirm = useCallback(() => {
|
|
34
|
-
|
|
34
|
+
const snapshot = {
|
|
35
35
|
productTitle: title,
|
|
36
36
|
productImage: thumb,
|
|
37
37
|
price: pricePerEntry,
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
productTitle: title,
|
|
44
|
-
productImage: thumb,
|
|
45
|
-
price: pricePerEntry,
|
|
46
|
-
});
|
|
38
|
+
storeId,
|
|
39
|
+
storeName,
|
|
40
|
+
};
|
|
41
|
+
cart.add(productId, 1, snapshot);
|
|
42
|
+
pushCartOp({ op: "add", productId, quantity: 1, ...snapshot });
|
|
47
43
|
setConsentOpen(false);
|
|
48
44
|
showToast("Entry added to cart", "success");
|
|
49
45
|
router.push(String(ROUTES.USER.CART));
|
|
50
|
-
}, [cart, productId, title, thumb, pricePerEntry, router, showToast]);
|
|
46
|
+
}, [cart, productId, title, thumb, pricePerEntry, storeId, storeName, router, showToast]);
|
|
51
47
|
return (_jsxs(Stack, { gap: "md", children: [_jsxs(Text, { className: "text-2xl font-bold text-zinc-900 dark:text-zinc-50", children: [formatCurrency(pricePerEntry, currency), _jsx(Text, { as: "span", className: "ml-1 text-xs font-normal text-[var(--appkit-color-text-muted)]", children: "per entry" })] }), _jsx(Button, { action: closed ? undefined : ACTIONS.PRIZE_DRAW["enter-draw"], variant: "primary", size: "md", className: "w-full", disabled: closed, onClick: handleEnter, children: closed ? "Draw closed" : undefined }), prizeGithubFileUrl ? (_jsx("a", { href: prizeGithubFileUrl, target: "_blank", rel: "noopener noreferrer", className: "text-center text-xs font-medium text-primary-600 underline-offset-4 hover:underline dark:text-primary-400", children: "View RNG source code on GitHub \u2192" })) : null, _jsx(Text, { className: "text-center text-xs text-[var(--appkit-color-text-muted)]", children: "Winners chosen by Node.js crypto.randomInt \u2014 fully auditable. Entries are locked once paid; refunds only if the prize pool is exhausted." }), _jsx(NonRefundableConsentModal, { open: consentOpen, listingType: "prize-draw", itemTitle: title, priceLabel: formatCurrency(pricePerEntry, currency), onCancel: () => setConsentOpen(false), onConfirm: handleConfirm }), _jsx(LoginRequiredModal, { isOpen: modalOpen, onClose: closeModal, message: modalMessage })] }));
|
|
52
48
|
}
|
|
@@ -5,6 +5,10 @@ export interface ProductDetailActionsProps {
|
|
|
5
5
|
productImage?: string;
|
|
6
6
|
price?: number;
|
|
7
7
|
currency?: string;
|
|
8
|
+
/** Store identifier (storeSlug) — propagated so guest cart lines carry it. */
|
|
9
|
+
storeId?: string;
|
|
10
|
+
/** Denormalised store display name — used by the cart group header. */
|
|
11
|
+
storeName?: string;
|
|
8
12
|
inStock: boolean;
|
|
9
13
|
variant?: "desktop" | "mobile";
|
|
10
14
|
}
|
|
@@ -13,4 +17,4 @@ export interface ProductDetailActionsProps {
|
|
|
13
17
|
* to the server APIs with guest-fallback. Replaces the previously-unwired
|
|
14
18
|
* placeholder buttons inside ProductDetailPageView.
|
|
15
19
|
*/
|
|
16
|
-
export declare function ProductDetailActions({ productId, productSlug, productTitle, productImage, price, inStock, variant, }: ProductDetailActionsProps): import("react/jsx-runtime").JSX.Element;
|
|
20
|
+
export declare function ProductDetailActions({ productId, productSlug, productTitle, productImage, price, storeId, storeName, inStock, variant, }: ProductDetailActionsProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -15,7 +15,7 @@ import { ACTION_ID } from "../constants/action-defs";
|
|
|
15
15
|
* to the server APIs with guest-fallback. Replaces the previously-unwired
|
|
16
16
|
* placeholder buttons inside ProductDetailPageView.
|
|
17
17
|
*/
|
|
18
|
-
export function ProductDetailActions({ productId, productSlug, productTitle, productImage, price, inStock, variant = "desktop", }) {
|
|
18
|
+
export function ProductDetailActions({ productId, productSlug, productTitle, productImage, price, storeId, storeName, inStock, variant = "desktop", }) {
|
|
19
19
|
const router = useRouter();
|
|
20
20
|
const { showToast } = useToast();
|
|
21
21
|
const { requireAuth, modalOpen, modalMessage, closeModal } = useAuthGate();
|
|
@@ -34,6 +34,8 @@ export function ProductDetailActions({ productId, productSlug, productTitle, pro
|
|
|
34
34
|
productTitle,
|
|
35
35
|
productImage,
|
|
36
36
|
price,
|
|
37
|
+
storeId,
|
|
38
|
+
storeName,
|
|
37
39
|
});
|
|
38
40
|
then?.();
|
|
39
41
|
}
|
|
@@ -270,6 +270,8 @@ export async function ProductDetailPageView({ slug, initialProduct, renderOfferA
|
|
|
270
270
|
productImage: product.mainImage,
|
|
271
271
|
price,
|
|
272
272
|
currency,
|
|
273
|
+
storeId: typeof p.storeId === "string" ? p.storeId : undefined,
|
|
274
|
+
storeName: typeof p.storeName === "string" ? p.storeName : undefined,
|
|
273
275
|
inStock,
|
|
274
276
|
variant: "desktop",
|
|
275
277
|
})) : (_jsxs(_Fragment, { children: [_jsx(Button, { variant: "primary", size: "md", className: "w-full", disabled: true, title: ACTION_NOT_WIRED, children: inStock ? "Buy Now" : "Out of Stock" }), _jsx(Button, { variant: "secondary", size: "md", className: "w-full", disabled: true, title: ACTION_NOT_WIRED, children: inStock ? "Add to Cart" : "Out of Stock" }), _jsx(Button, { variant: "ghost", size: "md", className: "w-full", disabled: true, title: ACTION_NOT_WIRED, children: "\u2661 Add to Wishlist" })] })), allowOffers && productType === "simple" && price !== null && renderOfferAction?.({
|
|
@@ -298,6 +300,8 @@ export async function ProductDetailPageView({ slug, initialProduct, renderOfferA
|
|
|
298
300
|
productImage: product.mainImage,
|
|
299
301
|
price,
|
|
300
302
|
currency,
|
|
303
|
+
storeId: typeof p.storeId === "string" ? p.storeId : undefined,
|
|
304
|
+
storeName: typeof p.storeName === "string" ? p.storeName : undefined,
|
|
301
305
|
inStock,
|
|
302
306
|
variant: "mobile",
|
|
303
307
|
})) : (_jsxs(_Fragment, { children: [_jsx(Button, { variant: "secondary", size: "sm", className: "shrink-0", disabled: true, title: ACTION_NOT_WIRED, children: "Add to Cart" }), _jsx(Button, { variant: "primary", size: "sm", className: "flex-1", disabled: true, title: ACTION_NOT_WIRED, children: inStock ? "Buy Now" : "Out of Stock" })] }))] })] })] }));
|