@mohasinac/appkit 2.6.0 → 2.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_internal/client/features/layout/DashboardLayoutClient.d.ts +38 -0
- package/dist/_internal/client/features/layout/DashboardLayoutClient.js +75 -0
- package/dist/_internal/client/features/layout/RoleGuard.d.ts +15 -0
- package/dist/_internal/client/features/layout/RoleGuard.js +25 -0
- package/dist/_internal/client/features/layout/index.d.ts +5 -0
- package/dist/_internal/client/features/layout/index.js +4 -0
- package/dist/_internal/server/features/brands/actions.d.ts +3 -3
- package/dist/_internal/server/features/brands/actions.js +72 -5
- package/dist/_internal/server/features/brands/data.d.ts +8 -8
- package/dist/_internal/server/features/brands/data.js +10 -11
- package/dist/_internal/server/features/brands/service.d.ts +2 -2
- package/dist/_internal/server/features/brands/service.js +5 -5
- package/dist/_internal/server/features/categories/og.d.ts +33 -0
- package/dist/_internal/server/features/categories/og.js +75 -0
- package/dist/_internal/server/features/checkout/actions.d.ts +24 -0
- package/dist/_internal/server/features/checkout/actions.js +442 -13
- package/dist/_internal/server/features/checkout/index.d.ts +1 -1
- package/dist/_internal/server/features/checkout/index.js +1 -1
- package/dist/_internal/server/features/checkout/prize-bundle-gates.d.ts +59 -0
- package/dist/_internal/server/features/checkout/prize-bundle-gates.js +99 -0
- package/dist/_internal/server/features/grouped/data.js +12 -5
- package/dist/_internal/server/features/homepage/data.d.ts +1 -1
- package/dist/_internal/server/features/homepage/data.js +2 -2
- package/dist/_internal/server/features/media/contextGuards.d.ts +52 -0
- package/dist/_internal/server/features/media/contextGuards.js +198 -0
- package/dist/_internal/server/features/orders/adapters.js +12 -0
- package/dist/_internal/server/features/products/data.d.ts +1 -1
- package/dist/_internal/server/features/sublisting-categories/data.d.ts +1 -1
- package/dist/_internal/server/features/sublisting-categories/data.js +2 -2
- package/dist/_internal/server/jobs/handlers/assignSpinPrize.d.ts +24 -0
- package/dist/_internal/server/jobs/handlers/assignSpinPrize.js +86 -0
- package/dist/_internal/server/jobs/handlers/bundleStockSync.d.ts +18 -0
- package/dist/_internal/server/jobs/handlers/bundleStockSync.js +80 -0
- package/dist/_internal/server/jobs/handlers/index.d.ts +8 -0
- package/dist/_internal/server/jobs/handlers/index.js +13 -0
- package/dist/_internal/server/jobs/handlers/listingProcessor.js +13 -3
- package/dist/_internal/server/jobs/handlers/onProductStockChange.d.ts +17 -0
- package/dist/_internal/server/jobs/handlers/onProductStockChange.js +136 -0
- package/dist/_internal/server/jobs/handlers/onProductWrite.js +17 -1
- package/dist/_internal/server/jobs/handlers/prizeRevealClose.d.ts +9 -0
- package/dist/_internal/server/jobs/handlers/prizeRevealClose.js +29 -0
- package/dist/_internal/server/jobs/handlers/prizeRevealExpiry.d.ts +10 -0
- package/dist/_internal/server/jobs/handlers/prizeRevealExpiry.js +58 -0
- package/dist/_internal/server/jobs/handlers/prizeRevealOpen.d.ts +10 -0
- package/dist/_internal/server/jobs/handlers/prizeRevealOpen.js +65 -0
- package/dist/_internal/server/jobs/handlers/prizeRevealReminder.d.ts +9 -0
- package/dist/_internal/server/jobs/handlers/prizeRevealReminder.js +45 -0
- package/dist/_internal/server/jobs/handlers/triggerEventRaffle.d.ts +30 -0
- package/dist/_internal/server/jobs/handlers/triggerEventRaffle.js +94 -0
- package/dist/_internal/shared/features/brands/schema.d.ts +3 -3
- package/dist/_internal/shared/features/cart/schema.d.ts +8 -8
- package/dist/_internal/shared/features/cart/schema.js +1 -1
- package/dist/_internal/shared/features/categories/bundle-config.d.ts +17 -0
- package/dist/_internal/shared/features/categories/bundle-config.js +17 -0
- package/dist/_internal/shared/features/layout/config.d.ts +35 -0
- package/dist/_internal/shared/features/layout/config.js +58 -0
- package/dist/_internal/shared/features/layout/index.d.ts +3 -0
- package/dist/_internal/shared/features/layout/index.js +2 -0
- package/dist/_internal/shared/features/layout/types.d.ts +137 -0
- package/dist/_internal/shared/features/layout/types.js +13 -0
- package/dist/_internal/shared/features/products/types.d.ts +1 -1
- package/dist/_internal/shared/listing-types/_registry.d.ts +57 -0
- package/dist/_internal/shared/listing-types/_registry.js +28 -0
- package/dist/_internal/shared/listing-types/auction/config.d.ts +7 -0
- package/dist/_internal/shared/listing-types/auction/config.js +8 -0
- package/dist/_internal/shared/listing-types/auction/ctas.d.ts +1 -0
- package/dist/_internal/shared/listing-types/auction/ctas.js +2 -0
- package/dist/_internal/shared/listing-types/auction/og.d.ts +1 -0
- package/dist/_internal/shared/listing-types/auction/og.js +1 -0
- package/dist/_internal/shared/listing-types/auction/schema.d.ts +1 -0
- package/dist/_internal/shared/listing-types/auction/schema.js +1 -0
- package/dist/_internal/shared/listing-types/auction/seed-factory.d.ts +1 -0
- package/dist/_internal/shared/listing-types/auction/seed-factory.js +1 -0
- package/dist/_internal/shared/listing-types/capabilities.d.ts +41 -0
- package/dist/_internal/shared/listing-types/capabilities.js +75 -0
- package/dist/_internal/shared/listing-types/pre-order/config.d.ts +7 -0
- package/dist/_internal/shared/listing-types/pre-order/config.js +8 -0
- package/dist/_internal/shared/listing-types/pre-order/ctas.d.ts +1 -0
- package/dist/_internal/shared/listing-types/pre-order/ctas.js +2 -0
- package/dist/_internal/shared/listing-types/pre-order/og.d.ts +1 -0
- package/dist/_internal/shared/listing-types/pre-order/og.js +1 -0
- package/dist/_internal/shared/listing-types/pre-order/schema.d.ts +1 -0
- package/dist/_internal/shared/listing-types/pre-order/schema.js +1 -0
- package/dist/_internal/shared/listing-types/pre-order/seed-factory.d.ts +1 -0
- package/dist/_internal/shared/listing-types/pre-order/seed-factory.js +1 -0
- package/dist/_internal/shared/listing-types/prize-draw/config.d.ts +7 -0
- package/dist/_internal/shared/listing-types/prize-draw/config.js +8 -0
- package/dist/_internal/shared/listing-types/prize-draw/ctas.d.ts +1 -0
- package/dist/_internal/shared/listing-types/prize-draw/ctas.js +2 -0
- package/dist/_internal/shared/listing-types/prize-draw/og.d.ts +1 -0
- package/dist/_internal/shared/listing-types/prize-draw/og.js +1 -0
- package/dist/_internal/shared/listing-types/prize-draw/schema.d.ts +1 -0
- package/dist/_internal/shared/listing-types/prize-draw/schema.js +1 -0
- package/dist/_internal/shared/listing-types/prize-draw/seed-factory.d.ts +1 -0
- package/dist/_internal/shared/listing-types/prize-draw/seed-factory.js +1 -0
- package/dist/_internal/shared/listing-types/standard/config.d.ts +7 -0
- package/dist/_internal/shared/listing-types/standard/config.js +8 -0
- package/dist/_internal/shared/listing-types/standard/ctas.d.ts +1 -0
- package/dist/_internal/shared/listing-types/standard/ctas.js +3 -0
- package/dist/_internal/shared/listing-types/standard/og.d.ts +1 -0
- package/dist/_internal/shared/listing-types/standard/og.js +1 -0
- package/dist/_internal/shared/listing-types/standard/schema.d.ts +1 -0
- package/dist/_internal/shared/listing-types/standard/schema.js +1 -0
- package/dist/_internal/shared/listing-types/standard/seed-factory.d.ts +1 -0
- package/dist/_internal/shared/listing-types/standard/seed-factory.js +1 -0
- package/dist/_internal/shared/media/limits.d.ts +33 -0
- package/dist/_internal/shared/media/limits.js +97 -0
- package/dist/_internal/shared/schema-versions.d.ts +76 -0
- package/dist/_internal/shared/schema-versions.js +82 -0
- package/dist/client.d.ts +9 -0
- package/dist/client.js +7 -0
- package/dist/constants/api-endpoints.d.ts +6 -3
- package/dist/constants/api-endpoints.js +2 -1
- package/dist/errors/messages.d.ts +1 -1
- package/dist/errors/messages.js +1 -1
- package/dist/features/account/migrations.d.ts +2 -0
- package/dist/features/account/migrations.js +10 -0
- package/dist/features/admin/components/AdminMediaView.js +1 -1
- package/dist/features/admin/components/AdminProductsView.js +7 -3
- package/dist/features/admin/migrations.d.ts +2 -0
- package/dist/features/admin/migrations.js +10 -0
- package/dist/features/admin/types/product.types.d.ts +1 -1
- package/dist/features/auctions/components/MarketplaceAuctionCard.d.ts +1 -1
- package/dist/features/auctions/migrations.d.ts +2 -0
- package/dist/features/auctions/migrations.js +10 -0
- package/dist/features/auctions/schemas/index.d.ts +3 -3
- package/dist/features/auctions/schemas/index.js +1 -1
- package/dist/features/auth/migrations.d.ts +2 -0
- package/dist/features/auth/migrations.js +10 -0
- package/dist/features/blog/migrations.d.ts +2 -0
- package/dist/features/blog/migrations.js +10 -0
- package/dist/features/brands/migrations.d.ts +2 -0
- package/dist/features/brands/migrations.js +10 -0
- package/dist/features/bundles/components/BundlesByCategoryListing.d.ts +6 -0
- package/dist/features/bundles/components/BundlesByCategoryListing.js +50 -0
- package/dist/features/bundles/components/index.d.ts +2 -0
- package/dist/features/bundles/components/index.js +1 -0
- package/dist/features/bundles/migrations.d.ts +2 -0
- package/dist/features/bundles/migrations.js +10 -0
- package/dist/features/bundles/schemas/index.d.ts +1 -0
- package/dist/features/bundles/schemas/index.js +1 -0
- package/dist/features/bundles/schemas/zod.d.ts +377 -0
- package/dist/features/bundles/schemas/zod.js +71 -0
- package/dist/features/cart/migrations.d.ts +2 -0
- package/dist/features/cart/migrations.js +10 -0
- package/dist/features/cart/schemas/firestore.d.ts +2 -2
- package/dist/features/categories/components/BrandDetailPageView.js +35 -4
- package/dist/features/categories/components/BrandDetailTabs.d.ts +5 -1
- package/dist/features/categories/components/BrandDetailTabs.js +22 -8
- package/dist/features/categories/components/CategoryBundlesListing.d.ts +6 -0
- package/dist/features/categories/components/CategoryBundlesListing.js +74 -0
- package/dist/features/categories/components/CategoryDetailPageView.js +29 -4
- package/dist/features/categories/components/CategoryDetailTabs.d.ts +5 -1
- package/dist/features/categories/components/CategoryDetailTabs.js +22 -8
- package/dist/features/categories/migrations.d.ts +2 -0
- package/dist/features/categories/migrations.js +10 -0
- package/dist/features/categories/repository/categories.repository.d.ts +29 -0
- package/dist/features/categories/repository/categories.repository.js +83 -0
- package/dist/features/categories/schemas/firestore.d.ts +59 -2
- package/dist/features/categories/schemas/firestore.js +6 -0
- package/dist/features/categories/types/index.d.ts +11 -3
- package/dist/features/events/migrations.d.ts +2 -0
- package/dist/features/events/migrations.js +10 -0
- package/dist/features/faq/migrations.d.ts +2 -0
- package/dist/features/faq/migrations.js +10 -0
- package/dist/features/grouped/migrations.d.ts +2 -0
- package/dist/features/grouped/migrations.js +10 -0
- package/dist/features/grouped/schemas/firestore.d.ts +29 -10
- package/dist/features/grouped/schemas/firestore.js +10 -5
- package/dist/features/history/migrations.d.ts +2 -0
- package/dist/features/history/migrations.js +10 -0
- package/dist/features/homepage/hooks/useFeaturedAuctions.js +2 -2
- package/dist/features/homepage/hooks/useFeaturedPreOrders.js +2 -2
- package/dist/features/homepage/lib/section-renderer.js +5 -3
- package/dist/features/media/AvatarUpload.js +6 -28
- package/dist/features/media/hooks/useMedia.d.ts +31 -15
- package/dist/features/media/hooks/useMedia.js +48 -13
- package/dist/features/media/upload/ImageUpload.js +1 -1
- package/dist/features/media/upload/MediaUploadField.js +1 -1
- package/dist/features/messages/migrations.d.ts +2 -0
- package/dist/features/messages/migrations.js +10 -0
- package/dist/features/orders/components/OrdersList.js +10 -1
- package/dist/features/orders/migrations.d.ts +2 -0
- package/dist/features/orders/migrations.js +10 -0
- package/dist/features/orders/repository/orders.repository.d.ts +16 -0
- package/dist/features/orders/repository/orders.repository.js +49 -0
- package/dist/features/orders/schemas/firestore.d.ts +8 -0
- package/dist/features/orders/types/index.d.ts +12 -0
- package/dist/features/orders/utils/order-splitter.d.ts +2 -2
- package/dist/features/orders/utils/order-splitter.js +5 -0
- package/dist/features/payments/migrations.d.ts +2 -0
- package/dist/features/payments/migrations.js +10 -0
- package/dist/features/pre-orders/components/PreOrderDetailPageView.js +4 -1
- package/dist/features/products/actions/product-actions.d.ts +1 -1
- package/dist/features/products/api/[id]/route.js +34 -0
- package/dist/features/products/api/route.js +1 -19
- package/dist/features/products/components/CompareOverlay.d.ts +1 -1
- package/dist/features/products/components/MarketplacePrizeDrawCard.d.ts +24 -0
- package/dist/features/products/components/MarketplacePrizeDrawCard.js +102 -0
- package/dist/features/products/components/PrizeDrawCollage.d.ts +32 -0
- package/dist/features/products/components/PrizeDrawCollage.js +22 -0
- package/dist/features/products/components/PrizeDrawDetailPageView.d.ts +27 -0
- package/dist/features/products/components/PrizeDrawDetailPageView.js +118 -0
- package/dist/features/products/components/PrizeDrawEntryActions.d.ts +19 -0
- package/dist/features/products/components/PrizeDrawEntryActions.js +48 -0
- package/dist/features/products/components/PrizeDrawItemsEditor.d.ts +13 -0
- package/dist/features/products/components/PrizeDrawItemsEditor.js +97 -0
- package/dist/features/products/components/PrizeDrawsIndexListing.d.ts +8 -0
- package/dist/features/products/components/PrizeDrawsIndexListing.js +128 -0
- package/dist/features/products/components/PrizeDrawsListingView.d.ts +15 -0
- package/dist/features/products/components/PrizeDrawsListingView.js +49 -0
- package/dist/features/products/components/PrizeRevealModal.d.ts +34 -0
- package/dist/features/products/components/PrizeRevealModal.js +124 -0
- package/dist/features/products/components/ProductDetailPageView.js +13 -1
- package/dist/features/products/components/ProductForm.js +35 -2
- package/dist/features/products/components/ProductGrid.js +3 -1
- package/dist/features/products/components/index.d.ts +16 -0
- package/dist/features/products/components/index.js +8 -0
- package/dist/features/products/constants/listing-tabs.d.ts +113 -0
- package/dist/features/products/constants/listing-tabs.js +43 -0
- package/dist/features/products/index.d.ts +1 -0
- package/dist/features/products/index.js +1 -0
- package/dist/features/products/migrations.d.ts +2 -0
- package/dist/features/products/migrations.js +10 -0
- package/dist/features/products/repository/products.repository.d.ts +11 -7
- package/dist/features/products/repository/products.repository.js +49 -24
- package/dist/features/products/schemas/firestore.d.ts +3 -3
- package/dist/features/products/schemas/firestore.js +2 -2
- package/dist/features/products/schemas/index.d.ts +5 -5
- package/dist/features/products/schemas/index.js +3 -1
- package/dist/features/products/schemas/product-features.validators.d.ts +6 -6
- package/dist/features/products/types/index.d.ts +17 -1
- package/dist/features/products/utils/listing-type.d.ts +7 -4
- package/dist/features/products/utils/listing-type.js +8 -4
- package/dist/features/promotions/actions/coupon-actions.d.ts +1 -1
- package/dist/features/promotions/hooks/useCouponValidate.d.ts +1 -1
- package/dist/features/promotions/migrations.d.ts +2 -0
- package/dist/features/promotions/migrations.js +10 -0
- package/dist/features/promotions/repository/coupons.repository.d.ts +1 -1
- package/dist/features/promotions/schemas/index.d.ts +2 -2
- package/dist/features/reviews/migrations.d.ts +2 -0
- package/dist/features/reviews/migrations.js +10 -0
- package/dist/features/scams/migrations.d.ts +2 -0
- package/dist/features/scams/migrations.js +10 -0
- package/dist/features/search/api/route.d.ts +1 -1
- package/dist/features/search/api/route.js +3 -3
- package/dist/features/search/components/Search.d.ts +1 -1
- package/dist/features/search/schemas/index.d.ts +3 -3
- package/dist/features/search/schemas/index.js +3 -1
- package/dist/features/search/types/index.d.ts +2 -2
- package/dist/features/seller/components/SellerProductShell.d.ts +1 -1
- package/dist/features/seller/components/SellerProductsView.js +20 -6
- package/dist/features/seller/migrations.d.ts +2 -0
- package/dist/features/seller/migrations.js +10 -0
- package/dist/features/seller/schemas/index.d.ts +2 -2
- package/dist/features/stores/components/StoreBundlesPageView.d.ts +12 -0
- package/dist/features/stores/components/StoreBundlesPageView.js +24 -0
- package/dist/features/stores/components/StoreDetailLayoutView.js +15 -3
- package/dist/features/stores/components/StorePrizeDrawsPageView.d.ts +11 -0
- package/dist/features/stores/components/StorePrizeDrawsPageView.js +27 -0
- package/dist/features/stores/components/index.d.ts +2 -0
- package/dist/features/stores/migrations.d.ts +2 -0
- package/dist/features/stores/migrations.js +10 -0
- package/dist/features/stores/schemas/index.d.ts +2 -2
- package/dist/features/stores/types/index.d.ts +1 -1
- package/dist/features/sublisting/migrations.d.ts +2 -0
- package/dist/features/sublisting/migrations.js +10 -0
- package/dist/features/sublisting/schemas/firestore.d.ts +2 -0
- package/dist/features/support/migrations.d.ts +2 -0
- package/dist/features/support/migrations.js +10 -0
- package/dist/features/wishlist/migrations.d.ts +2 -0
- package/dist/features/wishlist/migrations.js +10 -0
- package/dist/features/wishlist/types/index.d.ts +1 -1
- package/dist/index.d.ts +26 -18
- package/dist/index.js +41 -24
- package/dist/jobs.d.ts +1 -1
- package/dist/jobs.js +4 -0
- package/dist/next/api/routeHandler.js +6 -4
- package/dist/next/routing/route-map.d.ts +4 -0
- package/dist/next/routing/route-map.js +2 -0
- package/dist/providers/db-firebase/filter-aliases.d.ts +2 -2
- package/dist/repositories/index.d.ts +0 -5
- package/dist/repositories/index.js +5 -4
- package/dist/seed/actions/demo-seed-actions.d.ts +1 -1
- package/dist/seed/categories-seed-data.js +1105 -6
- package/dist/seed/faq-seed-data.js +160 -0
- package/dist/seed/grouped-listings-seed-data.js +32 -32
- package/dist/seed/homepage-sections-seed-data.js +52 -6
- package/dist/seed/index.d.ts +1 -3
- package/dist/seed/index.js +4 -3
- package/dist/seed/manifest.js +8 -13
- package/dist/seed/products-prize-draws-seed-data.d.ts +17 -0
- package/dist/seed/products-prize-draws-seed-data.js +313 -0
- package/dist/seo/json-ld.d.ts +1 -1
- package/dist/server-entry.d.ts +2 -2
- package/dist/server-entry.js +5 -3
- package/dist/server.d.ts +9 -2
- package/dist/server.js +11 -5
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/Button.js +21 -2
- package/dist/ui/components/Button.style.css +34 -0
- package/dist/validation/schemas.d.ts +8 -8
- package/package.json +1 -1
- package/scripts/seed-cli.mjs +2 -4
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import Link from "next/link";
|
|
3
|
+
import { orderRepository, productRepository } from "../../../repositories";
|
|
4
|
+
import { ROUTES } from "../../../next";
|
|
5
|
+
import { getDefaultCurrency } from "../../../core/baseline-resolver";
|
|
6
|
+
import { normalizeRichTextHtml } from "../../../utils/string.formatter";
|
|
7
|
+
import { safeDisplayName } from "../../../security";
|
|
8
|
+
import { Container, Div, Heading, Main, RichText, Row, Section, Span, Stack, Text, } from "../../../ui";
|
|
9
|
+
import { PreOrderDetailView } from "./PreOrderDetailView";
|
|
10
|
+
import { BuyBar } from "./BuyBar";
|
|
11
|
+
import { ProductTabsShell } from "./ProductTabsShell";
|
|
12
|
+
import { ShareButton } from "./ShareButton";
|
|
13
|
+
import { PrizeDrawCollage } from "./PrizeDrawCollage";
|
|
14
|
+
import { PrizeDrawEntryActions } from "./PrizeDrawEntryActions";
|
|
15
|
+
import { HistoryTracker } from "../../history/components/HistoryTracker";
|
|
16
|
+
import { formatCurrency } from "../../../utils/number.formatter";
|
|
17
|
+
function toDescriptionHtml(raw) {
|
|
18
|
+
if (!raw)
|
|
19
|
+
return "";
|
|
20
|
+
const s = typeof raw === "string" ? raw : JSON.stringify(raw);
|
|
21
|
+
return normalizeRichTextHtml(s);
|
|
22
|
+
}
|
|
23
|
+
function statusLabel(s) {
|
|
24
|
+
switch (s) {
|
|
25
|
+
case "open":
|
|
26
|
+
return "Reveal open";
|
|
27
|
+
case "closed":
|
|
28
|
+
return "Draw closed";
|
|
29
|
+
case "pending":
|
|
30
|
+
default:
|
|
31
|
+
return "Reveal pending";
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function statusClass(s) {
|
|
35
|
+
switch (s) {
|
|
36
|
+
case "open":
|
|
37
|
+
return "bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-300";
|
|
38
|
+
case "closed":
|
|
39
|
+
return "bg-zinc-200 text-zinc-700 dark:bg-zinc-700 dark:text-zinc-200";
|
|
40
|
+
case "pending":
|
|
41
|
+
default:
|
|
42
|
+
return "bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-300";
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function stripIsWon(items) {
|
|
46
|
+
if (!items)
|
|
47
|
+
return [];
|
|
48
|
+
return items.map((it) => ({ ...it, isWon: false }));
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Public prize-draw detail page (SB4-G).
|
|
52
|
+
*
|
|
53
|
+
* Server-fetches the prize-draw product, strips `isWon` from each item before
|
|
54
|
+
* passing it to `PrizeDrawCollage` so public buyers stay unspoiled, and
|
|
55
|
+
* delegates the buy-bar to the client `PrizeDrawEntryActions` which surfaces
|
|
56
|
+
* the `NonRefundableConsentModal` before any add-to-cart happens.
|
|
57
|
+
*
|
|
58
|
+
* Reuses the `PreOrderDetailView` (grid-2) shell for layout parity.
|
|
59
|
+
*/
|
|
60
|
+
export async function PrizeDrawDetailPageView({ id, initialPrizeDraw, currentUserId, }) {
|
|
61
|
+
const product = initialPrizeDraw !== undefined
|
|
62
|
+
? initialPrizeDraw ?? undefined
|
|
63
|
+
: await productRepository.findByIdOrSlug(id).catch(() => undefined);
|
|
64
|
+
if (!product) {
|
|
65
|
+
return (_jsx(Main, { children: _jsx(Section, { className: "py-20", children: _jsx(Container, { size: "md", children: _jsxs(Stack, { align: "center", gap: "md", className: "text-center", children: [_jsx(Heading, { level: 1, className: "text-2xl font-semibold text-zinc-900 dark:text-zinc-50", children: "Prize Draw Not Found" }), _jsx(Text, { className: "text-zinc-500", children: "The prize draw you are looking for may have been removed." }), _jsx(Link, { href: String(ROUTES.PUBLIC.PRIZE_DRAWS), className: "text-sm font-medium text-primary-600 hover:underline", children: "Browse Prize Draws" })] }) }) }) }));
|
|
66
|
+
}
|
|
67
|
+
const p = product;
|
|
68
|
+
const currency = p.currency || getDefaultCurrency();
|
|
69
|
+
const title = String(p.title ?? p.name ?? "Prize Draw");
|
|
70
|
+
const slug = typeof p.slug === "string" ? p.slug : String(p.id ?? "");
|
|
71
|
+
const items = stripIsWon(p.prizeDrawItems);
|
|
72
|
+
const pricePerEntry = typeof p.pricePerEntry === "number"
|
|
73
|
+
? p.pricePerEntry
|
|
74
|
+
: typeof p.price === "number"
|
|
75
|
+
? p.price
|
|
76
|
+
: 0;
|
|
77
|
+
const max = typeof p.prizeMaxEntries === "number" ? p.prizeMaxEntries : 0;
|
|
78
|
+
const current = typeof p.prizeCurrentEntries === "number" ? p.prizeCurrentEntries : 0;
|
|
79
|
+
const remaining = Math.max(0, max - current);
|
|
80
|
+
const revealStatus = typeof p.prizeRevealStatus === "string"
|
|
81
|
+
? p.prizeRevealStatus
|
|
82
|
+
: undefined;
|
|
83
|
+
const revealStart = p.prizeRevealWindowStart
|
|
84
|
+
? new Date(p.prizeRevealWindowStart)
|
|
85
|
+
: null;
|
|
86
|
+
const revealEnd = p.prizeRevealWindowEnd
|
|
87
|
+
? new Date(p.prizeRevealWindowEnd)
|
|
88
|
+
: null;
|
|
89
|
+
const githubUrl = typeof p.prizeGithubFileUrl === "string" ? p.prizeGithubFileUrl : undefined;
|
|
90
|
+
const maxPerUser = typeof p.maxPerUser === "number" && p.maxPerUser > 0
|
|
91
|
+
? p.maxPerUser
|
|
92
|
+
: null;
|
|
93
|
+
// SB6-D post-auth — fetch this buyer's existing-entry count when we have
|
|
94
|
+
// an authenticated uid. `orderRepository.countByUserAndProduct` already
|
|
95
|
+
// filters by active statuses (pending/confirmed/processing/shipped/
|
|
96
|
+
// delivered) so cancelled/refunded entries don't count.
|
|
97
|
+
const userEntriesUsed = currentUserId && maxPerUser != null
|
|
98
|
+
? await orderRepository
|
|
99
|
+
.countByUserAndProduct(currentUserId, String(product.id))
|
|
100
|
+
.catch(() => 0)
|
|
101
|
+
: null;
|
|
102
|
+
const thumb = items[0]?.images?.[0];
|
|
103
|
+
const storeName = typeof p.storeName === "string" ? p.storeName : null;
|
|
104
|
+
const safeSeller = storeName ? safeDisplayName(storeName, "") : null;
|
|
105
|
+
const storeSlug = (typeof p.storeSlug === "string" ? p.storeSlug : null) ||
|
|
106
|
+
(typeof p.storeId === "string" ? p.storeId : null);
|
|
107
|
+
const storeHref = storeSlug
|
|
108
|
+
? String(ROUTES.PUBLIC.STORE_DETAIL(storeSlug))
|
|
109
|
+
: null;
|
|
110
|
+
const descriptionHtml = toDescriptionHtml(p.description);
|
|
111
|
+
return (_jsxs(Main, { children: [_jsx(HistoryTracker, { productId: String(p.id ?? p.slug ?? ""), productType: "product", snapshot: {
|
|
112
|
+
title,
|
|
113
|
+
thumb,
|
|
114
|
+
price: pricePerEntry,
|
|
115
|
+
storeId: typeof p.storeId === "string" ? p.storeId : undefined,
|
|
116
|
+
storeName: storeName ?? undefined,
|
|
117
|
+
} }), _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: "Enter draw" }) })] })] })] }));
|
|
118
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface PrizeDrawEntryActionsProps {
|
|
2
|
+
productId: string;
|
|
3
|
+
productSlug?: string;
|
|
4
|
+
title: string;
|
|
5
|
+
thumb?: string;
|
|
6
|
+
pricePerEntry: number;
|
|
7
|
+
currency: string;
|
|
8
|
+
remainingEntries: number;
|
|
9
|
+
revealStatus: "pending" | "open" | "closed" | undefined;
|
|
10
|
+
prizeGithubFileUrl?: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Client buy panel for a prize-draw detail page (SB4-G).
|
|
14
|
+
*
|
|
15
|
+
* - Enter Draw → NonRefundableConsentModal (listingType="prize-draw")
|
|
16
|
+
* - On confirm → add 1 entry to guest cart and route to /user/cart
|
|
17
|
+
* - "View RNG source" link → prizeGithubFileUrl
|
|
18
|
+
*/
|
|
19
|
+
export declare function PrizeDrawEntryActions({ productId, productSlug, title, thumb, pricePerEntry, currency, remainingEntries, revealStatus, prizeGithubFileUrl, }: PrizeDrawEntryActionsProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useCallback, useState } from "react";
|
|
4
|
+
import { useRouter } from "next/navigation";
|
|
5
|
+
import { Button, Stack, Text, useToast } from "../../../ui";
|
|
6
|
+
import { ROUTES } from "../../../next";
|
|
7
|
+
import { formatCurrency } from "../../../utils/number.formatter";
|
|
8
|
+
import { useGuestCart } from "../../cart/hooks/useGuestCart";
|
|
9
|
+
import { pushCartOp } from "../../cart/utils/pending-ops";
|
|
10
|
+
import { NonRefundableConsentModal } from "./NonRefundableConsentModal";
|
|
11
|
+
/**
|
|
12
|
+
* Client buy panel for a prize-draw detail page (SB4-G).
|
|
13
|
+
*
|
|
14
|
+
* - Enter Draw → NonRefundableConsentModal (listingType="prize-draw")
|
|
15
|
+
* - On confirm → add 1 entry to guest cart and route to /user/cart
|
|
16
|
+
* - "View RNG source" link → prizeGithubFileUrl
|
|
17
|
+
*/
|
|
18
|
+
export function PrizeDrawEntryActions({ productId, productSlug, title, thumb, pricePerEntry, currency, remainingEntries, revealStatus, prizeGithubFileUrl, }) {
|
|
19
|
+
const router = useRouter();
|
|
20
|
+
const cart = useGuestCart();
|
|
21
|
+
const { showToast } = useToast();
|
|
22
|
+
const [consentOpen, setConsentOpen] = useState(false);
|
|
23
|
+
const closed = revealStatus === "closed" || remainingEntries === 0;
|
|
24
|
+
const handleEnter = useCallback(() => {
|
|
25
|
+
if (closed)
|
|
26
|
+
return;
|
|
27
|
+
setConsentOpen(true);
|
|
28
|
+
}, [closed]);
|
|
29
|
+
const handleConfirm = useCallback(() => {
|
|
30
|
+
cart.add(productId, 1, {
|
|
31
|
+
productTitle: title,
|
|
32
|
+
productImage: thumb,
|
|
33
|
+
price: pricePerEntry,
|
|
34
|
+
});
|
|
35
|
+
pushCartOp({
|
|
36
|
+
op: "add",
|
|
37
|
+
productId,
|
|
38
|
+
quantity: 1,
|
|
39
|
+
productTitle: title,
|
|
40
|
+
productImage: thumb,
|
|
41
|
+
price: pricePerEntry,
|
|
42
|
+
});
|
|
43
|
+
setConsentOpen(false);
|
|
44
|
+
showToast("Entry added to cart", "success");
|
|
45
|
+
router.push(String(ROUTES.USER.CART));
|
|
46
|
+
}, [cart, productId, title, thumb, pricePerEntry, router, showToast]);
|
|
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, { variant: "primary", size: "md", className: "w-full", disabled: closed, onClick: handleEnter, children: closed ? "Draw closed" : "Enter draw" }), 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 })] }));
|
|
48
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { PrizeDrawItem } from "../schemas/firestore";
|
|
2
|
+
export interface PrizeDrawItemsEditorProps {
|
|
3
|
+
items: PrizeDrawItem[];
|
|
4
|
+
onChange: (items: PrizeDrawItem[]) => void;
|
|
5
|
+
/** Upload an image File → returns the resolved storage URL (or media slug). */
|
|
6
|
+
onUploadImage: (file: File, itemNumber: number) => Promise<string>;
|
|
7
|
+
/** Optional video uploader. */
|
|
8
|
+
onUploadVideo?: (file: File, itemNumber: number) => Promise<string>;
|
|
9
|
+
/** Show a non-editable warning above the editor (e.g. "Draw already opened"). */
|
|
10
|
+
warning?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function PrizeDrawItemsEditor({ items, onChange, onUploadImage, onUploadVideo, warning, }: PrizeDrawItemsEditorProps): import("react/jsx-runtime").JSX.Element;
|
|
13
|
+
export default PrizeDrawItemsEditor;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* PrizeDrawItemsEditor (SB4-A)
|
|
5
|
+
*
|
|
6
|
+
* Editor for the `prizeDrawItems` array on a prize-draw ProductDocument.
|
|
7
|
+
* Owns: add / remove / reorder, per-item title/description/condition/
|
|
8
|
+
* estimatedValue, 1–2 images per item, optional video, and a locked-overlay
|
|
9
|
+
* for items already won during a previous reveal.
|
|
10
|
+
*
|
|
11
|
+
* Min 3 items, max 16 (mirrors PrizeDrawItem schema constraints).
|
|
12
|
+
*/
|
|
13
|
+
import { useCallback } from "react";
|
|
14
|
+
import { Button, Div, FormField, Heading, Row, Stack, Text } from "../../../ui";
|
|
15
|
+
import { ImageUpload } from "../../media";
|
|
16
|
+
const MIN_ITEMS = 3;
|
|
17
|
+
const MAX_ITEMS = 16;
|
|
18
|
+
const MAX_IMAGES_PER_ITEM = 2;
|
|
19
|
+
const CONDITION_OPTIONS = [
|
|
20
|
+
{ value: "new", label: "New" },
|
|
21
|
+
{ value: "like_new", label: "Like New" },
|
|
22
|
+
{ value: "good", label: "Good" },
|
|
23
|
+
{ value: "used", label: "Used" },
|
|
24
|
+
{ value: "graded", label: "Graded" },
|
|
25
|
+
{ value: "refurbished", label: "Refurbished" },
|
|
26
|
+
];
|
|
27
|
+
export function PrizeDrawItemsEditor({ items, onChange, onUploadImage, onUploadVideo, warning, }) {
|
|
28
|
+
// Once any prize has been revealed (isWon=true), the array is frozen for
|
|
29
|
+
// the seller — add/remove/reorder are all blocked. Per-item edits already
|
|
30
|
+
// respect the per-item `locked` flag below.
|
|
31
|
+
const anyWon = items.some((it) => it.isWon);
|
|
32
|
+
const update = useCallback((index, patch) => {
|
|
33
|
+
const next = items.map((it, i) => (i === index ? { ...it, ...patch } : it));
|
|
34
|
+
onChange(next);
|
|
35
|
+
}, [items, onChange]);
|
|
36
|
+
const renumber = (list) => list.map((it, i) => ({ ...it, itemNumber: i + 1 }));
|
|
37
|
+
const addItem = () => {
|
|
38
|
+
if (items.length >= MAX_ITEMS)
|
|
39
|
+
return;
|
|
40
|
+
onChange(renumber([
|
|
41
|
+
...items,
|
|
42
|
+
{
|
|
43
|
+
itemNumber: items.length + 1,
|
|
44
|
+
title: "",
|
|
45
|
+
description: "",
|
|
46
|
+
images: [],
|
|
47
|
+
condition: "new",
|
|
48
|
+
isWon: false,
|
|
49
|
+
},
|
|
50
|
+
]));
|
|
51
|
+
};
|
|
52
|
+
const removeItem = (index) => {
|
|
53
|
+
const target = items[index];
|
|
54
|
+
if (target?.isWon)
|
|
55
|
+
return; // cannot remove a won item
|
|
56
|
+
if (items.length <= MIN_ITEMS)
|
|
57
|
+
return;
|
|
58
|
+
onChange(renumber(items.filter((_, i) => i !== index)));
|
|
59
|
+
};
|
|
60
|
+
const move = (index, direction) => {
|
|
61
|
+
const target = index + direction;
|
|
62
|
+
if (target < 0 || target >= items.length)
|
|
63
|
+
return;
|
|
64
|
+
const next = [...items];
|
|
65
|
+
[next[index], next[target]] = [next[target], next[index]];
|
|
66
|
+
onChange(renumber(next));
|
|
67
|
+
};
|
|
68
|
+
const setImage = (index, slot, url) => {
|
|
69
|
+
const cur = items[index];
|
|
70
|
+
const nextImgs = [...(cur.images ?? [])];
|
|
71
|
+
nextImgs[slot] = url;
|
|
72
|
+
update(index, { images: nextImgs.filter(Boolean) });
|
|
73
|
+
};
|
|
74
|
+
const removeImage = (index, slot) => {
|
|
75
|
+
const cur = items[index];
|
|
76
|
+
const nextImgs = (cur.images ?? []).filter((_, i) => i !== slot);
|
|
77
|
+
update(index, { images: nextImgs });
|
|
78
|
+
};
|
|
79
|
+
return (_jsxs(Stack, { gap: "md", children: [_jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Heading, { level: 3, children: ["Prize Items (", items.length, ")"] }), _jsxs(Button, { type: "button", variant: "secondary", onClick: addItem, disabled: anyWon || items.length >= MAX_ITEMS, children: ["+ Add prize (", items.length, "/", MAX_ITEMS, ")"] })] }), _jsxs(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: ["Add between ", MIN_ITEMS, " and ", MAX_ITEMS, " prizes. Each entry will reveal exactly one of these. Items marked won during a prior reveal cannot be edited or removed."] }), anyWon ? (_jsxs(Div, { className: "rounded border border-red-400/40 bg-red-50 px-3 py-2 text-sm text-red-900 dark:bg-red-900/30 dark:text-red-100", children: [_jsx("strong", { children: "Draw locked." }), " At least one prize has been revealed \u2014 this listing can no longer be edited. To run a similar draw, clone it into a new prize-draw listing."] })) : warning ? (_jsx(Div, { className: "rounded border border-yellow-400/40 bg-yellow-50 px-3 py-2 text-sm text-yellow-900 dark:bg-yellow-900/30 dark:text-yellow-100", children: warning })) : null, _jsx(Stack, { gap: "md", children: items.map((it, index) => {
|
|
80
|
+
const locked = it.isWon || anyWon;
|
|
81
|
+
return (_jsxs(Div, { className: `relative rounded-lg border border-[var(--appkit-color-border)] p-4 ${locked ? "opacity-60" : ""}`, children: [locked ? (_jsx(Div, { className: "absolute inset-0 z-10 flex items-center justify-center bg-black/10 dark:bg-black/40 rounded-lg pointer-events-none", children: _jsx(Text, { className: "rounded bg-red-600 px-3 py-1 text-xs font-semibold uppercase tracking-wide text-white", children: "Won \u2014 locked" }) })) : null, _jsxs(Row, { justify: "between", align: "center", className: "mb-3", children: [_jsxs(Heading, { level: 4, children: ["Prize #", it.itemNumber] }), _jsxs(Row, { gap: "xs", children: [_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => move(index, -1), disabled: locked || index === 0, "aria-label": "Move up", children: "\u2191" }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => move(index, 1), disabled: locked || index === items.length - 1, "aria-label": "Move down", children: "\u2193" }), _jsx(Button, { type: "button", variant: "danger", size: "sm", onClick: () => removeItem(index), disabled: locked || items.length <= MIN_ITEMS, children: "Remove" })] })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(FormField, { name: `item-${index}-title`, label: "Title", type: "text", value: it.title, onChange: (v) => update(index, { title: v }), disabled: locked, placeholder: "e.g. PSA 9 Charizard Base Set Holo" }), _jsx(FormField, { name: `item-${index}-description`, label: "Description (optional)", type: "textarea", value: it.description ?? "", onChange: (v) => update(index, { description: v }), disabled: locked, placeholder: "What makes this prize special?" }), _jsxs(Row, { gap: "md", children: [_jsx(Div, { className: "flex-1", children: _jsx(FormField, { name: `item-${index}-condition`, label: "Condition", type: "select", value: it.condition, onChange: (v) => update(index, { condition: v }), disabled: locked, options: CONDITION_OPTIONS }) }), _jsx(Div, { className: "flex-1", children: _jsx(FormField, { name: `item-${index}-value`, label: "Estimated value (\u20B9)", type: "number", value: it.estimatedValue != null
|
|
82
|
+
? String(Math.round(it.estimatedValue / 100))
|
|
83
|
+
: "", onChange: (v) => update(index, {
|
|
84
|
+
estimatedValue: Math.round((parseFloat(v) || 0) * 100),
|
|
85
|
+
}), disabled: locked, placeholder: "2999" }) })] }), _jsxs(Stack, { gap: "xs", children: [_jsxs(Text, { className: "text-sm font-medium", children: ["Images (", (it.images ?? []).length, "/", MAX_IMAGES_PER_ITEM, ")"] }), _jsx(Row, { gap: "sm", className: "flex-wrap", children: Array.from({ length: MAX_IMAGES_PER_ITEM }).map((_, slot) => {
|
|
86
|
+
const existing = (it.images ?? [])[slot];
|
|
87
|
+
return (_jsxs(Div, { className: "w-40", children: [_jsx(ImageUpload, { currentImage: existing, label: existing ? "Replace" : `Image ${slot + 1}`, onUpload: (file) => onUploadImage(file, it.itemNumber), onChange: (url) => setImage(index, slot, url) }), existing && !locked ? (_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => removeImage(index, slot), children: "Remove" })) : null] }, slot));
|
|
88
|
+
}) })] }), onUploadVideo ? (_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { className: "text-sm font-medium", children: "Video (optional)" }), _jsx("input", { type: "file", accept: "video/mp4,video/webm", disabled: locked, onChange: async (e) => {
|
|
89
|
+
const file = e.target.files?.[0];
|
|
90
|
+
if (!file)
|
|
91
|
+
return;
|
|
92
|
+
const url = await onUploadVideo(file, it.itemNumber);
|
|
93
|
+
update(index, { video: { url } });
|
|
94
|
+
} }), it.video?.url ? (_jsxs(Text, { className: "text-xs text-[var(--appkit-color-text-muted)] truncate", children: ["Current: ", it.video.url] })) : null] })) : null] })] }, `prize-item-${it.itemNumber}-${index}`));
|
|
95
|
+
}) }), items.length < MIN_ITEMS ? (_jsxs(Div, { className: "rounded border border-red-400/40 bg-red-50 px-3 py-2 text-sm text-red-900 dark:bg-red-900/30 dark:text-red-100", children: ["At least ", MIN_ITEMS, " prizes are required."] })) : null] }));
|
|
96
|
+
}
|
|
97
|
+
export default PrizeDrawItemsEditor;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface PrizeDrawsIndexListingProps {
|
|
2
|
+
initialData?: any;
|
|
3
|
+
categorySlug?: string;
|
|
4
|
+
brandName?: string;
|
|
5
|
+
/** When set, the listing is hard-scoped to this store id — overrides URL `storeId`. */
|
|
6
|
+
storeId?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function PrizeDrawsIndexListing({ initialData, categorySlug, brandName, storeId: forcedStoreId, }: PrizeDrawsIndexListingProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useCallback, useMemo } from "react";
|
|
4
|
+
import { SlidersHorizontal, X } from "lucide-react";
|
|
5
|
+
import { useUrlTable } from "../../../react/hooks/useUrlTable";
|
|
6
|
+
import { useProducts } from "../hooks/useProducts";
|
|
7
|
+
import { Pagination, ListingToolbar } from "../../../ui";
|
|
8
|
+
import { useCategoryTree, categoriesToFacetOptions } from "../../categories/hooks/useCategoryTree";
|
|
9
|
+
import { useBrands } from "../hooks/useBrands";
|
|
10
|
+
import { MarketplacePrizeDrawCard } from "./MarketplacePrizeDrawCard";
|
|
11
|
+
import { ProductFilters } from "./ProductFilters";
|
|
12
|
+
const PRIZE_DRAW_SORT_OPTIONS = [
|
|
13
|
+
{ value: "-createdAt", label: "Newest First" },
|
|
14
|
+
{ value: "createdAt", label: "Oldest First" },
|
|
15
|
+
{ value: "prizeRevealWindowStart", label: "Reveal: Soonest" },
|
|
16
|
+
{ value: "-prizeRevealWindowStart", label: "Reveal: Furthest" },
|
|
17
|
+
{ value: "pricePerEntry", label: "Entry: Low to High" },
|
|
18
|
+
{ value: "-pricePerEntry", label: "Entry: High to Low" },
|
|
19
|
+
];
|
|
20
|
+
const FILTER_KEYS = [
|
|
21
|
+
"category",
|
|
22
|
+
"brand",
|
|
23
|
+
"minPrice",
|
|
24
|
+
"maxPrice",
|
|
25
|
+
"storeId",
|
|
26
|
+
"prizeRevealStatus",
|
|
27
|
+
];
|
|
28
|
+
export function PrizeDrawsIndexListing({ initialData, categorySlug, brandName, storeId: forcedStoreId, }) {
|
|
29
|
+
const table = useUrlTable({ defaults: { pageSize: "24", sort: "-createdAt" } });
|
|
30
|
+
const [searchInput, setSearchInput] = useState(table.get("q") || "");
|
|
31
|
+
const [filterOpen, setFilterOpen] = useState(false);
|
|
32
|
+
const showClosed = table.get("showClosed") === "true";
|
|
33
|
+
const [view, setView] = useState(table.get("view") || "grid");
|
|
34
|
+
const { categories } = useCategoryTree();
|
|
35
|
+
const categoryOptions = categoriesToFacetOptions(categories);
|
|
36
|
+
const { brandOptions } = useBrands();
|
|
37
|
+
const [pendingFilters, setPendingFilters] = useState(() => Object.fromEntries(FILTER_KEYS.map((k) => [k, table.get(k)])));
|
|
38
|
+
const pendingTable = useMemo(() => ({
|
|
39
|
+
get: (key) => pendingFilters[key] ?? "",
|
|
40
|
+
getNumber: (key, fallback = 0) => {
|
|
41
|
+
const v = pendingFilters[key];
|
|
42
|
+
if (!v)
|
|
43
|
+
return fallback;
|
|
44
|
+
const n = Number(v);
|
|
45
|
+
return isNaN(n) ? fallback : n;
|
|
46
|
+
},
|
|
47
|
+
set: (key, value) => setPendingFilters((p) => ({ ...p, [key]: value })),
|
|
48
|
+
setMany: (updates) => setPendingFilters((p) => ({ ...p, ...updates })),
|
|
49
|
+
clear: (keys) => {
|
|
50
|
+
const ks = keys ?? FILTER_KEYS;
|
|
51
|
+
setPendingFilters((p) => ({
|
|
52
|
+
...p,
|
|
53
|
+
...Object.fromEntries(ks.map((k) => [k, ""])),
|
|
54
|
+
}));
|
|
55
|
+
},
|
|
56
|
+
setPage: (_) => { },
|
|
57
|
+
setPageSize: (_) => { },
|
|
58
|
+
setSort: (_) => { },
|
|
59
|
+
buildSieveParams: () => "",
|
|
60
|
+
buildSearchParams: () => "",
|
|
61
|
+
params: new URLSearchParams(),
|
|
62
|
+
}), [pendingFilters]);
|
|
63
|
+
const openFilters = useCallback(() => {
|
|
64
|
+
setPendingFilters(Object.fromEntries(FILTER_KEYS.map((k) => [k, table.get(k)])));
|
|
65
|
+
setFilterOpen(true);
|
|
66
|
+
}, [table]);
|
|
67
|
+
const applyFilters = useCallback(() => {
|
|
68
|
+
const updates = { page: "1" };
|
|
69
|
+
for (const k of FILTER_KEYS)
|
|
70
|
+
updates[k] = pendingFilters[k] ?? "";
|
|
71
|
+
table.setMany(updates);
|
|
72
|
+
setFilterOpen(false);
|
|
73
|
+
}, [pendingFilters, table]);
|
|
74
|
+
const clearFilters = useCallback(() => {
|
|
75
|
+
setPendingFilters(Object.fromEntries(FILTER_KEYS.map((k) => [k, ""])));
|
|
76
|
+
}, []);
|
|
77
|
+
const resetAll = useCallback(() => {
|
|
78
|
+
const updates = { q: "", sort: "", showClosed: "" };
|
|
79
|
+
for (const k of FILTER_KEYS)
|
|
80
|
+
updates[k] = "";
|
|
81
|
+
table.setMany(updates);
|
|
82
|
+
setSearchInput("");
|
|
83
|
+
}, [table]);
|
|
84
|
+
const activeFilterCount = FILTER_KEYS.filter((k) => !!table.get(k)).length;
|
|
85
|
+
const hasActiveState = !!table.get("q") ||
|
|
86
|
+
showClosed ||
|
|
87
|
+
table.get("sort") !== "-createdAt" ||
|
|
88
|
+
activeFilterCount > 0;
|
|
89
|
+
const params = {
|
|
90
|
+
q: table.get("q") || undefined,
|
|
91
|
+
category: table.get("category") || undefined,
|
|
92
|
+
categorySlug: categorySlug || undefined,
|
|
93
|
+
brand: brandName || table.get("brand") || undefined,
|
|
94
|
+
minPrice: table.get("minPrice") ? Number(table.get("minPrice")) : undefined,
|
|
95
|
+
maxPrice: table.get("maxPrice") ? Number(table.get("maxPrice")) : undefined,
|
|
96
|
+
storeId: forcedStoreId || table.get("storeId") || undefined,
|
|
97
|
+
sort: table.get("sort") || "-createdAt",
|
|
98
|
+
page: table.getNumber("page", 1),
|
|
99
|
+
perPage: table.getNumber("pageSize", 24),
|
|
100
|
+
listingType: "prize-draw",
|
|
101
|
+
status: showClosed ? undefined : "published",
|
|
102
|
+
};
|
|
103
|
+
const { products: draws, totalPages, page, isLoading } = useProducts(params, { initialData });
|
|
104
|
+
// Client-side filter for prizeRevealStatus until the server query supports it.
|
|
105
|
+
const revealFilter = table.get("prizeRevealStatus");
|
|
106
|
+
const filteredDraws = revealFilter
|
|
107
|
+
? draws.filter((d) => d.prizeRevealStatus === revealFilter)
|
|
108
|
+
: draws;
|
|
109
|
+
const commitSearch = useCallback(() => {
|
|
110
|
+
table.set("q", searchInput.trim());
|
|
111
|
+
table.setPage(1);
|
|
112
|
+
}, [searchInput, table]);
|
|
113
|
+
const handleSearchKeyDown = (e) => {
|
|
114
|
+
if (e.key === "Enter")
|
|
115
|
+
commitSearch();
|
|
116
|
+
};
|
|
117
|
+
const gridClass = "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-4";
|
|
118
|
+
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search prize draws...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, onSearchKeyDown: handleSearchKeyDown, sortValue: table.get("sort") || "-createdAt", sortOptions: PRIZE_DRAW_SORT_OPTIONS, onSortChange: (v) => {
|
|
119
|
+
table.set("sort", v);
|
|
120
|
+
table.setPage(1);
|
|
121
|
+
}, view: view, onViewChange: (v) => {
|
|
122
|
+
setView(v);
|
|
123
|
+
table.set("view", v);
|
|
124
|
+
}, onResetAll: resetAll, hasActiveState: hasActiveState, extra: _jsxs("label", { className: "flex items-center gap-1.5 cursor-pointer select-none shrink-0", children: [_jsx("span", { className: "hidden sm:inline text-xs text-zinc-600 dark:text-zinc-300 whitespace-nowrap", children: "Show closed" }), _jsx("button", { type: "button", role: "switch", "aria-checked": showClosed, onClick: () => table.set("showClosed", showClosed ? "" : "true"), className: `relative inline-flex h-5 w-9 flex-shrink-0 items-center rounded-full transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-1 ${showClosed ? "bg-primary" : "bg-zinc-300 dark:bg-slate-600"}`, children: _jsx("span", { className: `inline-block h-3.5 w-3.5 transform rounded-full bg-white shadow-sm transition-transform duration-200 ${showClosed ? "translate-x-[19px]" : "translate-x-[3px]"}` }) })] }) }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: page, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsx("div", { className: "py-6", children: isLoading ? (_jsx("div", { className: gridClass, children: Array.from({ length: 8 }).map((_, i) => (_jsxs("div", { className: "rounded-xl border border-zinc-100 dark:border-slate-700 overflow-hidden animate-pulse", children: [_jsx("div", { className: "aspect-square bg-zinc-200 dark:bg-slate-700" }), _jsxs("div", { className: "p-3 space-y-2", children: [_jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-3/4" }), _jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-1/2" }), _jsx("div", { className: "h-8 bg-zinc-200 dark:bg-slate-700 rounded" })] })] }, i))) })) : filteredDraws.length === 0 ? (_jsx("p", { className: "py-12 text-center text-sm text-zinc-500 dark:text-zinc-400", children: "No prize draws found." })) : (_jsx("div", { className: gridClass, children: filteredDraws.map((product) => (_jsx(MarketplacePrizeDrawCard, { product: product, variant: view }, product.id))) })) }), filterOpen && (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-40 bg-black/40", "aria-hidden": "true", onClick: () => setFilterOpen(false) }), _jsxs("div", { className: "fixed inset-y-0 left-0 z-50 flex w-80 flex-col bg-white dark:bg-slate-900 shadow-2xl", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: [_jsxs("span", { className: "flex items-center gap-2 text-base font-semibold text-zinc-900 dark:text-zinc-100", children: [_jsx(SlidersHorizontal, { className: "h-4 w-4" }), "Filters"] }), _jsxs("div", { className: "flex items-center gap-2", children: [activeFilterCount > 0 && (_jsx("button", { type: "button", onClick: clearFilters, className: "text-xs text-zinc-500 hover:text-rose-500 dark:text-zinc-400 transition-colors", children: "Clear all" })), _jsx("button", { type: "button", onClick: () => setFilterOpen(false), "aria-label": "Close filters", className: "rounded-lg p-1.5 text-zinc-500 hover:bg-zinc-100 dark:hover:bg-slate-800 transition-colors", children: _jsx(X, { className: "h-5 w-5" }) })] })] }), _jsxs("div", { className: "flex-1 overflow-y-auto px-4 py-4 space-y-4", children: [_jsxs("div", { children: [_jsx("label", { htmlFor: "prizeRevealStatusFilter", className: "block text-xs font-semibold text-zinc-700 dark:text-zinc-300 mb-1.5", children: "Reveal status" }), _jsxs("select", { id: "prizeRevealStatusFilter", value: pendingFilters.prizeRevealStatus ?? "", onChange: (e) => setPendingFilters((p) => ({
|
|
125
|
+
...p,
|
|
126
|
+
prizeRevealStatus: e.target.value,
|
|
127
|
+
})), className: "w-full rounded border border-zinc-300 dark:border-slate-600 bg-white dark:bg-slate-800 px-2 py-1.5 text-sm", children: [_jsx("option", { value: "", children: "Any" }), _jsx("option", { value: "pending", children: "Reveal pending" }), _jsx("option", { value: "open", children: "Reveal open" }), _jsx("option", { value: "closed", children: "Closed" })] })] }), _jsx(ProductFilters, { table: pendingTable, currencyPrefix: "\u20B9", categoryOptions: categoryOptions, brandOptions: brandOptions })] }), _jsx("div", { className: "border-t border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: _jsxs("button", { type: "button", onClick: applyFilters, className: "w-full rounded-lg bg-primary py-2.5 text-sm font-semibold text-white hover:bg-primary-600 transition-colors active:scale-[0.98]", children: ["Apply Filters", activeFilterCount > 0 ? ` (${activeFilterCount})` : ""] }) })] })] }))] }));
|
|
128
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
type SearchParams = Record<string, string | string[]>;
|
|
2
|
+
export interface PrizeDrawsListingViewProps {
|
|
3
|
+
searchParams?: SearchParams;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Public listing page (SB4-F). Server-fetches published prize-draw products,
|
|
7
|
+
* hydrates the client `PrizeDrawsIndexListing` which renders the filter
|
|
8
|
+
* toolbar + collage-thumb grid. URL params: `?storeId=…&prizeRevealStatus=…`.
|
|
9
|
+
*
|
|
10
|
+
* Per the public-buyer contract, the product adapter strips `isWon` from
|
|
11
|
+
* `prizeDrawItems[]` server-side; the cards never reveal which prizes are
|
|
12
|
+
* already gone (matches `PrizeDrawCollage`'s `hideWonState` prop).
|
|
13
|
+
*/
|
|
14
|
+
export declare function PrizeDrawsListingView({ searchParams, }: PrizeDrawsListingViewProps): Promise<import("react/jsx-runtime").JSX.Element>;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { productRepository } from "../../../repositories";
|
|
3
|
+
import { Container, Main, Heading, Section, Text } from "../../../ui";
|
|
4
|
+
import { AdSlot } from "../../homepage/components/AdSlot";
|
|
5
|
+
import { parseListingSearchParams } from "../../../utils/listing-params";
|
|
6
|
+
import { PrizeDrawsIndexListing } from "./PrizeDrawsIndexListing";
|
|
7
|
+
const DEFAULT_PAGE = 1;
|
|
8
|
+
const DEFAULT_PAGE_SIZE = 24;
|
|
9
|
+
const DEFAULT_SORT = "-createdAt";
|
|
10
|
+
function sp(params, key) {
|
|
11
|
+
const v = params[key];
|
|
12
|
+
return Array.isArray(v) ? v[0] ?? "" : v ?? "";
|
|
13
|
+
}
|
|
14
|
+
function buildPrizeDrawFilters(params) {
|
|
15
|
+
const parts = ["status==published", "listingType==prize-draw"];
|
|
16
|
+
const minPrice = sp(params, "minPrice");
|
|
17
|
+
const maxPrice = sp(params, "maxPrice");
|
|
18
|
+
if (minPrice)
|
|
19
|
+
parts.push(`pricePerEntry>=${minPrice}`);
|
|
20
|
+
if (maxPrice)
|
|
21
|
+
parts.push(`pricePerEntry<=${maxPrice}`);
|
|
22
|
+
const store = sp(params, "storeId");
|
|
23
|
+
if (store)
|
|
24
|
+
parts.push(`storeId==${store}`);
|
|
25
|
+
const status = sp(params, "prizeRevealStatus");
|
|
26
|
+
if (status)
|
|
27
|
+
parts.push(`prizeRevealStatus==${status}`);
|
|
28
|
+
return parts.join(",");
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Public listing page (SB4-F). Server-fetches published prize-draw products,
|
|
32
|
+
* hydrates the client `PrizeDrawsIndexListing` which renders the filter
|
|
33
|
+
* toolbar + collage-thumb grid. URL params: `?storeId=…&prizeRevealStatus=…`.
|
|
34
|
+
*
|
|
35
|
+
* Per the public-buyer contract, the product adapter strips `isWon` from
|
|
36
|
+
* `prizeDrawItems[]` server-side; the cards never reveal which prizes are
|
|
37
|
+
* already gone (matches `PrizeDrawCollage`'s `hideWonState` prop).
|
|
38
|
+
*/
|
|
39
|
+
export async function PrizeDrawsListingView({ searchParams = {}, }) {
|
|
40
|
+
const std = parseListingSearchParams(searchParams);
|
|
41
|
+
const sort = std.sorts ?? DEFAULT_SORT;
|
|
42
|
+
const page = std.page ?? DEFAULT_PAGE;
|
|
43
|
+
const pageSize = std.pageSize ?? DEFAULT_PAGE_SIZE;
|
|
44
|
+
const filters = buildPrizeDrawFilters(searchParams);
|
|
45
|
+
const result = await productRepository
|
|
46
|
+
.list({ filters, sorts: sort, page, pageSize })
|
|
47
|
+
.catch(() => null);
|
|
48
|
+
return (_jsx(Main, { children: _jsx(Section, { className: "py-10", children: _jsxs(Container, { size: "xl", children: [_jsx(Heading, { level: 1, className: "mb-2 text-3xl font-semibold text-zinc-900 dark:text-zinc-100", children: "Prize Draws" }), _jsx(Text, { className: "mb-6 text-sm text-[var(--appkit-color-text-muted)]", children: "Fair-RNG draws for sealed Pok\u00E9mon, Hot Wheels Super Treasure Hunts, Gundam kits and more. Every winner picked by crypto.randomInt \u2014 proof on GitHub." }), _jsx(AdSlot, { id: "listing-sidebar-top", className: "mb-6" }), _jsx(PrizeDrawsIndexListing, { initialData: result ?? undefined }), _jsx(AdSlot, { id: "listing-sidebar-bottom", className: "mt-8" })] }) }) }));
|
|
49
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { PrizeDrawItem } from "../schemas/firestore";
|
|
2
|
+
export interface PrizeRevealResponse {
|
|
3
|
+
prizeWon?: {
|
|
4
|
+
itemNumber: number;
|
|
5
|
+
title: string;
|
|
6
|
+
images: string[];
|
|
7
|
+
estimatedValue?: number;
|
|
8
|
+
};
|
|
9
|
+
alreadyRevealed?: boolean;
|
|
10
|
+
rngSourceUrl?: string;
|
|
11
|
+
refunded?: true;
|
|
12
|
+
reason?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface PrizeRevealModalProps {
|
|
15
|
+
open: boolean;
|
|
16
|
+
onClose: () => void;
|
|
17
|
+
/** Items to render in the collage (won state hidden — buyers don't see prior wins). */
|
|
18
|
+
items: PrizeDrawItem[];
|
|
19
|
+
/** Order id used by the reveal endpoint. */
|
|
20
|
+
orderId: string;
|
|
21
|
+
/** Product id used in the reveal URL. */
|
|
22
|
+
productId: string;
|
|
23
|
+
/** Override the default `/api/prize-draws/[id]/reveal` POST call. */
|
|
24
|
+
onReveal?: (args: {
|
|
25
|
+
orderId: string;
|
|
26
|
+
productId: string;
|
|
27
|
+
}) => Promise<PrizeRevealResponse>;
|
|
28
|
+
/** Already-revealed prize, passed in if the order has `prizeWon` populated. */
|
|
29
|
+
initialPrizeWon?: PrizeRevealResponse["prizeWon"];
|
|
30
|
+
/** Public proof-of-fairness URL — shown in the disclaimer. */
|
|
31
|
+
rngSourceUrl?: string;
|
|
32
|
+
}
|
|
33
|
+
export declare function PrizeRevealModal({ open, onClose, items, orderId, productId, onReveal, initialPrizeWon, rngSourceUrl, }: PrizeRevealModalProps): import("react/jsx-runtime").JSX.Element;
|
|
34
|
+
export default PrizeRevealModal;
|