@mohasinac/appkit 4.2.0 → 4.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_internal/server/features/auth/actions.d.ts +0 -1
- package/dist/_internal/server/features/auth/actions.js +0 -1
- package/dist/_internal/server/features/bundles/data.d.ts +22 -4
- package/dist/_internal/server/features/bundles/data.js +41 -4
- package/dist/_internal/server/features/bundles/index.d.ts +1 -1
- package/dist/_internal/server/features/bundles/index.js +1 -1
- package/dist/_internal/server/features/checkout/actions.js +7 -78
- package/dist/_internal/server/features/checkout/prize-bundle-gates.d.ts +5 -16
- package/dist/_internal/server/features/checkout/prize-bundle-gates.js +5 -30
- package/dist/_internal/server/features/orders/adapters.js +1 -14
- package/dist/_internal/server/features/products/actions.js +8 -3
- package/dist/_internal/server/features/products/service.d.ts +15 -0
- package/dist/_internal/server/features/products/service.js +41 -0
- package/dist/_internal/server/functions/firestore.d.ts +3 -1
- package/dist/_internal/server/functions/firestore.js +17 -1
- package/dist/_internal/server/functions/scheduled.d.ts +2 -5
- package/dist/_internal/server/functions/scheduled.js +6 -30
- package/dist/_internal/server/jobs/core/bundleStockSync.js +25 -7
- package/dist/_internal/server/jobs/core/countersReconcile.js +7 -1
- package/dist/_internal/server/jobs/core/index.d.ts +4 -4
- package/dist/_internal/server/jobs/core/index.js +4 -4
- package/dist/_internal/server/jobs/core/onPrizeDrawPaymentConfirmed.d.ts +19 -0
- package/dist/_internal/server/jobs/core/onPrizeDrawPaymentConfirmed.js +30 -0
- package/dist/_internal/server/jobs/core/paymentWindowTimeout.d.ts +2 -2
- package/dist/_internal/server/jobs/core/paymentWindowTimeout.js +2 -2
- package/dist/_internal/server/jobs/core/prizeDrawAssignWinner.d.ts +20 -0
- package/dist/_internal/server/jobs/core/prizeDrawAssignWinner.js +113 -0
- package/dist/_internal/server/jobs/core/prizeDrawExpiryReveal.d.ts +14 -0
- package/dist/_internal/server/jobs/core/prizeDrawExpiryReveal.js +74 -0
- package/dist/_internal/server/jobs/core/prizeDrawSoldOutReveal.d.ts +22 -0
- package/dist/_internal/server/jobs/core/prizeDrawSoldOutReveal.js +68 -0
- package/dist/_internal/server/jobs/handlers/index.d.ts +3 -4
- package/dist/_internal/server/jobs/handlers/index.js +8 -6
- package/dist/_internal/server/jobs/handlers/onPrizeDrawPaymentConfirmed.d.ts +3 -0
- package/dist/_internal/server/jobs/handlers/onPrizeDrawPaymentConfirmed.js +11 -0
- package/dist/_internal/server/jobs/handlers/prizeDrawExpiryReveal.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/prizeDrawExpiryReveal.js +2 -0
- package/dist/_internal/server/jobs/handlers/prizeDrawSoldOutReveal.d.ts +3 -0
- package/dist/_internal/server/jobs/handlers/prizeDrawSoldOutReveal.js +4 -0
- package/dist/_internal/shared/actions/action-registry.js +40 -10
- package/dist/_internal/shared/checkout/rules/_defaults.js +1 -2
- package/dist/_internal/shared/checkout/rules/_limits.d.ts +3 -0
- package/dist/_internal/shared/checkout/rules/_limits.js +3 -0
- package/dist/_internal/shared/checkout/rules/_registry.d.ts +2 -1
- package/dist/_internal/shared/checkout/rules/_registry.js +2 -2
- package/dist/_internal/shared/checkout/rules/classified.rule.js +0 -1
- package/dist/_internal/shared/checkout/rules/index.d.ts +1 -1
- package/dist/_internal/shared/checkout/rules/index.js +1 -1
- package/dist/_internal/shared/checkout/rules/prize-draw.rule.js +20 -1
- package/dist/_internal/shared/checkout/rules/types.d.ts +17 -7
- package/dist/_internal/shared/features/categories/bundle-copy.d.ts +2 -0
- package/dist/_internal/shared/features/categories/bundle-copy.js +2 -0
- package/dist/_internal/shared/features/categories/bundle-pricing.d.ts +23 -0
- package/dist/_internal/shared/features/categories/bundle-pricing.js +25 -0
- package/dist/_internal/shared/listing-types/prize-draw/config.js +1 -1
- package/dist/constants/api-endpoints.d.ts +6 -9
- package/dist/constants/api-endpoints.js +5 -5
- package/dist/constants/field-names.d.ts +17 -1
- package/dist/constants/field-names.js +17 -1
- package/dist/features/account/hooks/useNotifications.js +5 -0
- package/dist/features/admin/actions/admin-actions.js +6 -0
- package/dist/features/admin/actions/notification-actions.js +1 -2
- package/dist/features/admin/components/AdminBlogEditorView.js +1 -1
- package/dist/features/admin/components/AdminPrizeDrawsView.js +3 -3
- package/dist/features/admin/components/AdminSiteSettingsView.js +33 -6
- package/dist/features/admin/components/AdminTesterChecklistView.js +74 -22
- package/dist/features/admin/hooks/useAdminListing.d.ts +4 -0
- package/dist/features/admin/hooks/useAdminListing.js +4 -2
- package/dist/features/admin/schemas/firestore.d.ts +13 -5
- package/dist/features/admin/schemas/firestore.js +1 -2
- package/dist/features/auctions/actions/bid-actions.js +3 -1
- package/dist/features/auctions/components/AuctionBidsTable.d.ts +6 -1
- package/dist/features/auctions/components/AuctionBidsTable.js +8 -4
- package/dist/features/auctions/components/AuctionDetailPageView.js +10 -13
- package/dist/features/auctions/components/CollapsibleBidHistory.d.ts +23 -2
- package/dist/features/auctions/components/CollapsibleBidHistory.js +21 -3
- package/dist/features/auctions/components/LiveBidPrice.d.ts +19 -0
- package/dist/features/auctions/components/LiveBidPrice.js +17 -0
- package/dist/features/auctions/components/PlaceBidFormClient.d.ts +1 -1
- package/dist/features/auctions/components/PlaceBidFormClient.js +16 -1
- package/dist/features/auctions/hooks/useBids.d.ts +33 -0
- package/dist/features/auctions/hooks/useBids.js +32 -0
- package/dist/features/auctions/hooks/useLiveAuctionBid.d.ts +20 -0
- package/dist/features/auctions/hooks/useLiveAuctionBid.js +23 -0
- package/dist/features/auth/checkout-value-otp.d.ts +7 -10
- package/dist/features/auth/checkout-value-otp.js +14 -10
- package/dist/features/auth/server.d.ts +0 -1
- package/dist/features/auth/server.js +0 -1
- package/dist/features/blog/api/[slug]/route.d.ts +5 -0
- package/dist/features/blog/api/[slug]/route.js +12 -3
- package/dist/features/blog/components/BlogPostView.d.ts +2 -0
- package/dist/features/blog/components/BlogPostView.js +5 -4
- package/dist/features/blog/hooks/useBlog.d.ts +2 -0
- package/dist/features/blog/hooks/useBlog.js +2 -0
- package/dist/features/blog/repository/blog.repository.d.ts +7 -0
- package/dist/features/blog/repository/blog.repository.js +44 -0
- package/dist/features/blog/schemas/firestore.d.ts +3 -0
- package/dist/features/blog/schemas/firestore.js +1 -0
- package/dist/features/blog/types/index.d.ts +2 -0
- package/dist/features/cart/components/index.d.ts +0 -2
- package/dist/features/cart/components/index.js +0 -1
- package/dist/features/cart/hooks/useCartCount.js +22 -5
- package/dist/features/cart/hooks/useGuestCart.js +11 -1
- package/dist/features/cart/utils/guest-cart.d.ts +4 -0
- package/dist/features/cart/utils/guest-cart.js +10 -0
- package/dist/features/cart/utils/pending-ops.d.ts +17 -0
- package/dist/features/cart/utils/pending-ops.js +46 -5
- package/dist/features/categories/components/BrandDetailPageView.js +6 -2
- package/dist/features/categories/components/BundleDetailView.js +4 -2
- package/dist/features/categories/components/CategoryDetailPageView.js +10 -2
- package/dist/features/categories/schemas/firestore.d.ts +7 -0
- package/dist/features/checkout/actions/index.d.ts +0 -1
- package/dist/features/checkout/actions/index.js +0 -1
- package/dist/features/checkout/schemas/firestore.d.ts +2 -2
- package/dist/features/events/actions/event-actions.d.ts +2 -0
- package/dist/features/events/actions/event-actions.js +5 -0
- package/dist/features/events/components/EventCard.js +1 -2
- package/dist/features/events/components/RelatedEventsCarousel.d.ts +15 -0
- package/dist/features/events/components/RelatedEventsCarousel.js +13 -0
- package/dist/features/events/components/index.d.ts +2 -0
- package/dist/features/events/components/index.js +1 -0
- package/dist/features/events/repository/events.repository.d.ts +6 -0
- package/dist/features/events/repository/events.repository.js +21 -0
- package/dist/features/history/repository/user-history.repository.d.ts +1 -1
- package/dist/features/history/utils/guest-history.d.ts +1 -1
- package/dist/features/homepage/components/FeaturedBundlesSection.js +4 -2
- package/dist/features/homepage/components/FeaturedProductsSection.js +2 -2
- package/dist/features/homepage/components/SectionCarousel.js +1 -1
- package/dist/features/homepage/components/WelcomeSection.js +1 -1
- package/dist/features/layout/AppLayoutShell.js +1 -1
- package/dist/features/layout/NavbarLayout.js +1 -1
- package/dist/features/layout/TitleBarLayout.js +2 -2
- package/dist/features/media/MediaVideo.d.ts +7 -0
- package/dist/features/media/MediaVideo.js +36 -5
- package/dist/features/orders/components/OrdersList.js +1 -5
- package/dist/features/orders/schemas/firestore.d.ts +2 -8
- package/dist/features/orders/schemas/index.d.ts +3 -32
- package/dist/features/orders/schemas/index.js +1 -4
- package/dist/features/orders/types/index.d.ts +6 -9
- package/dist/features/pre-orders/components/MarketplacePreorderCard.js +1 -1
- package/dist/features/pre-orders/components/PreOrderDetailPageView.js +2 -1
- package/dist/features/products/actions/product-actions.js +13 -10
- package/dist/features/products/components/MarketplaceBundleCard.d.ts +1 -1
- package/dist/features/products/components/MarketplaceBundleCard.js +7 -4
- package/dist/features/products/components/PrizeDrawDetailPageView.js +1 -1
- package/dist/features/products/components/PrizeDrawItemsEditor.js +2 -2
- package/dist/features/products/components/PrizeDrawWinnerMappingView.d.ts +14 -0
- package/dist/features/products/components/PrizeDrawWinnerMappingView.js +37 -0
- package/dist/features/products/components/PrizeRevealModal.d.ts +10 -23
- package/dist/features/products/components/PrizeRevealModal.js +18 -147
- package/dist/features/products/components/ProductDetailPageView.js +72 -30
- package/dist/features/products/components/ProductForm.js +7 -14
- package/dist/features/products/components/ProductGalleryClient.d.ts +10 -1
- package/dist/features/products/components/ProductGalleryClient.js +29 -10
- package/dist/features/products/components/ProductsIndexListing.js +2 -1
- package/dist/features/products/components/RelatedProductsCarousel.js +2 -2
- package/dist/features/products/components/ShowGroupSection.js +2 -2
- package/dist/features/products/components/SublistingCarouselSection.js +1 -1
- package/dist/features/products/components/index.d.ts +4 -2
- package/dist/features/products/components/index.js +1 -0
- package/dist/features/products/constants/action-defs.d.ts +0 -1
- package/dist/features/products/constants/action-defs.js +0 -2
- package/dist/features/products/repository/products.repository.d.ts +6 -0
- package/dist/features/products/repository/products.repository.js +20 -1
- package/dist/features/products/schemas/firestore.d.ts +26 -2
- package/dist/features/products/schemas/index.d.ts +11 -3
- package/dist/features/products/schemas/index.js +10 -2
- package/dist/features/products/types/index.d.ts +2 -1
- package/dist/features/reviews/actions/review-actions.js +1 -0
- package/dist/features/reviews/components/ReviewDetailPageView.js +8 -1
- package/dist/features/reviews/components/ReviewDetailShell.js +2 -1
- package/dist/features/reviews/components/ReviewsList.js +2 -1
- package/dist/features/reviews/schemas/firestore.d.ts +3 -0
- package/dist/features/reviews/schemas/index.d.ts +6 -0
- package/dist/features/reviews/schemas/index.js +2 -0
- package/dist/features/reviews/types/index.d.ts +3 -0
- package/dist/features/scams/actions/scam-actions.d.ts +3 -0
- package/dist/features/scams/actions/scam-actions.js +3 -2
- package/dist/features/scams/components/ScamProfileView.d.ts +2 -1
- package/dist/features/scams/components/ScamProfileView.js +2 -2
- package/dist/features/scams/repository/scammer.repository.d.ts +6 -0
- package/dist/features/scams/repository/scammer.repository.js +24 -0
- package/dist/features/seller/actions/seller-actions.js +8 -0
- package/dist/features/seller/components/SellerPrizeDrawsView.js +4 -0
- package/dist/features/stores/components/StoreDetailLayoutView.js +6 -6
- package/dist/features/stores/components/StoreNavTabs.d.ts +10 -1
- package/dist/features/stores/components/StoreNavTabs.js +21 -9
- package/dist/features/tester/actions/index.d.ts +1 -0
- package/dist/features/tester/actions/index.js +1 -0
- package/dist/features/tester/actions/leaderboard-actions.d.ts +3 -0
- package/dist/features/tester/actions/leaderboard-actions.js +5 -0
- package/dist/features/tester/components/AdminTesterFeedbackIssuesView.js +26 -4
- package/dist/features/tester/components/AdminTesterFeedbackListView.js +13 -0
- package/dist/features/tester/components/BugHunterLeaderboardView.d.ts +23 -0
- package/dist/features/tester/components/BugHunterLeaderboardView.js +18 -0
- package/dist/features/tester/components/index.d.ts +2 -0
- package/dist/features/tester/components/index.js +1 -0
- package/dist/features/tester/repository/tester-checklist-item.repository.d.ts +12 -1
- package/dist/features/tester/repository/tester-checklist-item.repository.js +80 -0
- package/dist/features/tester/repository/tester-checklist-response.repository.d.ts +4 -0
- package/dist/features/tester/repository/tester-checklist-response.repository.js +4 -0
- package/dist/features/tester/schemas/firestore.d.ts +22 -1
- package/dist/features/tester/schemas/firestore.js +8 -0
- package/dist/features/tester/seed-data/categories-tester-seed-data.js +42 -7
- package/dist/features/tester/seed-data/events-tester-seed-data.js +23 -0
- package/dist/features/tester/seed-data/orders-tester-seed-data.js +69 -31
- package/dist/features/tester/seed-data/products-tester-seed-data.js +152 -8
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +249 -14
- package/dist/features/wishlist/hooks/useGuestWishlist.js +11 -1
- package/dist/features/wishlist/hooks/useWishlistCount.js +16 -2
- package/dist/features/wishlist/utils/guest-wishlist.d.ts +5 -0
- package/dist/features/wishlist/utils/guest-wishlist.js +11 -0
- package/dist/index.d.ts +19 -22
- package/dist/index.js +25 -58
- package/dist/jobs.d.ts +1 -1
- package/dist/jobs.js +2 -2
- package/dist/next/routing/route-map.d.ts +2 -0
- package/dist/next/routing/route-map.js +1 -0
- package/dist/schemas/registry.d.ts +6 -26
- package/dist/seed/bids-seed-data.js +14 -2
- package/dist/seed/blog-posts-seed-data.js +2 -1
- package/dist/seed/categories-seed-data.js +11 -2
- package/dist/seed/events-seed-data.js +11 -11
- package/dist/seed/faq-seed-data.js +5 -1
- package/dist/seed/products-auctions-seed-data.js +2 -2
- package/dist/seed/products-preorders-seed-data.d.ts +2 -1
- package/dist/seed/products-prize-draws-seed-data.js +56 -10
- package/dist/seed/products-standard-seed-data.js +82 -3
- package/dist/seed/scammers-seed-data.js +75 -2
- package/dist/seed/site-settings-seed-data.js +5 -2
- package/dist/server-entry.d.ts +0 -1
- package/dist/server-entry.js +0 -1
- package/dist/server.d.ts +4 -20
- package/dist/server.js +10 -57
- package/dist/styles.css +1 -1
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/BaseListingCard.js +1 -1
- package/dist/ui/components/ImageLightbox.d.ts +9 -0
- package/dist/ui/components/ImageLightbox.js +6 -3
- package/dist/ui/components/ListingToolbar.d.ts +18 -1
- package/dist/ui/components/ListingToolbar.js +5 -2
- package/dist/ui/components/StickyToolbar.js +13 -8
- package/package.json +1 -1
|
@@ -7,6 +7,12 @@ declare class EventRepository extends BaseRepository<EventDocument> {
|
|
|
7
7
|
list(model: SieveModel): Promise<FirebaseSieveResult<EventDocument>>;
|
|
8
8
|
findBySlug(slug: string): Promise<EventDocument | null>;
|
|
9
9
|
findByIdOrSlug(idOrSlug: string): Promise<EventDocument | null>;
|
|
10
|
+
/**
|
|
11
|
+
* Active events sharing at least one tag with the given list — "related
|
|
12
|
+
* events" discovery. `array-contains-any` caps at 10 values, so only the
|
|
13
|
+
* first 10 tags are used.
|
|
14
|
+
*/
|
|
15
|
+
findByTagsOverlap(tags: string[], limit?: number): Promise<EventDocument[]>;
|
|
10
16
|
listActive(): Promise<EventDocument[]>;
|
|
11
17
|
createEvent(input: EventCreateInput): Promise<EventDocument>;
|
|
12
18
|
updateEvent(id: string, input: EventUpdateInput): Promise<EventDocument>;
|
|
@@ -21,6 +21,27 @@ class EventRepository extends BaseRepository {
|
|
|
21
21
|
return bySlug;
|
|
22
22
|
return this.findById(idOrSlug);
|
|
23
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Active events sharing at least one tag with the given list — "related
|
|
26
|
+
* events" discovery. `array-contains-any` caps at 10 values, so only the
|
|
27
|
+
* first 10 tags are used.
|
|
28
|
+
*/
|
|
29
|
+
async findByTagsOverlap(tags, limit = 8) {
|
|
30
|
+
if (tags.length === 0)
|
|
31
|
+
return [];
|
|
32
|
+
try {
|
|
33
|
+
const snap = await this.getCollection()
|
|
34
|
+
.where(EVENT_FIELDS.STATUS, "==", EVENT_FIELDS.STATUS_VALUES.ACTIVE)
|
|
35
|
+
.where(EVENT_FIELDS.TAGS, "array-contains-any", tags.slice(0, 10))
|
|
36
|
+
.limit(limit)
|
|
37
|
+
.get();
|
|
38
|
+
return snap.docs.map((doc) => this.mapDoc(doc));
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
void normalizeError(error);
|
|
42
|
+
throw new DatabaseError("Failed to find events by tag overlap", error);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
24
45
|
async listActive() {
|
|
25
46
|
try {
|
|
26
47
|
const now = new Date();
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* - re-visit semantics: removes existing entry for productId and unshifts new at position 0
|
|
10
10
|
* - all mutations run inside a Firestore transaction (concurrent-write safe)
|
|
11
11
|
*/
|
|
12
|
-
export type HistoryProductType = "product" | "auction" | "preorder";
|
|
12
|
+
export type HistoryProductType = "product" | "auction" | "preorder" | "prize-draw";
|
|
13
13
|
export interface HistoryItemSnapshot {
|
|
14
14
|
title?: string;
|
|
15
15
|
thumb?: string;
|
|
@@ -5,6 +5,7 @@ import { MediaImage } from "../../media/MediaImage";
|
|
|
5
5
|
import { ROUTES } from "../../../next/routing/route-map";
|
|
6
6
|
import { formatCurrency } from "../../../utils/number.formatter";
|
|
7
7
|
import { BUNDLE_COPY, BUNDLE_STOCK_VARIANT, } from "../../../_internal/shared/features/categories/bundle-copy";
|
|
8
|
+
import { computeBundleDiscount } from "../../../_internal/shared/features/categories/bundle-pricing";
|
|
8
9
|
import { BundleBuyNowCta } from "../../categories/components/BundleBuyNowCta";
|
|
9
10
|
const __P = {
|
|
10
11
|
p3: "p-[var(--appkit-space-3)]",
|
|
@@ -24,7 +25,8 @@ function FeaturedBundleCard({ bundle, onBuyNow }) {
|
|
|
24
25
|
const memberCount = bundle.bundleProductIds?.length ?? 0;
|
|
25
26
|
const cover = bundle.display?.coverImage;
|
|
26
27
|
const href = String(ROUTES.PUBLIC.BUNDLE_DETAIL?.(bundle.slug) ?? "#");
|
|
27
|
-
|
|
28
|
+
const discount = computeBundleDiscount(bundle.bundlePrice, bundle.bundleOriginalTotal);
|
|
29
|
+
return (_jsxs(Stack, { gap: "none", surface: "default", rounded: "xl", border: "default", children: [_jsxs(Link, { href: href, className: "group block p-[var(--appkit-space-3)] hover:no-underline", children: [_jsxs(Div, { className: `relative mb-2 aspect-square ${__O.hidden}`, rounded: "lg", surface: "subtle", children: [cover ? (_jsx(MediaImage, { src: cover, alt: bundle.name, size: "card", className: "transition-transform group-hover:scale-105" })) : (_jsx(Row, { textSize: "3xl", className: "h-full w-full", align: "center", justify: "center", children: PLACEHOLDER_EMOJI })), discount && (_jsx(Badge, { variant: "success", className: "absolute left-2 top-2", children: BUNDLE_COPY.detail.discountBadge(discount.percent) }))] }), _jsx(Text, { className: "line-clamp-2", size: "sm", weight: "semibold", children: bundle.name }), _jsxs(Row, { gap: "sm", align: "center", className: "mt-1", wrap: true, children: [_jsx(Text, { size: "sm", weight: "bold", children: bundle.bundlePrice
|
|
28
30
|
? formatCurrency(bundle.bundlePrice, "INR")
|
|
29
|
-
: BUNDLE_COPY.featured.priceFallback }), _jsxs(Text, { size: "xs", color: "muted", children: ["\u00B7 ", BUNDLE_COPY.featured.itemCount(memberCount)] }), stock !== "in_stock" && (_jsx(Badge, { variant: BUNDLE_STOCK_VARIANT[stock], children: BUNDLE_COPY.stockBadge.listVariantOutOfStock }))] })] }), onBuyNow && (_jsx(Div, { border: "subtle", className: `border-t ${__P.p3}`, padding: "t-xs", children: _jsx(BundleBuyNowCta, { bundleSlug: bundle.slug, outOfStock: stock === "out_of_stock", onBuyNow: onBuyNow, compact: true }) }))] }));
|
|
31
|
+
: BUNDLE_COPY.featured.priceFallback }), discount && (_jsx(Text, { size: "xs", color: "muted", className: "line-through", children: formatCurrency(discount.originalTotal, "INR") })), _jsxs(Text, { size: "xs", color: "muted", children: ["\u00B7 ", BUNDLE_COPY.featured.itemCount(memberCount)] }), stock !== "in_stock" && (_jsx(Badge, { variant: BUNDLE_STOCK_VARIANT[stock], children: BUNDLE_COPY.stockBadge.listVariantOutOfStock }))] })] }), onBuyNow && (_jsx(Div, { border: "subtle", className: `border-t ${__P.p3}`, padding: "t-xs", children: _jsx(BundleBuyNowCta, { bundleSlug: bundle.slug, outOfStock: stock === "out_of_stock", onBuyNow: onBuyNow, compact: true }) }))] }));
|
|
30
32
|
}
|
|
@@ -3,7 +3,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
3
3
|
import { SectionCarousel } from "./SectionCarousel";
|
|
4
4
|
import { useFeaturedProducts } from "../hooks/useFeaturedProducts";
|
|
5
5
|
import { InteractiveProductCard } from "../../products/components/InteractiveProductCard";
|
|
6
|
-
import {
|
|
6
|
+
import { pluginFor } from "../../../_internal/shared/listing-types/_registry";
|
|
7
7
|
import { CAROUSEL_PER_VIEW } from "../constants/carousel-per-view";
|
|
8
8
|
export function FeaturedProductsSection({ title = "Featured Products", description, viewMoreHref, viewMoreLabel = "View all products →", className = "", filterByBrand, initialItems, rows = 1, autoScroll = false, scrollInterval = 5000, loop = true, }) {
|
|
9
9
|
const { data, isLoading } = useFeaturedProducts({
|
|
@@ -13,5 +13,5 @@ export function FeaturedProductsSection({ title = "Featured Products", descripti
|
|
|
13
13
|
: undefined,
|
|
14
14
|
});
|
|
15
15
|
const items = data?.items ?? [];
|
|
16
|
-
return (_jsx(SectionCarousel, { title: title, description: description, pillLabel: "Featured Products", headingVariant: "editorial", viewMoreHref: viewMoreHref, viewMoreLabel: viewMoreLabel, items: items, isLoading: isLoading, skeletonCount: 4, perView: CAROUSEL_PER_VIEW.standard, gap: 16, rows: Math.min(Math.max(rows, 1), 4), autoScroll: autoScroll, autoScrollInterval: scrollInterval, loop: loop, keyExtractor: (product) => product.id, renderItem: (product) => (_jsx(InteractiveProductCard, { product: product, href:
|
|
16
|
+
return (_jsx(SectionCarousel, { title: title, description: description, pillLabel: "Featured Products", headingVariant: "editorial", viewMoreHref: viewMoreHref, viewMoreLabel: viewMoreLabel, items: items, isLoading: isLoading, skeletonCount: 4, perView: CAROUSEL_PER_VIEW.standard, gap: 16, rows: Math.min(Math.max(rows, 1), 4), autoScroll: autoScroll, autoScrollInterval: scrollInterval, loop: loop, keyExtractor: (product) => product.id, renderItem: (product) => (_jsx(InteractiveProductCard, { product: product, href: pluginFor(product.listingType ?? "standard").detailRoute(product.slug ?? product.id ?? "") })), className: className }));
|
|
17
17
|
}
|
|
@@ -41,5 +41,5 @@ export function SectionCarousel({ title, description, headingVariant = "editoria
|
|
|
41
41
|
headingClass,
|
|
42
42
|
]
|
|
43
43
|
.filter(Boolean)
|
|
44
|
-
.join(" "), children: title }), headingVariant === "editorial" && (_jsxs(Row, { align: "center", justify: "center", gap: "sm", textSize: "xs", color: "faint", className: "mt-1 select-none", "aria-hidden": "true", children: [_jsx(Span, { className: "h-px w-6 bg-current" }), _jsx(Span, { size: "xs", children: "\u2736" }), _jsx(Span, { className: "h-px w-6 bg-current" })] })), description && (_jsx(Text, { className: `${descVariant} mt-2`, size: "base", children: description }))] }), isLoading ? (_jsx(CarouselSkeleton, { count: skeletonCount })) : (_jsx(HorizontalScroller, { items: items, renderItem: renderItem, perView: perView, gap: gap, autoScroll: autoScroll, autoScrollInterval: autoScrollInterval, loop: loop, keyExtractor: keyExtractor, rows: rows, minItemWidth: minItemWidth, showArrows: true, snapToItems: true, showFadeEdges: true, showScrollbar: false, pauseOnHover: true })), viewMoreHref && !isLoading && (_jsx(Row, { className: "mt-6", justify: "start", children: _jsx(TextLink, { rounded: "lg", paddingX: "xl", paddingY: "sm", href: viewMoreHref, className: `inline-flex items-[center] gap-[0.375rem]
|
|
44
|
+
.join(" "), children: title }), headingVariant === "editorial" && (_jsxs(Row, { align: "center", justify: "center", gap: "sm", textSize: "xs", color: "faint", className: "mt-1 select-none", "aria-hidden": "true", children: [_jsx(Span, { className: "h-px w-6 bg-current" }), _jsx(Span, { size: "xs", children: "\u2736" }), _jsx(Span, { className: "h-px w-6 bg-current" })] })), description && (_jsx(Text, { className: `${descVariant} mt-2`, size: "base", children: description }))] }), isLoading ? (_jsx(CarouselSkeleton, { count: skeletonCount })) : (_jsx(HorizontalScroller, { items: items, renderItem: renderItem, perView: perView, gap: gap, autoScroll: autoScroll, autoScrollInterval: autoScrollInterval, loop: loop, keyExtractor: keyExtractor, rows: rows, minItemWidth: minItemWidth, showArrows: true, snapToItems: true, showFadeEdges: true, showScrollbar: false, pauseOnHover: true })), viewMoreHref && !isLoading && (_jsx(Row, { className: "mt-6", justify: "start", children: _jsx(TextLink, { rounded: "lg", paddingX: "xl", paddingY: "sm", href: viewMoreHref, className: `inline-flex items-[center] gap-[0.375rem] transition-colors bg-primary text-white hover:bg-primary-600 ${useLightText ? "shadow-lg" : ""}`, size: "sm", weight: "semibold", children: viewMoreLabel }) }))] })] }));
|
|
45
45
|
}
|
|
@@ -9,5 +9,5 @@ export function WelcomeSection({ title, subtitle, pillLabel, showCTA = true, cta
|
|
|
9
9
|
if (isLoading) {
|
|
10
10
|
return (_jsx(Section, { className: `relative overflow-hidden md:py-[6rem] ${className}`, paddingY: "y-4xl", paddingX: "x-md", children: _jsxs(Div, { className: "animate-pulse max-w-4xl mx-auto text-left", children: [_jsx(Div, { className: "h-6 w-52 mb-6", surface: "subtle", rounded: "full" }), _jsx(Div, { className: "h-20 mb-4 max-w-2xl", surface: "subtle", rounded: "lg" }), _jsx(Div, { className: "h-6 mb-8 max-w-lg", surface: "subtle", rounded: "lg" }), _jsxs(Row, { justify: "start", gap: "md", children: [_jsx(Div, { className: "h-12 w-36", surface: "subtle", rounded: "xl" }), _jsx(Div, { className: "h-12 w-36", surface: "subtle", rounded: "xl" })] })] }) }));
|
|
11
11
|
}
|
|
12
|
-
return (_jsxs(Section, { className: `relative overflow-hidden md:py-[7rem] ${className}`, paddingY: "y-5xl", paddingX: "x-md", children: [_jsx(Div, { className: "pointer-events-none absolute -top-32 -left-32 w-[28rem] h-[28rem] bg-primary/10 blur-3xl", rounded: "full", "aria-hidden": "true" }), _jsx(Div, { className: "pointer-events-none absolute -bottom-40 -right-40 w-[36rem] h-[36rem] bg-secondary/10 dark:bg-secondary/15 blur-3xl", rounded: "full", "aria-hidden": "true" }), _jsx(Div, { className: "relative z-10 max-w-7xl mx-auto", children: _jsxs(Grid, { align: "center", gap: "2xl", className: "grid-cols-1 lg:grid-cols-2 xl:grid-cols-2 2xl:grid-cols-2", children: [_jsxs(Div, { className: "text-left", children: [pillLabel && (_jsx(Div, { children: _jsxs(Span, { layout: "inline-flex", gap: "md", size: "xs", weight: "medium", className: "border border-primary-[500]/30 bg-primary-500/10 px-[1.25rem] py-[0.375rem] tracking-[0.2em] text-primary-700 dark:text-primary-400 backdrop-blur-sm", rounded: "full", transform: "uppercase", children: [_jsx(Span, { className: "w-1.5 h-1.5 bg-primary-500 inline-block", rounded: "full", "aria-hidden": "true" }), pillLabel, _jsx(Span, { className: "w-1.5 h-1.5 bg-primary-500 inline-block", rounded: "full", "aria-hidden": "true" })] }) })), _jsx(Heading, { level: 1, variant: "none", gradient: "brand-tri", className: "mt-4 font-display leading-[1.1] tracking-tight break-words", size: "5xl", mdSize: "6xl", lgSize: "7xl", xlSize: "7xl", children: title }), subtitle && (_jsx(Text, { className: `mt-4 ${themed.textSecondary} max-w-xl leading-relaxed`, size: "xl", children: subtitle })), showCTA && (_jsxs(Row, { wrap: true, gap: "md", className: "mt-8", justify: "start", children: [ctaHref ? (_jsx(TextLink, { rounded: "xl", href: ctaHref, className: "inline-flex items-[center] justify-[center] px-[2rem] py-[0.875rem] !bg-primary hover:!bg-primary-600 text-white dark:!bg-primary dark:hover:!bg-primary-600 dark:text-white btn-glow transition-all hover:scale-[1.02]", size: "base", weight: "bold", children: ctaLabel })) : (_jsx(Button, { variant: "primary", size: "lg", onClick: onCtaClick, children: ctaLabel })), secondaryCtaHref ? (_jsx(TextLink, { rounded: "xl", href: secondaryCtaHref, className: "inline-flex items-[center] justify-[center] border-2 border-cobalt/40 dark:border-cobalt-400/40 px-[2rem] py-[0.875rem] text-cobalt-700 dark:text-cobalt-300 hover:bg-cobalt-50 dark:hover:bg-cobalt-900/20 transition-all hover:scale-[1.02]", size: "base", weight: "semibold", children: secondaryCtaLabel })) : (_jsx(Button, { variant: "outline", size: "lg", onClick: onSecondaryCtaClick, children: secondaryCtaLabel }))] })), trustChips.length > 0 && (_jsx(Row, { wrap: true, gap: "sm", className: "mt-6", justify: "start", children: trustChips.map((chip) => (_jsxs(Span, { layout: "inline-flex", gap: "xs", size: "xs", weight: "medium", className: "border border-zinc-[200] dark:border-slate-[700] px-[0.875rem] py-[0.375rem]", rounded: "full", surface: "subtle", color: "muted", children: [chip.emoji, " ", chip.label] }, chip.key))) }))] }), _jsx(Div, { className: "hidden lg:block", children: _jsxs(Div, { className: `relative overflow-hidden aspect-[4/3] bg-[image:var(--appkit-gradient-section-mesh)]`, shadow: "2xl", border: "default", rounded: "3xl", children: [_jsx(Div, { className: "absolute inset-0 bg-[image:var(--appkit-gradient-glass)]" }), _jsxs(Stack, { centered: true, gap: "sm", className: `absolute inset-0 ${flex.center}
|
|
12
|
+
return (_jsxs(Section, { className: `relative overflow-hidden md:py-[7rem] ${className}`, paddingY: "y-5xl", paddingX: "x-md", children: [_jsx(Div, { className: "pointer-events-none absolute -top-32 -left-32 w-[28rem] h-[28rem] bg-primary/10 blur-3xl", rounded: "full", "aria-hidden": "true" }), _jsx(Div, { className: "pointer-events-none absolute -bottom-40 -right-40 w-[36rem] h-[36rem] bg-secondary/10 dark:bg-secondary/15 blur-3xl", rounded: "full", "aria-hidden": "true" }), _jsx(Div, { className: "relative z-10 max-w-7xl mx-auto", children: _jsxs(Grid, { align: "center", gap: "2xl", className: "grid-cols-1 lg:grid-cols-2 xl:grid-cols-2 2xl:grid-cols-2", children: [_jsxs(Div, { className: "text-left", children: [pillLabel && (_jsx(Div, { children: _jsxs(Span, { layout: "inline-flex", gap: "md", size: "xs", weight: "medium", className: "border border-primary-[500]/30 bg-primary-500/10 px-[1.25rem] py-[0.375rem] tracking-[0.2em] text-primary-700 dark:text-primary-400 backdrop-blur-sm", rounded: "full", transform: "uppercase", children: [_jsx(Span, { className: "w-1.5 h-1.5 bg-primary-500 inline-block", rounded: "full", "aria-hidden": "true" }), pillLabel, _jsx(Span, { className: "w-1.5 h-1.5 bg-primary-500 inline-block", rounded: "full", "aria-hidden": "true" })] }) })), _jsx(Heading, { level: 1, variant: "none", gradient: "brand-tri", className: "mt-4 font-display leading-[1.1] tracking-tight break-words", size: "5xl", mdSize: "6xl", lgSize: "7xl", xlSize: "7xl", children: title }), subtitle && (_jsx(Text, { className: `mt-4 ${themed.textSecondary} max-w-xl leading-relaxed`, size: "xl", children: subtitle })), showCTA && (_jsxs(Row, { wrap: true, gap: "md", className: "mt-8", justify: "start", children: [ctaHref ? (_jsx(TextLink, { rounded: "xl", href: ctaHref, className: "inline-flex items-[center] justify-[center] px-[2rem] py-[0.875rem] !bg-primary hover:!bg-primary-600 text-white dark:!bg-primary dark:hover:!bg-primary-600 dark:text-white btn-glow transition-all hover:scale-[1.02]", size: "base", weight: "bold", children: ctaLabel })) : (_jsx(Button, { variant: "primary", size: "lg", onClick: onCtaClick, children: ctaLabel })), secondaryCtaHref ? (_jsx(TextLink, { rounded: "xl", href: secondaryCtaHref, className: "inline-flex items-[center] justify-[center] border-2 border-cobalt/40 dark:border-cobalt-400/40 px-[2rem] py-[0.875rem] text-cobalt-700 dark:text-cobalt-300 hover:bg-cobalt-50 dark:hover:bg-cobalt-900/20 transition-all hover:scale-[1.02]", size: "base", weight: "semibold", children: secondaryCtaLabel })) : (_jsx(Button, { variant: "outline", size: "lg", onClick: onSecondaryCtaClick, children: secondaryCtaLabel }))] })), trustChips.length > 0 && (_jsx(Row, { wrap: true, gap: "sm", className: "mt-6", justify: "start", children: trustChips.map((chip) => (_jsxs(Span, { layout: "inline-flex", gap: "xs", size: "xs", weight: "medium", className: "border border-zinc-[200] dark:border-slate-[700] px-[0.875rem] py-[0.375rem]", rounded: "full", surface: "subtle", color: "muted", children: [chip.emoji, " ", chip.label] }, chip.key))) }))] }), _jsx(Div, { className: "hidden lg:block", children: _jsxs(Div, { className: `relative overflow-hidden aspect-[4/3] bg-[image:var(--appkit-gradient-section-mesh)]`, shadow: "2xl", border: "default", rounded: "3xl", children: [_jsx(Div, { className: "absolute inset-0 bg-[image:var(--appkit-gradient-glass)]" }), _jsxs(Stack, { centered: true, gap: "sm", className: `absolute inset-0 ${flex.center} p-[2.5rem]`, children: [_jsx(SiteMark, { title: brandLogoText || "LetItRip.in", size: "xl" }), _jsx(SiteLogo, { title: brandLogoText || "LetItRip.in", size: "xl" })] })] }) })] }) })] }));
|
|
13
13
|
}
|
|
@@ -79,7 +79,7 @@ function SidebarContent({ sidebarItems, sidebarSections, sidebarPrimaryActions,
|
|
|
79
79
|
storeDashboard: sidebarProfileLabels?.storeDashboard ?? sidebarProfileLabels?.sellerDashboard ?? "Store Dashboard",
|
|
80
80
|
logout: sidebarProfileLabels?.logout ?? "Logout",
|
|
81
81
|
};
|
|
82
|
-
const navItemClass = "flex w-full items-center justify-end gap-[var(--appkit-space-2)] rounded-lg px-[var(--appkit-space-3)] py-[var(--appkit-space-2)] text-[length:var(--appkit-text-sm)] text-zinc-700 transition-colors hover:bg-primary-50 hover:text-primary-800 text-[var(--appkit-color-text-muted)] hover:bg-[var(--appkit-color-surface-elevated)] dark:hover:text-secondary-300";
|
|
82
|
+
const navItemClass = "flex w-full items-center justify-end gap-[var(--appkit-space-2)] rounded-lg px-[var(--appkit-space-3)] py-[var(--appkit-space-2)] text-[length:var(--appkit-text-sm)] text-zinc-700 transition-colors hover:bg-primary-50 hover:text-primary-800 text-[var(--appkit-color-text-muted)] dark:hover:bg-[var(--appkit-color-surface-elevated)] dark:hover:text-secondary-300";
|
|
83
83
|
const normalizedSections = hasSections
|
|
84
84
|
? sidebarSections
|
|
85
85
|
: hasLegacyItems
|
|
@@ -11,7 +11,7 @@ function DefaultNavItem({ item, isActive, }) {
|
|
|
11
11
|
const activeClasses = item.highlighted
|
|
12
12
|
? "border border-primary-400/40 dark:border-secondary-400/30 text-primary-700 dark:text-secondary-400 bg-primary-50/80 dark:bg-secondary-900/30 px-[var(--appkit-space-3)] transition-colors duration-150"
|
|
13
13
|
: isActive
|
|
14
|
-
? "bg-primary-50 bg-[var(--appkit-color-surface-elevated)] text-primary-800 dark:text-white font-semibold px-[var(--appkit-space-3)] border-b-2 border-primary-500 dark:border-secondary-400 rounded-none pb-[6px] transition-colors duration-150"
|
|
14
|
+
? "bg-primary-50 dark:bg-[var(--appkit-color-surface-elevated)] text-primary-800 dark:text-white font-semibold px-[var(--appkit-space-3)] border-b-2 border-primary-500 dark:border-secondary-400 rounded-none pb-[6px] transition-colors duration-150"
|
|
15
15
|
: "text-[var(--appkit-color-text-muted)] hover:bg-[var(--appkit-color-surface)] hover:bg-[var(--appkit-color-surface-elevated)] hover:text-zinc-950 dark:hover:text-white transition-colors duration-150 px-[var(--appkit-space-3)]";
|
|
16
16
|
return (_jsxs(Link, { href: item.href, "aria-current": isActive ? "page" : undefined, className: `flex items-center gap-[var(--appkit-space-1-5)] py-[var(--appkit-space-2)] text-[length:var(--appkit-text-sm)] rounded-lg font-medium transition-colors duration-150 whitespace-nowrap ${activeClasses}`, children: [item.icon, _jsx(Span, { children: item.label })] }));
|
|
17
17
|
}
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import Link from "next/link";
|
|
3
3
|
import { Avatar, BlockHeader, Button, Div, Row, Section, SiteLogo, SiteMark, Span } from "../../ui";
|
|
4
4
|
/** Shared icon-button class for all action buttons in the title bar. */
|
|
5
|
-
const iconBtn = "flex items-center justify-center w-9 h-9 rounded-lg text-[var(--appkit-color-text-muted)] hover:bg-primary-50 hover:text-primary-700 hover:bg-[var(--appkit-color-surface-elevated)] dark:hover:text-secondary-400 transition-colors";
|
|
5
|
+
const iconBtn = "flex items-center justify-center w-9 h-9 rounded-lg text-[var(--appkit-color-text-muted)] hover:bg-primary-50 hover:text-primary-700 dark:hover:bg-[var(--appkit-color-surface-elevated)] dark:hover:text-secondary-400 transition-colors";
|
|
6
6
|
/** Badge counter class for wishlist/cart counts. */
|
|
7
7
|
const countBadge = "absolute -top-0.5 -right-0.5 flex items-center justify-center w-4 h-4 rounded-full bg-error-surface text-white text-[10px] font-bold leading-none";
|
|
8
8
|
/**
|
|
@@ -30,7 +30,7 @@ export function TitleBarLayout({ onToggleSidebar, sidebarOpen, onSearchToggle, s
|
|
|
30
30
|
const wishlistEl = wishlistHref ? (_jsxs(Link, { href: wishlistHref, "aria-label": `Wishlist${wishlistCount > 0 ? `, ${wishlistCount} items` : ""}`, className: `relative ${iconBtn}`, "data-tour": "nav-wishlist", children: [_jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4.318 6.318a4.5 4.5 0 0 0 0 6.364L12 20.364l7.682-7.682a4.5 4.5 0 0 0-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 0 0-6.364 0z" }) }), wishlistCount > 0 && (_jsx(Span, { className: countBadge, children: wishlistCount > 9 ? "9+" : wishlistCount }))] })) : null;
|
|
31
31
|
const cartEl = cartHref ? (_jsxs(Link, { href: cartHref, "aria-label": `Cart${cartCount > 0 ? `, ${cartCount} items` : ""}`, className: `relative ${iconBtn}`, "data-tour": "nav-cart", children: [_jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M2.25 3h1.386c.51 0 .955.343 1.087.835l.383 1.437M7.5 14.25a3 3 0 0 0-3 3h15.75m-12.75-3h11.218c1.121-2.3 2.1-4.684 2.924-7.138a60.114 60.114 0 0 0-16.536-1.84M7.5 14.25 5.106 5.272M6 20.25a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0zm12.75 0a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0z" }) }), cartCount > 0 && (_jsx(Span, { className: countBadge, children: cartCount > 9 ? "9+" : cartCount }))] })) : null;
|
|
32
32
|
const profileEl = profileHref ? (_jsx(Link, { href: profileHref, "aria-label": user ? `Profile — ${user.displayName ?? user.email}` : "Sign in", className: `relative ${iconBtn}`, "data-tour": "nav-profile", children: _jsx(Avatar, { src: user?.photoURL ?? undefined, name: user?.displayName ?? user?.email ?? undefined, alt: user?.displayName ?? "Profile", size: "sm" }) })) : null;
|
|
33
|
-
const authButtonsEl = !user && (loginHref || registerHref) ? (_jsxs(Row, { gap: "xs", className: "hidden lg:flex", children: [loginHref && (_jsx(Link, { href: loginHref, className: "px-[var(--appkit-space-3)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-sm)] font-medium text-[var(--appkit-color-text-muted)] hover:text-primary-700 dark:hover:text-secondary-400 transition-colors rounded-lg hover:bg-primary-50 hover:bg-[var(--appkit-color-surface-elevated)]", children: "Sign in" })), registerHref && (_jsx(Link, { href: registerHref, className: "px-[var(--appkit-space-3)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-sm)] font-semibold rounded-lg bg-primary text-white hover:bg-primary/90 transition-colors btn-glow shadow-sm", children: "Register" }))] })) : null;
|
|
33
|
+
const authButtonsEl = !user && (loginHref || registerHref) ? (_jsxs(Row, { gap: "xs", className: "hidden lg:flex", children: [loginHref && (_jsx(Link, { href: loginHref, className: "px-[var(--appkit-space-3)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-sm)] font-medium text-[var(--appkit-color-text-muted)] hover:text-primary-700 dark:hover:text-secondary-400 transition-colors rounded-lg hover:bg-primary-50 dark:hover:bg-[var(--appkit-color-surface-elevated)]", children: "Sign in" })), registerHref && (_jsx(Link, { href: registerHref, className: "px-[var(--appkit-space-3)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-sm)] font-semibold rounded-lg bg-primary text-white hover:bg-primary/90 transition-colors btn-glow shadow-sm", children: "Register" }))] })) : null;
|
|
34
34
|
const hasTb2 = !!(notificationsEl || wishlistEl || cartEl || profileEl);
|
|
35
35
|
// ── Render ───────────────────────────────────────────────────────────────────
|
|
36
36
|
return (_jsxs(BlockHeader, { id: id, className: `sticky top-0 z-50 bg-[var(--appkit-color-bg)]/95 backdrop-blur-md border-b border-[var(--appkit-color-border-subtle)] shadow-sm ${className}`, children: [promoStripText && (_jsx(Section, { color: "inverse", tone: "accent-banner", className: "text-[length:var(--appkit-text-xs)] text-center font-medium", padding: "y-2xs", children: promoStripText })), _jsxs(Div, { paddingX: "x-page", className: "container mx-auto max-w-[1920px]", children: [_jsxs(Row, { justify: "between", gap: "none", className: "relative h-14", children: [_jsx(Row, { gap: "3", children: _jsxs(Link, { href: logoHref, "aria-label": brandName, className: "flex items-center gap-[var(--appkit-space-2)] transition-opacity hover:opacity-80", children: [_jsx(Div, { className: "flex md:hidden", children: _jsx(SiteMark, { title: brandName, size: "sm" }) }), _jsx(SiteLogo, { title: brandName, size: "md" })] }) }), _jsx(Row, { className: "hidden md:flex absolute inset-y-0 left-1/2 -translate-x-1/2", align: "center", children: _jsx(Link, { href: logoHref, "aria-label": brandName, className: "flex items-center transition-opacity hover:opacity-80", children: _jsx(SiteMark, { src: siteLogoUrl, title: brandName, size: "lg" }) }) }), _jsxs(Row, { gap: "xs", children: [devSlot, compareEl, notificationSlot, notificationsEl && _jsx(Div, { className: "hidden lg:flex", children: notificationsEl }), wishlistEl && _jsx(Div, { className: "hidden lg:flex", children: wishlistEl }), cartEl && _jsx(Div, { className: "hidden lg:flex", children: cartEl }), user
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* client-side overlay covers `<video>` playback because there is no
|
|
6
6
|
* Vercel-Hobby-budget ffmpeg pipeline to bake the watermark into the frames.
|
|
7
7
|
*/
|
|
8
|
+
export type MediaVideoWatermarkPosition = "top-left" | "top-right" | "bottom-left" | "bottom-right" | "center" | "custom";
|
|
8
9
|
export interface MediaVideoWatermark {
|
|
9
10
|
/** `"text"` renders a `<Span>` overlay; `"image"` renders a `<MediaImage>` (proxied). */
|
|
10
11
|
type: "text" | "image";
|
|
@@ -16,6 +17,12 @@ export interface MediaVideoWatermark {
|
|
|
16
17
|
size?: number;
|
|
17
18
|
/** Percentage opacity (0–100). Default 20. */
|
|
18
19
|
opacity?: number;
|
|
20
|
+
/** Anchor preset — mirrors the server-side Sharp pipeline. Default `"center"`. */
|
|
21
|
+
position?: MediaVideoWatermarkPosition;
|
|
22
|
+
/** `position === "custom"` only: % of container width, from center. +right / -left. */
|
|
23
|
+
offsetX?: number;
|
|
24
|
+
/** `position === "custom"` only: % of container height, from center. +down / -up. */
|
|
25
|
+
offsetY?: number;
|
|
19
26
|
}
|
|
20
27
|
export interface MediaVideoProps {
|
|
21
28
|
/** Video URL. When undefined the fallback placeholder is rendered. */
|
|
@@ -48,6 +48,39 @@ export function MediaVideo({ src, thumbnailUrl, alt = "Video", controls = true,
|
|
|
48
48
|
}
|
|
49
49
|
return (_jsxs(_Fragment, { children: [_jsx("video", { ref: videoRef, src: resolvedSrc, poster: resolvedPoster, controls: controls, autoPlay: autoPlayMuted, muted: autoPlayMuted, loop: loop, playsInline: true, "aria-label": alt, className: `absolute inset-0 w-full h-full ${fitClass}` }), effectiveWatermark ? _jsx(MediaVideoWatermarkLayer, { config: effectiveWatermark }) : null] }));
|
|
50
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* Mirrors the server-side Sharp pipeline's 5 anchor presets + `"custom"`
|
|
53
|
+
* %-offset-from-center (`resolveCompositePlacement` in
|
|
54
|
+
* `src/app/api/media/_watermark.ts`) as CSS insets, so a video's live
|
|
55
|
+
* overlay and its poster-frame-as-image watermark land in the same spot.
|
|
56
|
+
* Default `"center"`, 0/0 offset — matches the server-side default too.
|
|
57
|
+
*/
|
|
58
|
+
function resolveWatermarkPlacementStyle(config) {
|
|
59
|
+
const position = config.position ?? "center";
|
|
60
|
+
const inset = "2%";
|
|
61
|
+
switch (position) {
|
|
62
|
+
case "top-left":
|
|
63
|
+
return { top: inset, left: inset };
|
|
64
|
+
case "top-right":
|
|
65
|
+
return { top: inset, right: inset };
|
|
66
|
+
case "bottom-left":
|
|
67
|
+
return { bottom: inset, left: inset };
|
|
68
|
+
case "bottom-right":
|
|
69
|
+
return { bottom: inset, right: inset };
|
|
70
|
+
case "custom": {
|
|
71
|
+
const offsetX = Math.max(-45, Math.min(45, config.offsetX ?? 0));
|
|
72
|
+
const offsetY = Math.max(-45, Math.min(45, config.offsetY ?? 0));
|
|
73
|
+
return {
|
|
74
|
+
top: `calc(50% + ${offsetY}%)`,
|
|
75
|
+
left: `calc(50% + ${offsetX}%)`,
|
|
76
|
+
transform: "translate(-50%, -50%)",
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
case "center":
|
|
80
|
+
default:
|
|
81
|
+
return { top: "50%", left: "50%", transform: "translate(-50%, -50%)" };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
51
84
|
/**
|
|
52
85
|
* Visual watermark overlay rendered on top of the playing `<video>`. Native
|
|
53
86
|
* video controls render in a higher layer (the browser shadow DOM), so this
|
|
@@ -62,16 +95,14 @@ function MediaVideoWatermarkLayer({ config }) {
|
|
|
62
95
|
const opacity = Math.max(0, Math.min(100, config.opacity ?? 20)) / 100;
|
|
63
96
|
if (widthPct === 0 || opacity === 0)
|
|
64
97
|
return null;
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
// with the video, just like the server-side Sharp pipeline scales images.
|
|
98
|
+
// Width is a percentage of the container so the watermark scales with the
|
|
99
|
+
// video, just like the server-side Sharp pipeline scales images.
|
|
68
100
|
const containerStyle = {
|
|
69
101
|
position: "absolute",
|
|
70
|
-
right: "2%",
|
|
71
|
-
bottom: "2%",
|
|
72
102
|
width: `${widthPct}%`,
|
|
73
103
|
pointerEvents: "none",
|
|
74
104
|
opacity,
|
|
105
|
+
...resolveWatermarkPlacementStyle(config),
|
|
75
106
|
};
|
|
76
107
|
if (config.type === "image" && config.imageUrl) {
|
|
77
108
|
return (_jsx(Div, { className: "z-10", style: containerStyle, "aria-hidden": "true", children: _jsx("img", { src: resolveMediaUrl(config.imageUrl), alt: "", className: "block w-full h-auto" }) }));
|
|
@@ -29,13 +29,9 @@ export function OrderCard({ order, onClick, labels = {}, renderActions }) {
|
|
|
29
29
|
it.prizeRevealStatus !== undefined &&
|
|
30
30
|
it.prizeRevealStatus !== "revealed");
|
|
31
31
|
const revealsRemaining = unrevealedPrizeDraws.length;
|
|
32
|
-
const earliestDeadline = unrevealedPrizeDraws
|
|
33
|
-
.map((it) => it.prizeRevealDeadline)
|
|
34
|
-
.filter((d) => !!d)
|
|
35
|
-
.sort()[0];
|
|
36
32
|
return (_jsxs(Div, { padding: "5", role: onClick ? "button" : undefined, tabIndex: onClick ? 0 : undefined, onKeyDown: onClick
|
|
37
33
|
? (e) => (e.key === "Enter" || e.key === " ") && onClick(order)
|
|
38
|
-
: undefined, onClick: onClick ? () => onClick(order) : undefined, className: `border border-neutral-200 ${onClick ? "cursor-pointer transition hover:shadow-md" : ""}`, rounded: "xl", surface: "default", children: [_jsxs(Row, { wrap: true, align: "start", justify: "between", gap: "3", children: [_jsxs(Div, { children: [_jsxs(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "xs", children: ["Order #", order.id.slice(-8).toUpperCase()] }), date && (_jsx(Text, { className: "mt-0.5", color: "faint", size: "xs", children: date }))] }), _jsx(Span, { size: "xs", weight: "semibold", className: `capitalize ${statusColor}`, rounded: "full", padding: "pill-md", children: labels[order.orderStatus] ?? order.orderStatus.replace(/_/g, " ") })] }), revealsRemaining > 0 && (
|
|
34
|
+
: undefined, onClick: onClick ? () => onClick(order) : undefined, className: `border border-neutral-200 ${onClick ? "cursor-pointer transition hover:shadow-md" : ""}`, rounded: "xl", surface: "default", children: [_jsxs(Row, { wrap: true, align: "start", justify: "between", gap: "3", children: [_jsxs(Div, { children: [_jsxs(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "xs", children: ["Order #", order.id.slice(-8).toUpperCase()] }), date && (_jsx(Text, { className: "mt-0.5", color: "faint", size: "xs", children: date }))] }), _jsx(Span, { size: "xs", weight: "semibold", className: `capitalize ${statusColor}`, rounded: "full", padding: "pill-md", children: labels[order.orderStatus] ?? order.orderStatus.replace(/_/g, " ") })] }), revealsRemaining > 0 && (_jsx(Row, { gap: "sm", className: "mt-2", children: _jsxs(Span, { size: "xs", weight: "semibold", className: CLS_PRIZE_BADGE, children: [revealsRemaining, " ", revealsRemaining === 1 ? "reveal" : "reveals", " pending"] }) })), _jsxs(Row, { wrap: true, gap: "3", className: "mt-4", children: [order.items.slice(0, 3).map((item, i) => (_jsxs(Row, { gap: "sm", children: [item.image && (_jsx(Div, { className: "relative h-10 w-10 flex-shrink-0", rounded: "lg", overflow: "hidden", children: _jsx(MediaImage, { src: item.image, alt: item.title, size: "thumbnail" }) })), _jsxs(Div, { children: [_jsx(Text, { truncate: true, size: "sm", weight: "medium", color: "primary", children: item.title }), _jsxs(Text, { size: "xs", color: "faint", children: ["\u00D7", item.quantity] })] })] }, i))), order.items.length > 3 && (_jsxs(Span, { size: "xs", className: "self-center", color: "faint", children: ["+", order.items.length - 3, " more"] }))] }), _jsxs(Row, { justify: "between", className: "mt-4 border-t border-neutral-100", padding: "t-sm", children: [_jsxs(Span, { size: "sm", className: "text-[var(--appkit-color-text-muted)]", children: [order.currency ?? "", " Total"] }), _jsx(Span, { weight: "semibold", className: "text-[var(--appkit-color-text)]", children: formatCurrency(order.total, order.currency) })] }), renderActions && (_jsx(Row, { justify: "end", gap: "sm", className: "mt-3", onClick: (e) => e.stopPropagation(), children: renderActions(order) }))] }));
|
|
39
35
|
}
|
|
40
36
|
export function OrdersList({ orders, isLoading, onOrderClick, totalPages = 1, currentPage = 1, onPageChange, emptyLabel = "No orders found", renderActions, }) {
|
|
41
37
|
if (isLoading) {
|
|
@@ -92,10 +92,6 @@ export interface OrderDocumentItem {
|
|
|
92
92
|
listingType?: ListingType;
|
|
93
93
|
/** SB8-F â€" per-item reveal status; flips through pending â†' open â†' revealed/closed. */
|
|
94
94
|
prizeRevealStatus?: "pending" | "open" | "closed" | "revealed";
|
|
95
|
-
/** SB8-F â€" ISO timestamp; deadline by which the buyer must claim the prize. */
|
|
96
|
-
prizeRevealDeadline?: string;
|
|
97
|
-
/** SB8-F â€" set after the reveal API picks a winner. */
|
|
98
|
-
revealedItemNumber?: number;
|
|
99
95
|
/** Set when the buyer cancels this specific line item post-order (partial cancellation). */
|
|
100
96
|
cancelledQuantity?: number;
|
|
101
97
|
cancelledAt?: Date;
|
|
@@ -236,12 +232,10 @@ export interface OrderDocument extends BaseDocument {
|
|
|
236
232
|
images: string[];
|
|
237
233
|
wonAt: Date;
|
|
238
234
|
};
|
|
239
|
-
/** Deadline for the buyer to claim the won prize (typically 7 days). */
|
|
240
|
-
prizeRevealDeadline?: Date;
|
|
241
|
-
/** True once `prizeRevealDeadline` passes without a claim â€" auto-forfeit. */
|
|
242
|
-
prizeRevealExpired?: boolean;
|
|
243
235
|
/** Source product id when the order came from a prize-draw entry. */
|
|
244
236
|
prizeDrawProductId?: string;
|
|
237
|
+
/** Denormalized from the product at order-creation time — drives reveal copy on the order-detail page without an extra product fetch. */
|
|
238
|
+
prizeRevealMode?: "instant" | "scheduled";
|
|
245
239
|
/** True for prize-draw entries and bundle purchases that bypass refund. */
|
|
246
240
|
isNonRefundable?: boolean;
|
|
247
241
|
/** Set when the order came from a bundle â€" points back to `bundles/{id}`. */
|
|
@@ -90,8 +90,6 @@ export declare const orderDocumentItemSchema: z.ZodObject<{
|
|
|
90
90
|
totalPrice: z.ZodEffects<z.ZodNumber, number, number>;
|
|
91
91
|
listingType: z.ZodOptional<z.ZodEnum<["standard", "auction", "pre-order", "prize-draw", "classified", "digital-code", "live"]>>;
|
|
92
92
|
prizeRevealStatus: z.ZodOptional<z.ZodEnum<["pending", "open", "closed", "revealed"]>>;
|
|
93
|
-
prizeRevealDeadline: z.ZodOptional<z.ZodString>;
|
|
94
|
-
revealedItemNumber: z.ZodOptional<z.ZodNumber>;
|
|
95
93
|
}, "strip", z.ZodTypeAny, {
|
|
96
94
|
productId: string;
|
|
97
95
|
quantity: number;
|
|
@@ -100,8 +98,6 @@ export declare const orderDocumentItemSchema: z.ZodObject<{
|
|
|
100
98
|
totalPrice: number;
|
|
101
99
|
listingType?: "standard" | "auction" | "pre-order" | "prize-draw" | "classified" | "digital-code" | "live" | undefined;
|
|
102
100
|
prizeRevealStatus?: "closed" | "open" | "pending" | "revealed" | undefined;
|
|
103
|
-
prizeRevealDeadline?: string | undefined;
|
|
104
|
-
revealedItemNumber?: number | undefined;
|
|
105
101
|
}, {
|
|
106
102
|
productId: string;
|
|
107
103
|
quantity: number;
|
|
@@ -110,8 +106,6 @@ export declare const orderDocumentItemSchema: z.ZodObject<{
|
|
|
110
106
|
totalPrice: number;
|
|
111
107
|
listingType?: "standard" | "auction" | "pre-order" | "prize-draw" | "classified" | "digital-code" | "live" | undefined;
|
|
112
108
|
prizeRevealStatus?: "closed" | "open" | "pending" | "revealed" | undefined;
|
|
113
|
-
prizeRevealDeadline?: string | undefined;
|
|
114
|
-
revealedItemNumber?: number | undefined;
|
|
115
109
|
}>;
|
|
116
110
|
export declare const orderRefundEventSchema: z.ZodObject<{
|
|
117
111
|
refundId: z.ZodString;
|
|
@@ -255,8 +249,6 @@ export declare const orderFirestoreSchema: z.ZodObject<{
|
|
|
255
249
|
totalPrice: z.ZodEffects<z.ZodNumber, number, number>;
|
|
256
250
|
listingType: z.ZodOptional<z.ZodEnum<["standard", "auction", "pre-order", "prize-draw", "classified", "digital-code", "live"]>>;
|
|
257
251
|
prizeRevealStatus: z.ZodOptional<z.ZodEnum<["pending", "open", "closed", "revealed"]>>;
|
|
258
|
-
prizeRevealDeadline: z.ZodOptional<z.ZodString>;
|
|
259
|
-
revealedItemNumber: z.ZodOptional<z.ZodNumber>;
|
|
260
252
|
}, "strip", z.ZodTypeAny, {
|
|
261
253
|
productId: string;
|
|
262
254
|
quantity: number;
|
|
@@ -265,8 +257,6 @@ export declare const orderFirestoreSchema: z.ZodObject<{
|
|
|
265
257
|
totalPrice: number;
|
|
266
258
|
listingType?: "standard" | "auction" | "pre-order" | "prize-draw" | "classified" | "digital-code" | "live" | undefined;
|
|
267
259
|
prizeRevealStatus?: "closed" | "open" | "pending" | "revealed" | undefined;
|
|
268
|
-
prizeRevealDeadline?: string | undefined;
|
|
269
|
-
revealedItemNumber?: number | undefined;
|
|
270
260
|
}, {
|
|
271
261
|
productId: string;
|
|
272
262
|
quantity: number;
|
|
@@ -275,8 +265,6 @@ export declare const orderFirestoreSchema: z.ZodObject<{
|
|
|
275
265
|
totalPrice: number;
|
|
276
266
|
listingType?: "standard" | "auction" | "pre-order" | "prize-draw" | "classified" | "digital-code" | "live" | undefined;
|
|
277
267
|
prizeRevealStatus?: "closed" | "open" | "pending" | "revealed" | undefined;
|
|
278
|
-
prizeRevealDeadline?: string | undefined;
|
|
279
|
-
revealedItemNumber?: number | undefined;
|
|
280
268
|
}>, "many">>;
|
|
281
269
|
orderType: z.ZodOptional<z.ZodString>;
|
|
282
270
|
imageUrls: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
@@ -450,15 +438,8 @@ export declare const orderFirestoreSchema: z.ZodObject<{
|
|
|
450
438
|
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
451
439
|
}, z.ZodTypeAny, "passthrough">;
|
|
452
440
|
}>>;
|
|
453
|
-
prizeRevealDeadline: z.ZodOptional<z.ZodUnion<[z.ZodDate, z.ZodString, z.ZodObject<{
|
|
454
|
-
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
455
|
-
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
456
|
-
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
457
|
-
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
458
|
-
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
459
|
-
}, z.ZodTypeAny, "passthrough">>]>>;
|
|
460
|
-
prizeRevealExpired: z.ZodOptional<z.ZodBoolean>;
|
|
461
441
|
prizeDrawProductId: z.ZodOptional<z.ZodString>;
|
|
442
|
+
prizeRevealMode: z.ZodOptional<z.ZodEnum<["instant", "scheduled"]>>;
|
|
462
443
|
isNonRefundable: z.ZodOptional<z.ZodBoolean>;
|
|
463
444
|
bundleId: z.ZodOptional<z.ZodString>;
|
|
464
445
|
paymentBatchId: z.ZodOptional<z.ZodString>;
|
|
@@ -582,6 +563,7 @@ export declare const orderFirestoreSchema: z.ZodObject<{
|
|
|
582
563
|
paymentMethod?: string | undefined;
|
|
583
564
|
notes?: string | undefined;
|
|
584
565
|
storeName?: string | undefined;
|
|
566
|
+
prizeRevealMode?: "instant" | "scheduled" | undefined;
|
|
585
567
|
physicalLocation?: {
|
|
586
568
|
zone: string;
|
|
587
569
|
shelf: string;
|
|
@@ -595,8 +577,6 @@ export declare const orderFirestoreSchema: z.ZodObject<{
|
|
|
595
577
|
totalPrice: number;
|
|
596
578
|
listingType?: "standard" | "auction" | "pre-order" | "prize-draw" | "classified" | "digital-code" | "live" | undefined;
|
|
597
579
|
prizeRevealStatus?: "closed" | "open" | "pending" | "revealed" | undefined;
|
|
598
|
-
prizeRevealDeadline?: string | undefined;
|
|
599
|
-
revealedItemNumber?: number | undefined;
|
|
600
580
|
}[] | undefined;
|
|
601
581
|
payoutStatus?: "paid" | "eligible" | "requested" | undefined;
|
|
602
582
|
couponCode?: string | undefined;
|
|
@@ -668,10 +648,6 @@ export declare const orderFirestoreSchema: z.ZodObject<{
|
|
|
668
648
|
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
669
649
|
}, z.ZodTypeAny, "passthrough">;
|
|
670
650
|
} | undefined;
|
|
671
|
-
prizeRevealDeadline?: string | Date | z.objectOutputType<{
|
|
672
|
-
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
673
|
-
}, z.ZodTypeAny, "passthrough"> | undefined;
|
|
674
|
-
prizeRevealExpired?: boolean | undefined;
|
|
675
651
|
prizeDrawProductId?: string | undefined;
|
|
676
652
|
isNonRefundable?: boolean | undefined;
|
|
677
653
|
bundleId?: string | undefined;
|
|
@@ -735,6 +711,7 @@ export declare const orderFirestoreSchema: z.ZodObject<{
|
|
|
735
711
|
paymentMethod?: string | undefined;
|
|
736
712
|
notes?: string | undefined;
|
|
737
713
|
storeName?: string | undefined;
|
|
714
|
+
prizeRevealMode?: "instant" | "scheduled" | undefined;
|
|
738
715
|
physicalLocation?: {
|
|
739
716
|
zone: string;
|
|
740
717
|
shelf: string;
|
|
@@ -748,8 +725,6 @@ export declare const orderFirestoreSchema: z.ZodObject<{
|
|
|
748
725
|
totalPrice: number;
|
|
749
726
|
listingType?: "standard" | "auction" | "pre-order" | "prize-draw" | "classified" | "digital-code" | "live" | undefined;
|
|
750
727
|
prizeRevealStatus?: "closed" | "open" | "pending" | "revealed" | undefined;
|
|
751
|
-
prizeRevealDeadline?: string | undefined;
|
|
752
|
-
revealedItemNumber?: number | undefined;
|
|
753
728
|
}[] | undefined;
|
|
754
729
|
payoutStatus?: "paid" | "eligible" | "requested" | undefined;
|
|
755
730
|
couponCode?: string | undefined;
|
|
@@ -821,10 +796,6 @@ export declare const orderFirestoreSchema: z.ZodObject<{
|
|
|
821
796
|
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
822
797
|
}, z.ZodTypeAny, "passthrough">;
|
|
823
798
|
} | undefined;
|
|
824
|
-
prizeRevealDeadline?: string | Date | z.objectInputType<{
|
|
825
|
-
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
826
|
-
}, z.ZodTypeAny, "passthrough"> | undefined;
|
|
827
|
-
prizeRevealExpired?: boolean | undefined;
|
|
828
799
|
prizeDrawProductId?: string | undefined;
|
|
829
800
|
isNonRefundable?: boolean | undefined;
|
|
830
801
|
bundleId?: string | undefined;
|
|
@@ -51,8 +51,6 @@ export const orderDocumentItemSchema = z.object({
|
|
|
51
51
|
totalPrice: rupeesSchema,
|
|
52
52
|
listingType: orderListingTypeSchema.optional(),
|
|
53
53
|
prizeRevealStatus: z.enum(["pending", "open", "closed", "revealed"]).optional(),
|
|
54
|
-
prizeRevealDeadline: z.string().optional(),
|
|
55
|
-
revealedItemNumber: z.number().int().optional(),
|
|
56
54
|
});
|
|
57
55
|
export const orderRefundEventSchema = z.object({
|
|
58
56
|
refundId: z.string(),
|
|
@@ -152,9 +150,8 @@ export const orderFirestoreSchema = z.object({
|
|
|
152
150
|
payoutId: z.string().optional(),
|
|
153
151
|
offerId: z.string().optional(),
|
|
154
152
|
prizeWon: prizeWonSchema.optional(),
|
|
155
|
-
prizeRevealDeadline: firestoreDateSchema.optional(),
|
|
156
|
-
prizeRevealExpired: z.boolean().optional(),
|
|
157
153
|
prizeDrawProductId: z.string().optional(),
|
|
154
|
+
prizeRevealMode: z.enum(["instant", "scheduled"]).optional(),
|
|
158
155
|
isNonRefundable: z.boolean().optional(),
|
|
159
156
|
bundleId: z.string().optional(),
|
|
160
157
|
paymentBatchId: z.string().optional(),
|
|
@@ -32,10 +32,6 @@ export interface OrderItem {
|
|
|
32
32
|
* "X reveals pending" badge on user orders.
|
|
33
33
|
*/
|
|
34
34
|
prizeRevealStatus?: "pending" | "open" | "closed" | "revealed";
|
|
35
|
-
/** ISO timestamp — when the user must claim their reveal before forfeit. */
|
|
36
|
-
prizeRevealDeadline?: string;
|
|
37
|
-
/** Set after the reveal endpoint picks a winner — the prize item index. */
|
|
38
|
-
revealedItemNumber?: number;
|
|
39
35
|
/** Set when the buyer cancels this specific line item post-order (partial cancellation). */
|
|
40
36
|
cancelledQuantity?: number;
|
|
41
37
|
/**
|
|
@@ -85,9 +81,10 @@ export interface Order {
|
|
|
85
81
|
disputeRaised?: boolean;
|
|
86
82
|
disputeStatus?: "open" | "resolved";
|
|
87
83
|
/**
|
|
88
|
-
* Reveal-mode prize-draw fields (SB4-H/SB8-C). Set once the
|
|
89
|
-
*
|
|
90
|
-
*
|
|
84
|
+
* Reveal-mode prize-draw fields (SB4-H/SB8-C). Set once the winner has
|
|
85
|
+
* been automatically assigned to this order (on payment confirmation,
|
|
86
|
+
* draw expiry, or sellout — see assignPrizeDrawWinner); drives the
|
|
87
|
+
* PrizeRevealModal's "won" display on the order-detail page.
|
|
91
88
|
*/
|
|
92
89
|
prizeWon?: {
|
|
93
90
|
itemNumber: number;
|
|
@@ -95,10 +92,10 @@ export interface Order {
|
|
|
95
92
|
images: string[];
|
|
96
93
|
wonAt: string;
|
|
97
94
|
};
|
|
98
|
-
prizeRevealDeadline?: string;
|
|
99
|
-
prizeRevealExpired?: boolean;
|
|
100
95
|
/** Source prize-draw product id, when this order came from one. */
|
|
101
96
|
prizeDrawProductId?: string;
|
|
97
|
+
/** Denormalized from the product at order-creation time — drives PrizeRevealModal copy. */
|
|
98
|
+
prizeRevealMode?: "instant" | "scheduled";
|
|
102
99
|
}
|
|
103
100
|
export interface OrderListResponse {
|
|
104
101
|
items: Order[];
|
|
@@ -64,5 +64,5 @@ export function MarketplacePreorderCard({ product, className = "", variant = "gr
|
|
|
64
64
|
/* ── Compact list layout ── */
|
|
65
65
|
_jsxs(_Fragment, { children: [_jsxs(Row, { className: "min-w-0", align: "start", justify: "between", gap: "sm", children: [_jsx(TextLink, { href: detailHref, className: "min-w-0 flex-1", children: _jsx(Text, { size: "sm", weight: "medium", color: "primary", truncate: 2, children: product.title }) }), wishlistActions && (_jsx(Button, { type: "button", variant: "ghost", size: "sm", className: `shrink-0 p-[0.25rem] leading-none ${inWishlist ? "text-primary" : "text-[var(--appkit-color-text-faint)]"}`, onClick: handleWishlist, "aria-label": inWishlist ? mergedLabels.removeFromWishlist : mergedLabels.addToWishlist, children: inWishlist ? "♥" : "♡" }))] }), _jsxs(Row, { align: "center", gap: "sm", wrap: true, children: [_jsx(Text, { className: "text-primary", size: "sm", weight: "bold", children: formatCurrency(product.price, getDefaultCurrency()) }), shipDate && _jsx(PreorderBadge, { shipDate: shipDate })] }), _jsx(Button, { type: "button", variant: "primary", size: "sm", textSize: "xs", className: "self-start mt-0.5", onClick: handleNavigate, children: mergedLabels.reserveNow })] })) : (
|
|
66
66
|
/* ── Full grid layout ── */
|
|
67
|
-
_jsxs(_Fragment, { children: [_jsx(TextLink, { href: detailHref, children: _jsx(Text, { size: "sm", weight: "medium", color: "primary", truncate: 2, children: product.title }) }), product.description ? (_jsx(RichText, { html: normalizeRichTextHtml(product.description), proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "line-clamp-2 text-[length:var(--appkit-text-xs)] text-[var(--appkit-color-text-muted)]" })) : null, _jsxs(Row, { justify: "between", className: "mt-1", gap: "sm", children: [_jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: formatCurrency(product.price, getDefaultCurrency()) }), shipDate && _jsx(PreorderBadge, { shipDate: shipDate })] }), _jsxs(Row, { justify: "between", className: "mt-2", gap: "sm", children: [_jsx(Button, { type: "button", variant: "primary", size: "sm", textSize: "xs", className: "flex-1", onClick: handleNavigate, children: mergedLabels.reserveNow }), onAddToCart ? (_jsx(Button, { type: "button", variant: "outline", size: "sm", textSize: "xs", className: "flex-1", onClick: (e) => { e.stopPropagation(); onAddToCart(product); }, children: mergedLabels.addToCart })) : null, wishlistActions ? (_jsx(Button, { type: "button", variant: "ghost", size: "sm", className: `${inWishlist ? "text-primary" : "text-[var(--appkit-color-text-muted)]"}`, onClick: handleWishlist, "aria-label": inWishlist ? mergedLabels.removeFromWishlist : mergedLabels.addToWishlist, children: inWishlist ? "♥" : "♡" })) : null] })] })) })] }));
|
|
67
|
+
_jsxs(_Fragment, { children: [_jsx(TextLink, { href: detailHref, children: _jsx(Text, { size: "sm", weight: "medium", color: "primary", truncate: 2, children: product.title }) }), product.description ? (_jsx(RichText, { html: normalizeRichTextHtml(product.description), proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "line-clamp-2 text-[length:var(--appkit-text-xs)] text-[var(--appkit-color-text-muted)]" })) : null, _jsxs(Row, { justify: "between", className: "mt-1", gap: "sm", children: [_jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: formatCurrency(product.price, getDefaultCurrency()) }), shipDate && _jsx(PreorderBadge, { shipDate: shipDate })] }), _jsxs(Row, { justify: "between", className: "mt-auto pt-2", gap: "sm", children: [_jsx(Button, { type: "button", variant: "primary", size: "sm", textSize: "xs", className: "flex-1", onClick: handleNavigate, children: mergedLabels.reserveNow }), onAddToCart ? (_jsx(Button, { type: "button", variant: "outline", size: "sm", textSize: "xs", className: "flex-1", onClick: (e) => { e.stopPropagation(); onAddToCart(product); }, children: mergedLabels.addToCart })) : null, wishlistActions ? (_jsx(Button, { type: "button", variant: "ghost", size: "sm", className: `${inWishlist ? "text-primary" : "text-[var(--appkit-color-text-muted)]"}`, onClick: handleWishlist, "aria-label": inWishlist ? mergedLabels.removeFromWishlist : mergedLabels.addToWishlist, children: inWishlist ? "♥" : "♡" })) : null] })] })) })] }));
|
|
68
68
|
}
|
|
@@ -82,6 +82,7 @@ export async function PreOrderDetailPageView({ id, initialPreOrder, onReserveNow
|
|
|
82
82
|
: typeof p.mainImage === "string"
|
|
83
83
|
? [p.mainImage]
|
|
84
84
|
: [];
|
|
85
|
+
const productVideo = p.video;
|
|
85
86
|
const reservedCount = typeof p.preOrderCurrentCount === "number"
|
|
86
87
|
? p.preOrderCurrentCount
|
|
87
88
|
: typeof p.reservedCount === "number"
|
|
@@ -150,7 +151,7 @@ export async function PreOrderDetailPageView({ id, initialPreOrder, onReserveNow
|
|
|
150
151
|
price: typeof p.price === "number" ? p.price : undefined,
|
|
151
152
|
storeId: typeof p.storeId === "string" ? p.storeId : undefined,
|
|
152
153
|
storeName: typeof p.storeName === "string" ? p.storeName : undefined,
|
|
153
|
-
} }), _jsxs(Container, { size: "xl", padding: "y-lg", children: [_jsxs(Row, { className: "mb-4", align: "center", justify: "between", gap: "sm", wrap: true, children: [_jsxs(Nav, { "aria-label": "Breadcrumb", layout: "flex-wrap", gap: "2xs", textSize: "xs", color: "muted", children: [_jsx(Link, { href: String(ROUTES.HOME), className: CLS_BREADCRUMB_LINK, children: "Home" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.PRE_ORDERS), className: CLS_BREADCRUMB_LINK, children: "Pre-Orders" }), category && (_jsxs(_Fragment, { children: [_jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(category)), className: CLS_BREADCRUMB_LINK, children: categoryName || category })] })), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Span, { className: "truncate max-w-[200px]", color: "muted", children: title })] }), _jsx(ShareButton, { title: title })] }), _jsx(PreOrderDetailView, { renderGallery: () => (_jsx(ProductGalleryClient, { images: images, productName: title })), renderInfo: () => (_jsx(PreOrderInfoSection, { title: title, productionStatus: productionStatus, maxPerUser: maxPerUser, deliveryDate: deliveryDate, featured: featured, freeShipping: freeShipping, condition: condition, isCancellable: isCancellable, category: category, categoryName: categoryName, brand: brand, brandSlug: brandSlug, productFeatures: productFeatures, features: features, descriptionHtml: descriptionHtml, safeSeller: safeSeller, storeHref: storeHref })), renderSublistingSection: sublistingCategoryId
|
|
154
|
+
} }), _jsxs(Container, { size: "xl", padding: "y-lg", children: [_jsxs(Row, { className: "mb-4", align: "center", justify: "between", gap: "sm", wrap: true, children: [_jsxs(Nav, { "aria-label": "Breadcrumb", layout: "flex-wrap", gap: "2xs", textSize: "xs", color: "muted", children: [_jsx(Link, { href: String(ROUTES.HOME), className: CLS_BREADCRUMB_LINK, children: "Home" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.PRE_ORDERS), className: CLS_BREADCRUMB_LINK, children: "Pre-Orders" }), category && (_jsxs(_Fragment, { children: [_jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(category)), className: CLS_BREADCRUMB_LINK, children: categoryName || category })] })), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Span, { className: "truncate max-w-[200px]", color: "muted", children: title })] }), _jsx(ShareButton, { title: title })] }), _jsx(PreOrderDetailView, { renderGallery: () => (_jsx(ProductGalleryClient, { images: images, video: productVideo, productName: title })), renderInfo: () => (_jsx(PreOrderInfoSection, { title: title, productionStatus: productionStatus, maxPerUser: maxPerUser, deliveryDate: deliveryDate, featured: featured, freeShipping: freeShipping, condition: condition, isCancellable: isCancellable, category: category, categoryName: categoryName, brand: brand, brandSlug: brandSlug, productFeatures: productFeatures, features: features, descriptionHtml: descriptionHtml, safeSeller: safeSeller, storeHref: storeHref })), renderSublistingSection: sublistingCategoryId
|
|
154
155
|
? () => (_jsx(SublistingCarouselSection, { sublistingCategoryId: sublistingCategoryId, currentListingId: String(product.id) }))
|
|
155
156
|
: undefined, renderGroupSection: groupId
|
|
156
157
|
? () => (_jsx(ShowGroupSection, { groupId: groupId, currentSlug: String(p.slug ?? product.id), isParent: isGroupParent, groupTitle: groupTitle }))
|
|
@@ -101,16 +101,19 @@ export async function listPreOrders(params = {}) {
|
|
|
101
101
|
});
|
|
102
102
|
}
|
|
103
103
|
export async function getRelatedProducts(categoryId, excludeId, limit = 6) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
104
|
+
// `categoryId` here is really a category slug — products store their
|
|
105
|
+
// category FK in categorySlugs[] (array-contains), not a scalar
|
|
106
|
+
// "categoryId" field, which never existed on ProductDocument.
|
|
107
|
+
const docs = await productRepository.findByCategory(categoryId);
|
|
108
|
+
const items = docs
|
|
109
|
+
.filter((p) => p.id !== excludeId && p.status === ProductStatusValues.PUBLISHED)
|
|
110
|
+
.sort((a, b) => {
|
|
111
|
+
const aTime = a.createdAt instanceof Date ? a.createdAt.getTime() : 0;
|
|
112
|
+
const bTime = b.createdAt instanceof Date ? b.createdAt.getTime() : 0;
|
|
113
|
+
return bTime - aTime;
|
|
114
|
+
})
|
|
115
|
+
.slice(0, limit);
|
|
116
|
+
return { items, total: items.length, page: 1, pageSize: limit, totalPages: 1, hasMore: false };
|
|
114
117
|
}
|
|
115
118
|
export async function getStoreStorefrontProducts(storeId) {
|
|
116
119
|
const products = await productRepository.findByStore(storeId);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CategoryDocument } from "../../categories/schemas/firestore";
|
|
2
|
-
export type MarketplaceBundleCardData = Pick<CategoryDocument, "id" | "name" | "slug" | "bundleItemDetails" | "bundleProductIds" | "bundlePrice" | "bundleStockStatus" | "display">;
|
|
2
|
+
export type MarketplaceBundleCardData = Pick<CategoryDocument, "id" | "name" | "slug" | "bundleItemDetails" | "bundleProductIds" | "bundlePrice" | "bundleOriginalTotal" | "bundleStockStatus" | "display">;
|
|
3
3
|
export interface MarketplaceBundleCardLabels {
|
|
4
4
|
bundleBadge?: string;
|
|
5
5
|
inStockBadge?: string;
|