@mohasinac/appkit 4.1.3 → 4.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_internal/server/features/auth/actions.d.ts +0 -1
- package/dist/_internal/server/features/auth/actions.js +0 -1
- package/dist/_internal/server/features/bundles/data.d.ts +22 -4
- package/dist/_internal/server/features/bundles/data.js +41 -4
- package/dist/_internal/server/features/bundles/index.d.ts +1 -1
- package/dist/_internal/server/features/bundles/index.js +1 -1
- package/dist/_internal/server/features/checkout/actions.d.ts +15 -0
- package/dist/_internal/server/features/checkout/actions.js +249 -257
- package/dist/_internal/server/features/checkout/prize-bundle-gates.d.ts +5 -16
- package/dist/_internal/server/features/checkout/prize-bundle-gates.js +5 -30
- package/dist/_internal/server/features/orders/adapters.js +1 -14
- package/dist/_internal/server/features/products/actions.js +8 -3
- package/dist/_internal/server/features/products/service.d.ts +15 -0
- package/dist/_internal/server/features/products/service.js +41 -0
- package/dist/_internal/server/functions/firestore.d.ts +3 -1
- package/dist/_internal/server/functions/firestore.js +17 -1
- package/dist/_internal/server/functions/scheduled.d.ts +3 -5
- package/dist/_internal/server/functions/scheduled.js +14 -30
- package/dist/_internal/server/jobs/core/bundleStockSync.js +25 -7
- package/dist/_internal/server/jobs/core/countersReconcile.js +7 -1
- package/dist/_internal/server/jobs/core/index.d.ts +4 -4
- package/dist/_internal/server/jobs/core/index.js +4 -4
- package/dist/_internal/server/jobs/core/jobRunners.js +10 -0
- package/dist/_internal/server/jobs/core/newsletterExport.d.ts +15 -0
- package/dist/_internal/server/jobs/core/newsletterExport.js +43 -0
- package/dist/_internal/server/jobs/core/onOrderStatusChange.d.ts +2 -0
- package/dist/_internal/server/jobs/core/onOrderStatusChange.js +1 -0
- package/dist/_internal/server/jobs/core/onPrizeDrawPaymentConfirmed.d.ts +19 -0
- package/dist/_internal/server/jobs/core/onPrizeDrawPaymentConfirmed.js +30 -0
- package/dist/_internal/server/jobs/core/paymentWindowTimeout.d.ts +2 -2
- package/dist/_internal/server/jobs/core/paymentWindowTimeout.js +3 -2
- package/dist/_internal/server/jobs/core/pendingOrderTimeout.js +1 -0
- package/dist/_internal/server/jobs/core/prizeDrawAssignWinner.d.ts +20 -0
- package/dist/_internal/server/jobs/core/prizeDrawAssignWinner.js +113 -0
- package/dist/_internal/server/jobs/core/prizeDrawExpiryReveal.d.ts +14 -0
- package/dist/_internal/server/jobs/core/prizeDrawExpiryReveal.js +74 -0
- package/dist/_internal/server/jobs/core/prizeDrawSoldOutReveal.d.ts +22 -0
- package/dist/_internal/server/jobs/core/prizeDrawSoldOutReveal.js +68 -0
- package/dist/_internal/server/jobs/core/revenueRollup.d.ts +13 -0
- package/dist/_internal/server/jobs/core/revenueRollup.js +22 -0
- package/dist/_internal/server/jobs/core/whatsappNotify.d.ts +35 -0
- package/dist/_internal/server/jobs/core/whatsappNotify.js +82 -0
- package/dist/_internal/server/jobs/handlers/index.d.ts +4 -4
- package/dist/_internal/server/jobs/handlers/index.js +9 -6
- package/dist/_internal/server/jobs/handlers/onPrizeDrawPaymentConfirmed.d.ts +3 -0
- package/dist/_internal/server/jobs/handlers/onPrizeDrawPaymentConfirmed.js +11 -0
- package/dist/_internal/server/jobs/handlers/prizeDrawExpiryReveal.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/prizeDrawExpiryReveal.js +2 -0
- package/dist/_internal/server/jobs/handlers/prizeDrawSoldOutReveal.d.ts +3 -0
- package/dist/_internal/server/jobs/handlers/prizeDrawSoldOutReveal.js +4 -0
- package/dist/_internal/server/jobs/handlers/revenueRollup.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/revenueRollup.js +2 -0
- package/dist/_internal/shared/actions/action-registry.js +40 -10
- package/dist/_internal/shared/checkout/rules/_defaults.js +1 -2
- package/dist/_internal/shared/checkout/rules/_limits.d.ts +3 -0
- package/dist/_internal/shared/checkout/rules/_limits.js +3 -0
- package/dist/_internal/shared/checkout/rules/_registry.d.ts +2 -1
- package/dist/_internal/shared/checkout/rules/_registry.js +2 -2
- package/dist/_internal/shared/checkout/rules/classified.rule.js +0 -1
- package/dist/_internal/shared/checkout/rules/index.d.ts +1 -1
- package/dist/_internal/shared/checkout/rules/index.js +1 -1
- package/dist/_internal/shared/checkout/rules/prize-draw.rule.js +20 -1
- package/dist/_internal/shared/checkout/rules/types.d.ts +17 -7
- package/dist/_internal/shared/features/categories/bundle-copy.d.ts +2 -0
- package/dist/_internal/shared/features/categories/bundle-copy.js +2 -0
- package/dist/_internal/shared/features/categories/bundle-pricing.d.ts +23 -0
- package/dist/_internal/shared/features/categories/bundle-pricing.js +25 -0
- package/dist/_internal/shared/features/checkout/config.d.ts +7 -0
- package/dist/_internal/shared/features/checkout/config.js +7 -0
- package/dist/_internal/shared/fees/calculator.d.ts +26 -0
- package/dist/_internal/shared/fees/calculator.js +25 -0
- package/dist/_internal/shared/listing-types/prize-draw/config.js +1 -1
- package/dist/constants/api-endpoints.d.ts +6 -9
- package/dist/constants/api-endpoints.js +5 -5
- package/dist/constants/field-names.d.ts +17 -1
- package/dist/constants/field-names.js +17 -1
- package/dist/features/about/components/FeesView.js +18 -0
- package/dist/features/account/hooks/useNotifications.js +5 -0
- package/dist/features/admin/actions/admin-actions.js +6 -0
- package/dist/features/admin/actions/notification-actions.d.ts +4 -1
- package/dist/features/admin/actions/notification-actions.js +57 -12
- package/dist/features/admin/components/AdminAdsView.js +1 -1
- package/dist/features/admin/components/AdminBlogEditorView.js +1 -1
- package/dist/features/admin/components/AdminNewsletterView.js +42 -11
- package/dist/features/admin/components/AdminPrizeDrawsView.js +3 -3
- package/dist/features/admin/components/AdminSiteSettingsView.js +115 -11
- package/dist/features/admin/components/AdminTesterChecklistView.js +74 -22
- package/dist/features/admin/hooks/useAdminListing.d.ts +4 -0
- package/dist/features/admin/hooks/useAdminListing.js +4 -2
- package/dist/features/admin/repository/analytics-rollup.repository.d.ts +25 -0
- package/dist/features/admin/repository/analytics-rollup.repository.js +31 -0
- package/dist/features/admin/repository/site-settings.repository.js +8 -1
- package/dist/features/admin/schemas/firestore.d.ts +54 -5
- package/dist/features/admin/schemas/firestore.js +63 -26
- package/dist/features/auctions/actions/bid-actions.js +3 -1
- package/dist/features/auctions/components/AuctionBidsTable.d.ts +6 -1
- package/dist/features/auctions/components/AuctionBidsTable.js +8 -4
- package/dist/features/auctions/components/AuctionDetailPageView.js +10 -13
- package/dist/features/auctions/components/CollapsibleBidHistory.d.ts +23 -2
- package/dist/features/auctions/components/CollapsibleBidHistory.js +21 -3
- package/dist/features/auctions/components/LiveBidPrice.d.ts +19 -0
- package/dist/features/auctions/components/LiveBidPrice.js +17 -0
- package/dist/features/auctions/components/PlaceBidFormClient.d.ts +1 -1
- package/dist/features/auctions/components/PlaceBidFormClient.js +16 -1
- package/dist/features/auctions/hooks/useBids.d.ts +33 -0
- package/dist/features/auctions/hooks/useBids.js +32 -0
- package/dist/features/auctions/hooks/useLiveAuctionBid.d.ts +20 -0
- package/dist/features/auctions/hooks/useLiveAuctionBid.js +23 -0
- package/dist/features/auth/checkout-value-otp.d.ts +7 -10
- package/dist/features/auth/checkout-value-otp.js +14 -10
- package/dist/features/auth/server.d.ts +0 -1
- package/dist/features/auth/server.js +0 -1
- package/dist/features/blog/api/[slug]/route.d.ts +5 -0
- package/dist/features/blog/api/[slug]/route.js +12 -3
- package/dist/features/blog/components/BlogPostView.d.ts +2 -0
- package/dist/features/blog/components/BlogPostView.js +5 -4
- package/dist/features/blog/hooks/useBlog.d.ts +2 -0
- package/dist/features/blog/hooks/useBlog.js +2 -0
- package/dist/features/blog/repository/blog.repository.d.ts +7 -0
- package/dist/features/blog/repository/blog.repository.js +44 -0
- package/dist/features/blog/schemas/firestore.d.ts +3 -0
- package/dist/features/blog/schemas/firestore.js +1 -0
- package/dist/features/blog/types/index.d.ts +2 -0
- package/dist/features/cart/components/index.d.ts +0 -2
- package/dist/features/cart/components/index.js +0 -1
- package/dist/features/cart/hooks/useCartCount.js +22 -5
- package/dist/features/cart/hooks/useGuestCart.js +11 -1
- package/dist/features/cart/utils/guest-cart.d.ts +4 -0
- package/dist/features/cart/utils/guest-cart.js +10 -0
- package/dist/features/cart/utils/pending-ops.d.ts +17 -0
- package/dist/features/cart/utils/pending-ops.js +46 -5
- package/dist/features/categories/components/BrandDetailPageView.js +6 -2
- package/dist/features/categories/components/BundleDetailView.js +4 -2
- package/dist/features/categories/components/CategoryDetailPageView.js +10 -2
- package/dist/features/categories/schemas/firestore.d.ts +7 -0
- package/dist/features/checkout/actions/index.d.ts +0 -1
- package/dist/features/checkout/actions/index.js +0 -1
- package/dist/features/checkout/schemas/firestore.d.ts +2 -2
- package/dist/features/events/actions/event-actions.d.ts +2 -0
- package/dist/features/events/actions/event-actions.js +5 -0
- package/dist/features/events/components/EventCard.js +1 -2
- package/dist/features/events/components/RelatedEventsCarousel.d.ts +15 -0
- package/dist/features/events/components/RelatedEventsCarousel.js +13 -0
- package/dist/features/events/components/index.d.ts +2 -0
- package/dist/features/events/components/index.js +1 -0
- package/dist/features/events/repository/events.repository.d.ts +6 -0
- package/dist/features/events/repository/events.repository.js +21 -0
- package/dist/features/history/repository/user-history.repository.d.ts +1 -1
- package/dist/features/history/utils/guest-history.d.ts +1 -1
- package/dist/features/homepage/components/FeaturedBundlesSection.js +4 -2
- package/dist/features/homepage/components/FeaturedProductsSection.js +2 -2
- package/dist/features/homepage/components/SectionCarousel.js +1 -1
- package/dist/features/homepage/components/WelcomeSection.js +1 -1
- package/dist/features/layout/AppLayoutShell.js +1 -1
- package/dist/features/layout/BackToTop.d.ts +9 -5
- package/dist/features/layout/BackToTop.js +17 -21
- package/dist/features/layout/NavbarLayout.js +1 -1
- package/dist/features/layout/TitleBarLayout.js +2 -2
- package/dist/features/media/MediaVideo.d.ts +7 -0
- package/dist/features/media/MediaVideo.js +36 -5
- package/dist/features/orders/actions/refund-actions.js +1 -0
- package/dist/features/orders/components/OrdersList.js +1 -5
- package/dist/features/orders/schemas/firestore.d.ts +16 -8
- package/dist/features/orders/schemas/index.d.ts +3 -32
- package/dist/features/orders/schemas/index.js +1 -4
- package/dist/features/orders/types/index.d.ts +6 -9
- package/dist/features/pre-orders/components/MarketplacePreorderCard.js +1 -1
- package/dist/features/pre-orders/components/PreOrderDetailPageView.js +2 -1
- package/dist/features/products/actions/product-actions.js +13 -10
- package/dist/features/products/components/MarketplaceBundleCard.d.ts +1 -1
- package/dist/features/products/components/MarketplaceBundleCard.js +7 -4
- package/dist/features/products/components/PrizeDrawDetailPageView.js +1 -1
- package/dist/features/products/components/PrizeDrawItemsEditor.js +2 -2
- package/dist/features/products/components/PrizeDrawWinnerMappingView.d.ts +14 -0
- package/dist/features/products/components/PrizeDrawWinnerMappingView.js +37 -0
- package/dist/features/products/components/PrizeRevealModal.d.ts +10 -23
- package/dist/features/products/components/PrizeRevealModal.js +18 -147
- package/dist/features/products/components/ProductDetailPageView.js +72 -30
- package/dist/features/products/components/ProductForm.js +7 -14
- package/dist/features/products/components/ProductGalleryClient.d.ts +10 -1
- package/dist/features/products/components/ProductGalleryClient.js +29 -10
- package/dist/features/products/components/ProductsIndexListing.js +2 -1
- package/dist/features/products/components/RelatedProductsCarousel.js +2 -2
- package/dist/features/products/components/ShowGroupSection.js +2 -2
- package/dist/features/products/components/SublistingCarouselSection.js +1 -1
- package/dist/features/products/components/index.d.ts +4 -2
- package/dist/features/products/components/index.js +1 -0
- package/dist/features/products/constants/action-defs.d.ts +28 -4
- package/dist/features/products/constants/action-defs.js +76 -3
- package/dist/features/products/repository/products.repository.d.ts +6 -0
- package/dist/features/products/repository/products.repository.js +20 -1
- package/dist/features/products/schemas/firestore.d.ts +26 -2
- package/dist/features/products/schemas/index.d.ts +11 -3
- package/dist/features/products/schemas/index.js +10 -2
- package/dist/features/products/types/index.d.ts +2 -1
- package/dist/features/reviews/actions/review-actions.js +1 -0
- package/dist/features/reviews/components/ReviewDetailPageView.js +8 -1
- package/dist/features/reviews/components/ReviewDetailShell.js +2 -1
- package/dist/features/reviews/components/ReviewsList.js +2 -1
- package/dist/features/reviews/schemas/firestore.d.ts +3 -0
- package/dist/features/reviews/schemas/index.d.ts +6 -0
- package/dist/features/reviews/schemas/index.js +2 -0
- package/dist/features/reviews/types/index.d.ts +3 -0
- package/dist/features/scams/actions/scam-actions.d.ts +3 -0
- package/dist/features/scams/actions/scam-actions.js +3 -2
- package/dist/features/scams/components/ScamProfileView.d.ts +2 -1
- package/dist/features/scams/components/ScamProfileView.js +2 -2
- package/dist/features/scams/repository/scammer.repository.d.ts +6 -0
- package/dist/features/scams/repository/scammer.repository.js +24 -0
- package/dist/features/seller/actions/seller-actions.js +8 -0
- package/dist/features/seller/components/SellerOrdersView.js +1 -1
- package/dist/features/seller/components/SellerPrizeDrawsView.js +4 -0
- package/dist/features/stores/components/StoreDetailLayoutView.js +6 -6
- package/dist/features/stores/components/StoreNavTabs.d.ts +10 -1
- package/dist/features/stores/components/StoreNavTabs.js +21 -9
- package/dist/features/tester/actions/index.d.ts +1 -0
- package/dist/features/tester/actions/index.js +1 -0
- package/dist/features/tester/actions/leaderboard-actions.d.ts +3 -0
- package/dist/features/tester/actions/leaderboard-actions.js +5 -0
- package/dist/features/tester/components/AdminTesterFeedbackIssuesView.js +26 -4
- package/dist/features/tester/components/AdminTesterFeedbackListView.js +13 -0
- package/dist/features/tester/components/BugHunterLeaderboardView.d.ts +23 -0
- package/dist/features/tester/components/BugHunterLeaderboardView.js +18 -0
- package/dist/features/tester/components/index.d.ts +2 -0
- package/dist/features/tester/components/index.js +1 -0
- package/dist/features/tester/repository/tester-checklist-item.repository.d.ts +12 -1
- package/dist/features/tester/repository/tester-checklist-item.repository.js +80 -0
- package/dist/features/tester/repository/tester-checklist-response.repository.d.ts +4 -0
- package/dist/features/tester/repository/tester-checklist-response.repository.js +4 -0
- package/dist/features/tester/schemas/firestore.d.ts +22 -1
- package/dist/features/tester/schemas/firestore.js +8 -0
- package/dist/features/tester/seed-data/categories-tester-seed-data.js +42 -7
- package/dist/features/tester/seed-data/events-tester-seed-data.js +23 -0
- package/dist/features/tester/seed-data/orders-tester-seed-data.js +69 -31
- package/dist/features/tester/seed-data/products-tester-seed-data.js +152 -8
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +249 -14
- package/dist/features/whatsapp-bot/helpers/whatsapp.d.ts +10 -1
- package/dist/features/whatsapp-bot/helpers/whatsapp.js +47 -0
- package/dist/features/whatsapp-bot/types/index.d.ts +19 -0
- package/dist/features/wishlist/hooks/useGuestWishlist.js +11 -1
- package/dist/features/wishlist/hooks/useWishlistCount.js +16 -2
- package/dist/features/wishlist/utils/guest-wishlist.d.ts +5 -0
- package/dist/features/wishlist/utils/guest-wishlist.js +11 -0
- package/dist/index.d.ts +23 -24
- package/dist/index.js +29 -60
- package/dist/jobs.d.ts +1 -1
- package/dist/jobs.js +2 -2
- package/dist/next/routing/route-map.d.ts +4 -0
- package/dist/next/routing/route-map.js +2 -0
- package/dist/repositories/index.d.ts +2 -0
- package/dist/repositories/index.js +1 -0
- package/dist/schemas/registry.d.ts +6 -26
- package/dist/seed/bids-seed-data.js +14 -2
- package/dist/seed/blog-posts-seed-data.js +2 -1
- package/dist/seed/categories-seed-data.js +11 -2
- package/dist/seed/events-seed-data.js +11 -11
- package/dist/seed/faq-seed-data.js +5 -1
- package/dist/seed/products-auctions-seed-data.js +2 -2
- package/dist/seed/products-preorders-seed-data.d.ts +2 -1
- package/dist/seed/products-prize-draws-seed-data.js +56 -10
- package/dist/seed/products-standard-seed-data.js +82 -3
- package/dist/seed/scammers-seed-data.js +75 -2
- package/dist/seed/site-settings-seed-data.js +12 -2
- package/dist/server-entry.d.ts +0 -1
- package/dist/server-entry.js +0 -1
- package/dist/server.d.ts +6 -22
- package/dist/server.js +12 -59
- package/dist/styles.css +1 -1
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/BaseListingCard.js +1 -1
- package/dist/ui/components/ImageLightbox.d.ts +9 -0
- package/dist/ui/components/ImageLightbox.js +6 -3
- package/dist/ui/components/ListingToolbar.d.ts +18 -1
- package/dist/ui/components/ListingToolbar.js +5 -2
- package/dist/ui/components/StickyToolbar.js +13 -8
- package/package.json +1 -1
|
@@ -5,6 +5,12 @@
|
|
|
5
5
|
* Written to localStorage so they survive page refreshes.
|
|
6
6
|
* The sync manager replays these and clears them after a successful push.
|
|
7
7
|
*/
|
|
8
|
+
/** Fired whenever the pending cart/wishlist op queue changes, so a badge
|
|
9
|
+
* reading server-authoritative counts (authenticated users) can layer the
|
|
10
|
+
* not-yet-synced local delta on top instead of waiting for the next
|
|
11
|
+
* useSyncManager interval (up to 30s) to reflect a just-added item. */
|
|
12
|
+
export declare const CART_OPS_CHANGE_EVENT = "appkit/cart/ops-changed";
|
|
13
|
+
export declare const WISHLIST_OPS_CHANGE_EVENT = "appkit/wishlist/ops-changed";
|
|
8
14
|
export type CartOpKind = "add" | "remove";
|
|
9
15
|
export type WishlistOpKind = "add" | "remove";
|
|
10
16
|
export interface CartOp {
|
|
@@ -34,6 +40,17 @@ export interface WishlistOp {
|
|
|
34
40
|
export declare function getCartOps(): CartOp[];
|
|
35
41
|
export declare function pushCartOp(op: Omit<CartOp, "ts">): void;
|
|
36
42
|
export declare function clearCartOps(): void;
|
|
43
|
+
/** Net quantity of unsynced "add" ops — layered on top of the server's
|
|
44
|
+
* authoritative itemCount so the header badge updates immediately instead
|
|
45
|
+
* of waiting for the next useSyncManager interval. Cart never queues
|
|
46
|
+
* "remove" ops (removal from an authenticated cart is always a synchronous
|
|
47
|
+
* API call), so summing "add" quantities is exact, not an approximation. */
|
|
48
|
+
export declare function getCartOpsDelta(): number;
|
|
37
49
|
export declare function getWishlistOps(): WishlistOp[];
|
|
38
50
|
export declare function pushWishlistOp(op: Omit<WishlistOp, "ts">): void;
|
|
51
|
+
/** Net count of unsynced wishlist ops — add/remove pairs for the same item
|
|
52
|
+
* are already collapsed by `pushWishlistOp`, so this is exact: +1 per
|
|
53
|
+
* queued "add", -1 per queued "remove" (an item already on the server
|
|
54
|
+
* being removed before the next sync). */
|
|
55
|
+
export declare function getWishlistOpsDelta(): number;
|
|
39
56
|
export declare function clearWishlistOps(): void;
|
|
@@ -11,6 +11,22 @@ const APP_ID = () => typeof process !== "undefined" && process.env.NEXT_PUBLIC_A
|
|
|
11
11
|
: "letitrip";
|
|
12
12
|
const CART_OPS_KEY = () => `${APP_ID()}_cart_ops`;
|
|
13
13
|
const WL_OPS_KEY = () => `${APP_ID()}_wl_ops`;
|
|
14
|
+
/** Fired whenever the pending cart/wishlist op queue changes, so a badge
|
|
15
|
+
* reading server-authoritative counts (authenticated users) can layer the
|
|
16
|
+
* not-yet-synced local delta on top instead of waiting for the next
|
|
17
|
+
* useSyncManager interval (up to 30s) to reflect a just-added item. */
|
|
18
|
+
export const CART_OPS_CHANGE_EVENT = "appkit/cart/ops-changed";
|
|
19
|
+
export const WISHLIST_OPS_CHANGE_EVENT = "appkit/wishlist/ops-changed";
|
|
20
|
+
function dispatchCartOpsChange() {
|
|
21
|
+
if (typeof window === "undefined")
|
|
22
|
+
return;
|
|
23
|
+
window.dispatchEvent(new Event(CART_OPS_CHANGE_EVENT));
|
|
24
|
+
}
|
|
25
|
+
function dispatchWishlistOpsChange() {
|
|
26
|
+
if (typeof window === "undefined")
|
|
27
|
+
return;
|
|
28
|
+
window.dispatchEvent(new Event(WISHLIST_OPS_CHANGE_EVENT));
|
|
29
|
+
}
|
|
14
30
|
// ---------------------------------------------------------------------------
|
|
15
31
|
// Storage helpers
|
|
16
32
|
// ---------------------------------------------------------------------------
|
|
@@ -40,6 +56,14 @@ function write(key, items) {
|
|
|
40
56
|
// storage quota exceeded — best effort
|
|
41
57
|
}
|
|
42
58
|
}
|
|
59
|
+
function writeCartOps(items) {
|
|
60
|
+
write(CART_OPS_KEY(), items);
|
|
61
|
+
dispatchCartOpsChange();
|
|
62
|
+
}
|
|
63
|
+
function writeWishlistOps(items) {
|
|
64
|
+
write(WL_OPS_KEY(), items);
|
|
65
|
+
dispatchWishlistOpsChange();
|
|
66
|
+
}
|
|
43
67
|
// ---------------------------------------------------------------------------
|
|
44
68
|
// Cart ops
|
|
45
69
|
// ---------------------------------------------------------------------------
|
|
@@ -53,7 +77,7 @@ export function pushCartOp(op) {
|
|
|
53
77
|
const withoutProduct = ops.filter((o) => o.productId !== op.productId);
|
|
54
78
|
if (withoutProduct.length < ops.length) {
|
|
55
79
|
// There was a pending add — cancelling
|
|
56
|
-
|
|
80
|
+
writeCartOps(withoutProduct);
|
|
57
81
|
return;
|
|
58
82
|
}
|
|
59
83
|
}
|
|
@@ -64,16 +88,25 @@ export function pushCartOp(op) {
|
|
|
64
88
|
const merged = ops.map((o) => o.op === "add" && o.productId === op.productId
|
|
65
89
|
? { ...o, quantity: Math.min((o.quantity ?? 1) + (op.quantity ?? 1), 99) }
|
|
66
90
|
: o);
|
|
67
|
-
|
|
91
|
+
writeCartOps(merged);
|
|
68
92
|
return;
|
|
69
93
|
}
|
|
70
94
|
}
|
|
71
|
-
|
|
95
|
+
writeCartOps([...ops, { ...op, ts: Date.now() }]);
|
|
72
96
|
}
|
|
73
97
|
export function clearCartOps() {
|
|
74
98
|
if (typeof window === "undefined")
|
|
75
99
|
return;
|
|
76
100
|
localStorage.removeItem(CART_OPS_KEY());
|
|
101
|
+
dispatchCartOpsChange();
|
|
102
|
+
}
|
|
103
|
+
/** Net quantity of unsynced "add" ops — layered on top of the server's
|
|
104
|
+
* authoritative itemCount so the header badge updates immediately instead
|
|
105
|
+
* of waiting for the next useSyncManager interval. Cart never queues
|
|
106
|
+
* "remove" ops (removal from an authenticated cart is always a synchronous
|
|
107
|
+
* API call), so summing "add" quantities is exact, not an approximation. */
|
|
108
|
+
export function getCartOpsDelta() {
|
|
109
|
+
return getCartOps().reduce((sum, op) => (op.op === "add" ? sum + (op.quantity ?? 1) : sum), 0);
|
|
77
110
|
}
|
|
78
111
|
// ---------------------------------------------------------------------------
|
|
79
112
|
// Wishlist ops
|
|
@@ -88,7 +121,7 @@ export function pushWishlistOp(op) {
|
|
|
88
121
|
if (op.op === "remove") {
|
|
89
122
|
const withoutItem = ops.filter((o) => key(o) !== key(op));
|
|
90
123
|
if (withoutItem.length < ops.length) {
|
|
91
|
-
|
|
124
|
+
writeWishlistOps(withoutItem);
|
|
92
125
|
return;
|
|
93
126
|
}
|
|
94
127
|
}
|
|
@@ -96,10 +129,18 @@ export function pushWishlistOp(op) {
|
|
|
96
129
|
if (op.op === "add" && ops.some((o) => o.op === "add" && key(o) === key(op))) {
|
|
97
130
|
return;
|
|
98
131
|
}
|
|
99
|
-
|
|
132
|
+
writeWishlistOps([...ops, { ...op, ts: Date.now() }]);
|
|
133
|
+
}
|
|
134
|
+
/** Net count of unsynced wishlist ops — add/remove pairs for the same item
|
|
135
|
+
* are already collapsed by `pushWishlistOp`, so this is exact: +1 per
|
|
136
|
+
* queued "add", -1 per queued "remove" (an item already on the server
|
|
137
|
+
* being removed before the next sync). */
|
|
138
|
+
export function getWishlistOpsDelta() {
|
|
139
|
+
return getWishlistOps().reduce((sum, op) => sum + (op.op === "add" ? 1 : -1), 0);
|
|
100
140
|
}
|
|
101
141
|
export function clearWishlistOps() {
|
|
102
142
|
if (typeof window === "undefined")
|
|
103
143
|
return;
|
|
104
144
|
localStorage.removeItem(WL_OPS_KEY());
|
|
145
|
+
dispatchWishlistOpsChange();
|
|
105
146
|
}
|
|
@@ -8,6 +8,7 @@ import { Container, Div, Heading, Main, Nav, Section, Span, Text } from "../../.
|
|
|
8
8
|
import { DynamicBgDiv } from "../../../ui/components/DynamicBgDiv";
|
|
9
9
|
import { MediaImage } from "../../media/MediaImage";
|
|
10
10
|
import { BrandDetailTabs } from "./BrandDetailTabs";
|
|
11
|
+
import { CategoryGrid } from "./CategoryGrid";
|
|
11
12
|
const __O = {
|
|
12
13
|
hidden: "overflow-hidden",
|
|
13
14
|
};
|
|
@@ -16,7 +17,7 @@ export async function BrandDetailPageView({ slug, initialBrand }) {
|
|
|
16
17
|
.getCategoryBySlug(slug)
|
|
17
18
|
.catch(() => undefined)));
|
|
18
19
|
const brandName = brand?.name;
|
|
19
|
-
const [productsResult, auctionsResult, preOrdersResult, prizeDrawsResult, allBundles] = await Promise.all([
|
|
20
|
+
const [productsResult, auctionsResult, preOrdersResult, prizeDrawsResult, allBundles, activeBrands] = await Promise.all([
|
|
20
21
|
brandName
|
|
21
22
|
? productRepository
|
|
22
23
|
.list({
|
|
@@ -65,6 +66,8 @@ export async function BrandDetailPageView({ slug, initialBrand }) {
|
|
|
65
66
|
.listByType("bundle", { activeOnly: true, limit: 50 })
|
|
66
67
|
.catch(() => [])
|
|
67
68
|
: Promise.resolve([]),
|
|
69
|
+
// Related brands — every other active brand row, excluding this one.
|
|
70
|
+
categoriesRepository.findActiveBrands().catch(() => []),
|
|
68
71
|
]);
|
|
69
72
|
const brandLower = brandName?.toLowerCase();
|
|
70
73
|
const brandBundles = brandLower
|
|
@@ -73,6 +76,7 @@ export async function BrandDetailPageView({ slug, initialBrand }) {
|
|
|
73
76
|
return seo.includes(brandLower);
|
|
74
77
|
})
|
|
75
78
|
: [];
|
|
79
|
+
const relatedBrands = activeBrands.filter((b) => b.id !== brand?.id);
|
|
76
80
|
const coverImage = brand?.display?.coverImage;
|
|
77
81
|
const hasCover = Boolean(coverImage);
|
|
78
82
|
// Brand accent — falls back to the primary theme token when the brand hasn't
|
|
@@ -91,5 +95,5 @@ export async function BrandDetailPageView({ slug, initialBrand }) {
|
|
|
91
95
|
counts.preOrders +
|
|
92
96
|
counts.prizeDraws +
|
|
93
97
|
counts.bundles;
|
|
94
|
-
return (_jsxs(Main, { children: [_jsxs(Section, { className: `relative ${__O.hidden} ${hasCover ? "min-h-[220px] md:min-h-[280px]" : "bg-[var(--appkit-color-bg)]"}`, children: [hasCover && (_jsxs(_Fragment, { children: [_jsx(Div, { className: "absolute inset-0", children: _jsx(MediaImage, { src: coverImage, alt: "", size: "banner" }) }), _jsx(Div, { surface: "overlay-md", className: "absolute inset-0" })] })), !hasCover && (_jsx(DynamicBgDiv, { color: brandColor, className: "absolute inset-0 opacity-10" })), _jsxs(Div, { className: "relative z-10 max-w-7xl mx-auto", paddingY: hasCover ? "y-3xl" : "y-xl", padding: "x-md", children: [_jsxs(Nav, { layout: "flex", gap: "2xs", textSize: "sm", className: "mb-4", "aria-label": "Breadcrumb", children: [_jsx(Link, { href: String(ROUTES.HOME), className: hasCover ? "text-white/70 hover:text-white transition-colors" : "text-[var(--appkit-color-text-muted)] hover:text-primary-600 transition-colors", children: "Home" }), _jsx(Span, { className: hasCover ? "text-white/40" : "text-zinc-400", children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.BRANDS), className: hasCover ? "text-white/70 hover:text-white transition-colors" : "text-[var(--appkit-color-text-muted)] hover:text-primary-600 transition-colors", children: "Brands" }), _jsx(Span, { className: hasCover ? "text-white/40" : "text-zinc-400", children: "/" }), _jsx(Span, { weight: "medium", className: hasCover ? "text-white" : "text-[var(--appkit-color-text)]", children: brand?.name ?? slug })] }), _jsxs(Row, { className: "mb-3", align: "center", gap: "md", children: [brand?.display?.icon && (_jsx(Span, { className: "leading-none", size: "5xl", children: brand.display.icon })), _jsxs(_Fragment, { children: [_jsx(Heading, { color: "inverse", level: 1, className: `${hasCover ? "" : "text-[var(--appkit-color-text)]"}`, size: "3xl", mdSize: "4xl", weight: "bold", children: brand?.name ?? slug }), brand?.description && typeof brand.description === "string" && !brand.description.startsWith("{") && (_jsx(Text, { color: hasCover ? "inverse" : "muted", className: `max-w-2xl mt-1 ${hasCover ? "/80" : ""}`, size: "base", children: brand.description }))] })] }), _jsxs(Row, { gap: "sm", wrap: true, className: "mt-3", children: [counts.products > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: "inverse", size: "xs", weight: "medium", className: `${hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "bg-primary/10 text-primary-700 dark:text-primary-400"}`, rounded: "full", padding: "pill-sm-tall", children: [counts.products.toLocaleString(), " ", counts.products === 1 ? "product" : "products"] })), counts.auctions > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: hasCover ? "inverse" : "warning", surface: hasCover ? undefined : "warning-surface", size: "xs", weight: "medium", className: hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "", rounded: "full", padding: "pill-sm-tall", children: [counts.auctions.toLocaleString(), " ", counts.auctions === 1 ? "auction" : "auctions"] })), counts.preOrders > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: hasCover ? "inverse" : "info", surface: hasCover ? undefined : "info-surface", size: "xs", weight: "medium", className: hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "", rounded: "full", padding: "pill-sm-tall", children: [counts.preOrders.toLocaleString(), " ", counts.preOrders === 1 ? "pre-order" : "pre-orders"] })), totalItems === 0 && (_jsx(Span, { size: "sm", className: hasCover ? "text-white/60" : "text-zinc-400", children: "No items listed yet" }))] })] })] }), _jsx(Section, { padding: "y-lg", children: _jsx(Container, { size: "xl", children: brandName ? (_jsx(BrandDetailTabs, { brandName: brandName, initialProductsData: productsResult ?? undefined, initialBundles: brandBundles, counts: counts })) : (_jsx(Text, { paddingY: "3xl", color: "muted", size: "sm", align: "start", children: "Brand not found." })) }) })] }));
|
|
98
|
+
return (_jsxs(Main, { children: [_jsxs(Section, { className: `relative ${__O.hidden} ${hasCover ? "min-h-[220px] md:min-h-[280px]" : "bg-[var(--appkit-color-bg)]"}`, children: [hasCover && (_jsxs(_Fragment, { children: [_jsx(Div, { className: "absolute inset-0", children: _jsx(MediaImage, { src: coverImage, alt: "", size: "banner" }) }), _jsx(Div, { surface: "overlay-md", className: "absolute inset-0" })] })), !hasCover && (_jsx(DynamicBgDiv, { color: brandColor, className: "absolute inset-0 opacity-10" })), _jsxs(Div, { className: "relative z-10 max-w-7xl mx-auto", paddingY: hasCover ? "y-3xl" : "y-xl", padding: "x-md", children: [_jsxs(Nav, { layout: "flex", gap: "2xs", textSize: "sm", className: "mb-4", "aria-label": "Breadcrumb", children: [_jsx(Link, { href: String(ROUTES.HOME), className: hasCover ? "text-white/70 hover:text-white transition-colors" : "text-[var(--appkit-color-text-muted)] hover:text-primary-600 transition-colors", children: "Home" }), _jsx(Span, { className: hasCover ? "text-white/40" : "text-zinc-400", children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.BRANDS), className: hasCover ? "text-white/70 hover:text-white transition-colors" : "text-[var(--appkit-color-text-muted)] hover:text-primary-600 transition-colors", children: "Brands" }), _jsx(Span, { className: hasCover ? "text-white/40" : "text-zinc-400", children: "/" }), _jsx(Span, { weight: "medium", className: hasCover ? "text-white" : "text-[var(--appkit-color-text)]", children: brand?.name ?? slug })] }), _jsxs(Row, { className: "mb-3", align: "center", gap: "md", children: [brand?.display?.icon && (_jsx(Span, { className: "leading-none", size: "5xl", children: brand.display.icon })), _jsxs(_Fragment, { children: [_jsx(Heading, { color: "inverse", level: 1, className: `${hasCover ? "" : "text-[var(--appkit-color-text)]"}`, size: "3xl", mdSize: "4xl", weight: "bold", children: brand?.name ?? slug }), brand?.description && typeof brand.description === "string" && !brand.description.startsWith("{") && (_jsx(Text, { color: hasCover ? "inverse" : "muted", className: `max-w-2xl mt-1 ${hasCover ? "/80" : ""}`, size: "base", children: brand.description }))] })] }), _jsxs(Row, { gap: "sm", wrap: true, className: "mt-3", children: [counts.products > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: "inverse", size: "xs", weight: "medium", className: `${hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "bg-primary/10 text-primary-700 dark:text-primary-400"}`, rounded: "full", padding: "pill-sm-tall", children: [counts.products.toLocaleString(), " ", counts.products === 1 ? "product" : "products"] })), counts.auctions > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: hasCover ? "inverse" : "warning", surface: hasCover ? undefined : "warning-surface", size: "xs", weight: "medium", className: hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "", rounded: "full", padding: "pill-sm-tall", children: [counts.auctions.toLocaleString(), " ", counts.auctions === 1 ? "auction" : "auctions"] })), counts.preOrders > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: hasCover ? "inverse" : "info", surface: hasCover ? undefined : "info-surface", size: "xs", weight: "medium", className: hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "", rounded: "full", padding: "pill-sm-tall", children: [counts.preOrders.toLocaleString(), " ", counts.preOrders === 1 ? "pre-order" : "pre-orders"] })), totalItems === 0 && (_jsx(Span, { size: "sm", className: hasCover ? "text-white/60" : "text-zinc-400", children: "No items listed yet" }))] })] })] }), _jsx(Section, { padding: "y-lg", children: _jsx(Container, { size: "xl", children: brandName ? (_jsx(BrandDetailTabs, { brandName: brandName, initialProductsData: productsResult ?? undefined, initialBundles: brandBundles, counts: counts })) : (_jsx(Text, { paddingY: "3xl", color: "muted", size: "sm", align: "start", children: "Brand not found." })) }) }), relatedBrands.length > 0 && (_jsx(Section, { border: "subtle", surface: "default", className: "border-t", padding: "y-lg", children: _jsxs(Container, { size: "xl", children: [_jsx(Heading, { level: 2, className: "mb-4", size: "xl", weight: "semibold", children: "Related Brands" }), _jsx(CategoryGrid, { categories: relatedBrands, getHref: (b) => String(ROUTES.PUBLIC.BRAND_DETAIL(b.slug)) })] }) }))] }));
|
|
95
99
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Badge, Container, Div, Heading, Li, Main, Row, Section, Span, Stack, Text, Ul, } from "../../../ui";
|
|
3
3
|
import { formatCurrency } from "../../../utils/number.formatter";
|
|
4
4
|
import Link from "next/link";
|
|
@@ -7,6 +7,7 @@ import { BundleBuyNowCta } from "./BundleBuyNowCta";
|
|
|
7
7
|
import { BundleCollage } from "./BundleCollage";
|
|
8
8
|
import { MediaImage } from "../../media/MediaImage";
|
|
9
9
|
import { ROUTES } from "../../../next/routing/route-map";
|
|
10
|
+
import { computeBundleDiscount } from "../../../_internal/shared/features/categories/bundle-pricing";
|
|
10
11
|
const __O = {
|
|
11
12
|
hidden: "overflow-hidden",
|
|
12
13
|
};
|
|
@@ -22,5 +23,6 @@ export function BundleDetailView({ bundle, members = [], onBuyNow, }) {
|
|
|
22
23
|
const priceLabel = bundle.bundlePrice
|
|
23
24
|
? formatCurrency(bundle.bundlePrice, "INR")
|
|
24
25
|
: BUNDLE_COPY.detail.priceFallback;
|
|
25
|
-
|
|
26
|
+
const discount = computeBundleDiscount(bundle.bundlePrice, bundle.bundleOriginalTotal);
|
|
27
|
+
return (_jsx(Main, { children: _jsx(Section, { padding: "y-2xl", children: _jsx(Container, { size: "xl", children: _jsxs(Stack, { gap: "lg", children: [_jsxs(Stack, { gap: "lg", direction: "md-row", align: "start", children: [_jsx(Div, { className: "w-full md:w-2/5", children: members.length > 0 ? (_jsx(BundleCollage, { members: members })) : (_jsx(Div, { className: `aspect-video w-full ${__O.hidden}`, rounded: "2xl", surface: "subtle", children: _jsx(Div, { className: "h-full w-full", children: _jsx(MediaImage, { src: cover, alt: bundle.name, size: "hero", fallback: PLACEHOLDER_EMOJI }) }) })) }), _jsxs(Stack, { gap: "md", className: "flex-1", children: [_jsx(Heading, { level: 1, size: "3xl", weight: "semibold", color: "primary", children: bundle.name }), _jsxs(Row, { gap: "sm", align: "center", wrap: true, children: [_jsx(Text, { size: "lg", weight: "bold", children: priceLabel }), discount && (_jsxs(_Fragment, { children: [_jsx(Text, { size: "sm", color: "muted", className: "line-through", children: formatCurrency(discount.originalTotal, "INR") }), _jsx(Badge, { variant: "success", children: BUNDLE_COPY.detail.discountBadge(discount.percent) })] })), _jsxs(Text, { size: "sm", color: "muted", children: ["\u00B7 ", BUNDLE_COPY.detail.itemCount(memberCount)] }), _jsx(Badge, { variant: BUNDLE_STOCK_VARIANT[stock], children: STOCK_BADGE_TEXT[stock] })] }), discount && (_jsx(Text, { size: "sm", color: "success", weight: "medium", children: BUNDLE_COPY.detail.savingsLabel(formatCurrency(discount.savings, "INR")) })), bundle.description && (_jsxs(Stack, { gap: "xs", children: [_jsx(Heading, { level: 2, className: "text-[var(--appkit-color-text-muted)]", size: "sm", weight: "semibold", children: BUNDLE_COPY.detailDescriptionHeading }), _jsx(Text, { className: "whitespace-pre-line", children: bundle.description })] })), onBuyNow ? (_jsx(BundleBuyNowCta, { bundleSlug: bundle.slug, outOfStock: stock === "out_of_stock", onBuyNow: onBuyNow })) : (_jsxs(Div, { textSize: "sm", className: "border-dashed", border: "strong", surface: "muted", rounded: "xl", padding: "md", "aria-live": "polite", children: [_jsx(Text, { weight: "semibold", className: "mb-1 block", children: BUNDLE_COPY.detail.ctaDisabled }), _jsx(Text, { size: "sm", color: "muted", children: BUNDLE_COPY.detail.ctaHint })] }))] })] }), _jsxs(Stack, { gap: "md", children: [_jsx(Heading, { level: 2, size: "xl", weight: "semibold", color: "primary", children: BUNDLE_COPY.detailItemsHeading }), members.length === 0 ? (_jsx(Text, { color: "muted", children: BUNDLE_COPY.detail.emptyMembers })) : (_jsx(Ul, { gap: "2", className: "grid grid-cols-1 sm:grid-cols-2", children: members.map((p, i) => (_jsxs(Li, { layout: "flex-center", gap: "2", rounded: "lg", border: "subtle", padding: "inlineSm", textSize: "sm", children: [_jsxs(Span, { size: "xs", weight: "semibold", className: "w-5", color: "faint", children: ["#", i + 1] }), _jsx(Link, { href: String(ROUTES.PUBLIC.PRODUCT_DETAIL?.(p.slug ?? p.id) ?? "#"), className: "flex-1 truncate font-medium hover:underline hover:text-primary", children: p.title }), _jsx(Span, { size: "xs", color: "muted", children: formatCurrency(p.price ?? 0, p.currency ?? "INR") })] }, p.id))) }))] })] }) }) }) }));
|
|
26
28
|
}
|
|
@@ -7,6 +7,7 @@ import { ROUTES } from "../../../next";
|
|
|
7
7
|
import { Container, Div, Heading, Main, Nav, Section, Span, Text } from "../../../ui";
|
|
8
8
|
import { MediaImage } from "../../media/MediaImage";
|
|
9
9
|
import { CategoryDetailTabs } from "./CategoryDetailTabs";
|
|
10
|
+
import { CategoryGrid } from "./CategoryGrid";
|
|
10
11
|
const __O = {
|
|
11
12
|
hidden: "overflow-hidden",
|
|
12
13
|
xAuto: "overflow-x-auto",
|
|
@@ -18,7 +19,7 @@ export async function CategoryDetailPageView({ slug }) {
|
|
|
18
19
|
// Use categorySlugs@=<id> (array-contains) — products store category FK in
|
|
19
20
|
// the categorySlugs[] array, not the legacy `category` string field.
|
|
20
21
|
const catFilter = category?.id ? sieveFilter("categorySlugs", SIEVE_OP.CONTAINS, category.id) : null;
|
|
21
|
-
const [productsResult, auctionsCountResult, preOrdersCountResult, prizeDrawsCountResult, bundlesResult, childCategories] = await Promise.all([
|
|
22
|
+
const [productsResult, auctionsCountResult, preOrdersCountResult, prizeDrawsCountResult, bundlesResult, childCategories, rootSiblingCategories] = await Promise.all([
|
|
22
23
|
catFilter
|
|
23
24
|
? productRepository
|
|
24
25
|
.list({
|
|
@@ -69,7 +70,14 @@ export async function CategoryDetailPageView({ slug }) {
|
|
|
69
70
|
category?.id
|
|
70
71
|
? categoriesRepository.getChildren(category.id).catch(() => [])
|
|
71
72
|
: Promise.resolve([]),
|
|
73
|
+
// Related categories — every other category sharing this category's root
|
|
74
|
+
// (siblings + cousins across the tree, up to the tier-0/1/2 depth the
|
|
75
|
+
// catalog actually uses), not just direct children.
|
|
76
|
+
category?.rootId
|
|
77
|
+
? categoriesRepository.getCategoriesByRootId(category.rootId).catch(() => [])
|
|
78
|
+
: Promise.resolve([]),
|
|
72
79
|
]);
|
|
80
|
+
const relatedCategories = rootSiblingCategories.filter((c) => c.id !== category?.id && (!c.categoryType || c.categoryType === "category"));
|
|
73
81
|
// Stores tab — query stores whose storeCategory matches this category or any child
|
|
74
82
|
const storeCategorySlugs = [
|
|
75
83
|
slug,
|
|
@@ -121,5 +129,5 @@ export async function CategoryDetailPageView({ slug }) {
|
|
|
121
129
|
prizeDraws: prizeDrawCount,
|
|
122
130
|
bundles: bundleCount,
|
|
123
131
|
stores: storeCount,
|
|
124
|
-
} }) }) })] }));
|
|
132
|
+
} }) }) }), relatedCategories.length > 0 && (_jsx(Section, { border: "subtle", surface: "default", className: "border-t", padding: "y-lg", children: _jsxs(Container, { size: "xl", children: [_jsx(Heading, { level: 2, className: "mb-4", size: "xl", weight: "semibold", children: "Related Categories" }), _jsx(CategoryGrid, { categories: relatedCategories, getHref: (c) => String(ROUTES.PUBLIC.CATEGORY_DETAIL(c.slug)) })] }) }))] }));
|
|
125
133
|
}
|
|
@@ -145,6 +145,13 @@ export interface CategoryDocument extends BaseDocument {
|
|
|
145
145
|
bundleQueryResolvedAt?: Date;
|
|
146
146
|
/** Hand-picked products list (mirror of bundleQueryRule for static rules); kept for index-friendly queries. */
|
|
147
147
|
bundleProductIds?: string[];
|
|
148
|
+
/**
|
|
149
|
+
* Sum of member products' individual prices (decimal rupees) — the "buy
|
|
150
|
+
* separately" total the discount badge is measured against. Denormalised
|
|
151
|
+
* at bundle create/update time and refreshed by the daily bundle stock
|
|
152
|
+
* sync job; undefined when a member price couldn't be resolved.
|
|
153
|
+
*/
|
|
154
|
+
bundleOriginalTotal?: number;
|
|
148
155
|
/**
|
|
149
156
|
* Per-member metadata parallel to `bundleProductIds`.
|
|
150
157
|
* Carries `drawCount` for prize-draw members (how many raffle entries
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Checkout Feature Firestore Document Types & Constants
|
|
3
3
|
* Covers: failed checkouts, failed payments
|
|
4
4
|
*/
|
|
5
|
-
export type FailedCheckoutReason = "
|
|
6
|
-
export type FailedPaymentReason = "signature_mismatch" | "payment_cancelled" | "amount_mismatch" | "
|
|
5
|
+
export type FailedCheckoutReason = "stock_failed" | "address_not_found" | "cart_empty" | "validation_error" | "unknown";
|
|
6
|
+
export type FailedPaymentReason = "signature_mismatch" | "payment_cancelled" | "amount_mismatch" | "product_unavailable" | "stock_insufficient" | "unknown";
|
|
7
7
|
export type FailedCheckoutDocument = {
|
|
8
8
|
id: string;
|
|
9
9
|
uid: string;
|
|
@@ -66,6 +66,8 @@ export declare function listPublicEvents(params?: string | {
|
|
|
66
66
|
}): Promise<FirebaseSieveResult<EventDocument>>;
|
|
67
67
|
export declare function getPublicEventById(id: string): Promise<EventDocument | null>;
|
|
68
68
|
export declare function getEventLeaderboard(eventId: string): Promise<import("../types").LeaderboardEntry[]>;
|
|
69
|
+
/** Related events — other active events sharing at least one tag, excluding this one. */
|
|
70
|
+
export declare function getRelatedEvents(tags: string[], excludeId: string, limit?: number): Promise<EventDocument[]>;
|
|
69
71
|
export declare function adminListEvents(params?: {
|
|
70
72
|
filters?: string;
|
|
71
73
|
sorts?: string;
|
|
@@ -148,6 +148,11 @@ export async function getEventLeaderboard(eventId) {
|
|
|
148
148
|
return [];
|
|
149
149
|
return eventEntryRepository.getLeaderboard(event.id);
|
|
150
150
|
}
|
|
151
|
+
/** Related events — other active events sharing at least one tag, excluding this one. */
|
|
152
|
+
export async function getRelatedEvents(tags, excludeId, limit = 6) {
|
|
153
|
+
const docs = await eventRepository.findByTagsOverlap(tags, limit + 1);
|
|
154
|
+
return docs.filter((e) => e.id !== excludeId).slice(0, limit);
|
|
155
|
+
}
|
|
151
156
|
export async function adminListEvents(params) {
|
|
152
157
|
const sieve = {
|
|
153
158
|
filters: params?.filters,
|
|
@@ -3,7 +3,6 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import Link from "next/link";
|
|
4
4
|
import { Article, BaseListingCard, Button, Div, Heading, RichText, Row, Span, Stack, TextLink } from "../../../ui";
|
|
5
5
|
import { MediaImage } from "../../media/MediaImage";
|
|
6
|
-
import { LAYOUT } from "../../../tokens";
|
|
7
6
|
import { normalizeRichTextHtml } from "../../../utils/string.formatter";
|
|
8
7
|
import { EVENT_FIELDS } from "../schemas";
|
|
9
8
|
import { EventStatusBadge } from "./EventStatusBadge";
|
|
@@ -33,6 +32,6 @@ export function EventCard({ event, labels = {}, onParticipate, className = "", s
|
|
|
33
32
|
const msLeft = endsAt.getTime() - now.getTime();
|
|
34
33
|
const daysLeft = Math.max(0, Math.ceil(msLeft / (1000 * 60 * 60 * 24)));
|
|
35
34
|
const detailHref = String(ROUTES.PUBLIC.EVENT_DETAIL(event.slug ?? event.id));
|
|
36
|
-
return (_jsxs(Article, { border: "default", rounded: "xl", shadow: "hover-md", className: `group relative h-full
|
|
35
|
+
return (_jsxs(Article, { border: "default", rounded: "xl", shadow: "hover-md", className: `group relative flex h-full flex-col overflow-hidden bg-[var(--appkit-color-surface)] ${isSelected ? "border-primary outline outline-2 outline-primary" : " "} ${className}`, onMouseDown: onSelect && !isSelected ? longPress.onMouseDown : undefined, onMouseUp: onSelect && !isSelected ? longPress.onMouseUp : undefined, onMouseLeave: onSelect && !isSelected ? longPress.onMouseLeave : undefined, onTouchStart: onSelect && !isSelected ? longPress.onTouchStart : undefined, onTouchEnd: onSelect && !isSelected ? longPress.onTouchEnd : undefined, onTouchCancel: onSelect && !isSelected ? longPress.onTouchCancel : undefined, children: [onSelect && (_jsx(BaseListingCard.Checkbox, { selected: isSelected, onSelect: (e) => { e.preventDefault(); onSelect(event.id, !isSelected); }, label: isSelected ? "Deselect event" : "Select event", position: "top-2 left-2", className: selectable || isSelected ? "opacity-100" : "opacity-0 group-hover:opacity-100 transition-opacity" })), _jsx(Link, { href: detailHref, className: "block flex-shrink-0", children: event.coverImageUrl || event.coverImage?.url ? (_jsx(Div, { className: `relative aspect-video ${__O.hidden}`, children: _jsx(MediaImage, { src: event.coverImageUrl || event.coverImage?.url || "", alt: safeTitle, size: "card", className: "transition-transform duration-300 group-hover:scale-105" }) })) : (_jsx(Row, { surface: "muted", className: "aspect-video", align: "center", justify: "center", children: _jsx(Span, { className: "opacity-40", size: "5xl", "aria-hidden": "true", children: TYPE_ICONS[event.type] }) })) }), _jsxs(Stack, { className: `flex-1 ${__P.p4}`, children: [_jsxs(Row, { className: "mb-2", align: "start", justify: "between", gap: "sm", children: [_jsx(Span, { size: "lg", "aria-hidden": "true", children: TYPE_ICONS[event.type] }), _jsx(EventStatusBadge, { status: event.status })] }), _jsx(Link, { href: detailHref, className: "block", children: _jsx(Heading, { level: 3, className: "text-[var(--appkit-color-text)] leading-snug mb-1 group-hover:text-primary transition-colors", size: "base", weight: "semibold", children: safeTitle }) }), _jsx(RichText, { html: normalizeRichTextHtml(event.description ?? ""), proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "mb-3 line-clamp-3 text-[length:var(--appkit-text-sm)] text-[var(--appkit-color-text-muted)]" }), _jsxs(Row, { color: "muted", textSize: "xs", className: "mb-3 mt-auto", align: "center", justify: "between", children: [event.status === EVENT_FIELDS.STATUS_VALUES.ACTIVE &&
|
|
37
36
|
daysLeft > 0 && _jsxs(Span, { children: ["\u23F1 ", daysLeft, "d remaining"] }), _jsxs(Span, { children: ["\uD83D\uDC65 ", event.stats.totalEntries, " ", labels.entries ?? "entries"] })] }), event.status === EVENT_FIELDS.STATUS_VALUES.ACTIVE && onParticipate ? (_jsx(Button, { rounded: "lg", type: "button", onClick: () => onParticipate(event), className: "w-full bg-primary py-[var(--appkit-space-2)] text-[length:var(--appkit-text-sm)] font-medium text-white transition-colors hover:bg-primary-600", children: labels.participate ?? "Participate" })) : (_jsxs(TextLink, { rounded: "lg", paddingX: "sm", paddingY: "xs", href: detailHref, layout: "inline-flex", align: "center", justify: "center", gap: "xs", className: "w-full border border-[var(--appkit-color-border)] transition-colors hover:bg-[var(--appkit-color-bg)]", color: "primary", size: "sm", weight: "medium", children: [labels.viewDetails ?? "View details", " \u2192"] }))] })] }));
|
|
38
37
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { MediaField } from "../../media/types";
|
|
3
|
+
export interface RelatedEventItem {
|
|
4
|
+
id: string;
|
|
5
|
+
slug?: string;
|
|
6
|
+
title: string;
|
|
7
|
+
coverImage?: MediaField | null;
|
|
8
|
+
coverImageUrl?: string;
|
|
9
|
+
}
|
|
10
|
+
interface RelatedEventsCarouselProps {
|
|
11
|
+
items: RelatedEventItem[];
|
|
12
|
+
title?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function RelatedEventsCarousel({ items, title, }: RelatedEventsCarouselProps): React.JSX.Element | null;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import Link from "next/link";
|
|
4
|
+
import { ROUTES } from "../../../next";
|
|
5
|
+
import { SectionCarousel } from "../../homepage/components/SectionCarousel";
|
|
6
|
+
import { MediaImage } from "../../media/MediaImage";
|
|
7
|
+
import { Div, Heading, Stack } from "../../../ui";
|
|
8
|
+
import { CAROUSEL_PER_VIEW } from "../../homepage/constants/carousel-per-view";
|
|
9
|
+
export function RelatedEventsCarousel({ items, title = "Related Events", }) {
|
|
10
|
+
if (items.length === 0)
|
|
11
|
+
return null;
|
|
12
|
+
return (_jsx(SectionCarousel, { title: title, pillLabel: "Related Events", headingVariant: "editorial", items: items, isLoading: false, perView: CAROUSEL_PER_VIEW.standard, keyExtractor: (item) => item.id, renderItem: (item) => (_jsx(Link, { href: String(ROUTES.PUBLIC.EVENT_DETAIL(item.slug || item.id)), className: "block h-full", children: _jsxs(Stack, { className: "h-full", surface: "default", rounded: "xl", border: "default", overflow: "hidden", children: [_jsx(Div, { surface: "muted", overflow: "hidden", className: "relative aspect-[4/3] w-full flex-shrink-0", children: (item.coverImage?.url || item.coverImageUrl) && (_jsx(MediaImage, { src: (item.coverImage?.url || item.coverImageUrl), alt: item.title, size: "card" })) }), _jsx(Heading, { level: 3, className: "p-[var(--appkit-space-3)]", truncate: 2, size: "sm", weight: "semibold", children: item.title })] }) })) }));
|
|
13
|
+
}
|
|
@@ -2,6 +2,8 @@ export { EventStatusBadge } from "./EventStatusBadge";
|
|
|
2
2
|
export { EventFilters, EVENT_SORT_OPTIONS, EVENT_FILTER_KEYS, EVENT_ADMIN_SORT_OPTIONS, EVENT_SELLER_SORT_OPTIONS, EVENT_PUBLIC_SORT_OPTIONS, getEventFilterKeys, getEventSortOptions, } from "./EventFilters";
|
|
3
3
|
export type { EventFiltersProps, EventFilterVariant } from "./EventFilters";
|
|
4
4
|
export { EventCard } from "./EventCard";
|
|
5
|
+
export { RelatedEventsCarousel } from "./RelatedEventsCarousel";
|
|
6
|
+
export type { RelatedEventItem } from "./RelatedEventsCarousel";
|
|
5
7
|
export { EventsListView } from "./EventsListView";
|
|
6
8
|
export { EventsIndexListing } from "./EventsIndexListing";
|
|
7
9
|
export type { EventsIndexListingProps } from "./EventsIndexListing";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { EventStatusBadge } from "./EventStatusBadge";
|
|
2
2
|
export { EventFilters, EVENT_SORT_OPTIONS, EVENT_FILTER_KEYS, EVENT_ADMIN_SORT_OPTIONS, EVENT_SELLER_SORT_OPTIONS, EVENT_PUBLIC_SORT_OPTIONS, getEventFilterKeys, getEventSortOptions, } from "./EventFilters";
|
|
3
3
|
export { EventCard } from "./EventCard";
|
|
4
|
+
export { RelatedEventsCarousel } from "./RelatedEventsCarousel";
|
|
4
5
|
export { EventsListView } from "./EventsListView";
|
|
5
6
|
export { EventsIndexListing } from "./EventsIndexListing";
|
|
6
7
|
export { EventDetailView } from "./EventDetailView";
|
|
@@ -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
|
|
@@ -7,11 +7,15 @@ export interface BackToTopProps {
|
|
|
7
7
|
/**
|
|
8
8
|
* BackToTop — floating scroll-to-top button.
|
|
9
9
|
* Appears after the page has scrolled past `threshold` pixels. A small
|
|
10
|
-
* dismiss control
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
10
|
+
* dismiss control hides it for the current page view only — dismissal is
|
|
11
|
+
* plain in-memory component state, reset on every route change (via
|
|
12
|
+
* `usePathname()`) so navigating anywhere brings it back. Deliberately NOT
|
|
13
|
+
* persisted to storage: an earlier version wrote the dismissal to
|
|
14
|
+
* `sessionStorage`, and because this component is mounted once inside the
|
|
15
|
+
* persistent `AppLayoutShell` (it never remounts on client-side navigation),
|
|
16
|
+
* that meant one click hid the button for the rest of the browser tab
|
|
17
|
+
* session with no way to bring it back — reported as "the button is gone
|
|
18
|
+
* forever." See CLAUDE.md Recurrent Root Cause Patterns.
|
|
15
19
|
*/
|
|
16
20
|
export declare function BackToTop({ threshold, ariaLabel, className, }: BackToTopProps): import("react").JSX.Element | null;
|
|
17
21
|
/** SkipToMain — visually hidden link that becomes visible on focus for keyboard users. */
|