@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
|
@@ -69,6 +69,12 @@ const rawTesterChecklistItems = [
|
|
|
69
69
|
{ key: "avatar-upload", label: "Uploading a profile avatar works" },
|
|
70
70
|
{ key: "notification-prefs", label: "Notification preferences save correctly" },
|
|
71
71
|
{ key: "public-profile-toggle", label: "Public profile visibility toggle works" },
|
|
72
|
+
{
|
|
73
|
+
key: "own-public-profile-quick-links",
|
|
74
|
+
label: "\"View public profile\" is easy to find and works from three places: the My Account dashboard header, the My Account quick-links grid (\"My Public Profile\" tile), and the /user/profile page (next to \"Manage Addresses\")",
|
|
75
|
+
description: "Each of the three links should open your own public-facing profile page (/profile/[your uid]) — not the edit-profile page. If your profile visibility is set to Private, confirm the page still loads for you (the owner) even though other users would get a 404.",
|
|
76
|
+
href: "/user",
|
|
77
|
+
},
|
|
72
78
|
],
|
|
73
79
|
},
|
|
74
80
|
{
|
|
@@ -92,6 +98,18 @@ const rawTesterChecklistItems = [
|
|
|
92
98
|
{ key: "search", label: "Search returns relevant results", href: "/search" },
|
|
93
99
|
{ key: "filters", label: "Search/listing filters (price, brand, condition) work correctly" },
|
|
94
100
|
{ key: "pagination", label: "Pagination / infinite scroll works on listing pages" },
|
|
101
|
+
{
|
|
102
|
+
key: "default-listing-not-empty",
|
|
103
|
+
label: "Products, Auctions, Pre-Orders, Art & Stickers, and Prize Draws listing pages show real items by default — not an empty \"no products found\" state or a toast about a missing Firestore index",
|
|
104
|
+
description: "A 2026-08-19 index-shape bug (2-field composite left in place of the 3-field one the actual sort-inclusive query needed) silently broke the default \"hide sold/ended/closed\" query on several listing pages and dashboards, returning zero results with a swallowed FAILED_PRECONDITION error. Load each listing page fresh (no filters applied) and confirm it's populated, not blank.",
|
|
105
|
+
href: "/products",
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
key: "show-sold-toggle-reveals-items",
|
|
109
|
+
label: "The \"Show sold\" / \"Show ended\" / \"Show closed\" toggle on Products/Auctions/Prize Draws listing pages is off by default (hiding sold-out/ended/closed items) and reveals them when switched on",
|
|
110
|
+
description: "Verify against the seeded fixtures: \"Test Collectible — Sold Out\" (standard, hidden until \"Show sold\" is on), \"Test Auction — Already Won\" (hidden until \"Show ended\" is on), \"Test Prize Draw — Already Closed\" (hidden until \"Show closed\" is on). All three should be genuinely absent by default, not just from an unrelated broken query.",
|
|
111
|
+
href: "/products",
|
|
112
|
+
},
|
|
95
113
|
],
|
|
96
114
|
},
|
|
97
115
|
{
|
|
@@ -101,10 +119,35 @@ const rawTesterChecklistItems = [
|
|
|
101
119
|
{ key: "standard-detail", label: "Standard product detail page loads correctly" },
|
|
102
120
|
{ key: "auction-detail", label: "Auction detail page shows current bid + countdown correctly" },
|
|
103
121
|
{ key: "preorder-detail", label: "Pre-order detail page shows expected ship date correctly" },
|
|
104
|
-
{
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
122
|
+
{
|
|
123
|
+
key: "image-gallery",
|
|
124
|
+
label: "Product image gallery thumbnails load reliably (no broken-image icons) and click-to-zoom/rotate works in the lightbox",
|
|
125
|
+
description: "The thumbnail strip occasionally showed a broken-image icon instead of the photo (transient 3rd-party fetch failures with no retry, fixed 2026-08-19). Reload an auction or product detail page a few times and confirm every thumbnail — not just the main image — renders correctly each time.",
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
key: "video-playback",
|
|
129
|
+
label: "A product's video slide opens in theater mode with playback, zoom, and rotate controls",
|
|
130
|
+
description: "Product video is no longer a YouTube embed — when a product has a video, it appears as a trailing gallery slide (poster image + play badge) alongside the photos. Clicking it opens the full-screen lightbox in theater mode, plays the video with native controls, and the zoom (+/-) and rotate (R) buttons in the top bar still apply. Test on the two seeded fixtures: \"Beyblade Original — Dragoon F (Video Demo)\" and \"Beyblade X BX-02 Dran Sword (Video Demo)\".",
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
key: "related-listings-sections",
|
|
134
|
+
label: "A standard product's detail page shows up to 4 \"related\" carousels below the main content — More in [category], More by [brand], You might also like (shared tags), and More from [store] — each populated with real items, not empty",
|
|
135
|
+
description: "Verify against \"Beyblade Burst B-01 Valkyrie\" (product-beyblade-burst-valkyrie) — all 4 sections should show real items: other Beyblade Burst products, other \"Beyblade\"-brand products, other attack-type/starter-set tagged products, and other Beyblade Arena store listings.",
|
|
136
|
+
href: "/products",
|
|
137
|
+
},
|
|
138
|
+
{ key: "prizedraw-buy-reveal", label: "Buying a prize-draw entry correctly assigns a prize once payment is confirmed (instant mode) or shows a pending state until the draw closes (scheduled mode)" },
|
|
139
|
+
{
|
|
140
|
+
key: "bundle-purchase",
|
|
141
|
+
label: "Purchasing a bundle works and shows all included items in the order",
|
|
142
|
+
description: "Verify against \"Test Bundle\" (bundle-tester-sandbox, findable from the bundles listing page) — after checkout, the order should show a single \"Test Bundle\" line item, not two separate product lines.",
|
|
143
|
+
href: "/bundles",
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
key: "product-group-set-widget",
|
|
147
|
+
label: "A product's detail page shows a collapsible \"Part of / Parts in this group\" panel with a working thumbnail strip and a \"View whole group\" table when the product belongs to a product-group (\"Set\")",
|
|
148
|
+
description: "Verify against \"Test Product Set — Standard #1 + Standard #2\" (group-tester-sandbox-bundle) and either of its two children (product-tester-standard-1 / product-tester-standard-2), findable from the products listing page — all three should show the panel with each other listed, the arrow/triangle expand icons should render as real glyphs (not garbled text), and \"View whole group\" should open a working modal/drawer.",
|
|
149
|
+
href: "/products",
|
|
150
|
+
},
|
|
108
151
|
{ key: "classified-contact-flow", label: "A classified listing shows a contact-seller flow with deliberately no checkout/buy button" },
|
|
109
152
|
{ key: "digitalcode-delivery", label: "Purchasing a digital-code listing delivers the code to the buyer post-purchase" },
|
|
110
153
|
{ key: "live-item-detail", label: "A live-item listing's detail page shows the livestream link correctly" },
|
|
@@ -125,7 +168,7 @@ const rawTesterChecklistItems = [
|
|
|
125
168
|
{ key: "shipping-address-edit", label: "Editing an existing shipping address from checkout works" },
|
|
126
169
|
{ key: "shipping-method-selection", label: "Selecting a shipping method/provider at checkout recalculates the shipping fee correctly" },
|
|
127
170
|
{ key: "gst-breakdown-display", label: "When GST is enabled in Site Settings, checkout shows the correct CGST/SGST or IGST breakdown based on buyer vs seller state" },
|
|
128
|
-
{ key: "checkout-otp-highvalue", label: "Carts ≥ the OTP threshold prompt
|
|
171
|
+
{ key: "checkout-otp-highvalue", label: "Carts ≥ the admin-configured high-value OTP threshold (Site Settings → Shipping → \"High-value checkout OTP threshold\") prompt a \"Verify this order\" email OTP right before payment (except COD)" },
|
|
129
172
|
{ key: "payment-method-selection", label: "Choosing between COD, UPI/manual, and Razorpay (when enabled) at checkout works" },
|
|
130
173
|
{ key: "payment-window-countdown", label: "Manual-payment orders show a 15-minute countdown timer on the payment page" },
|
|
131
174
|
{ key: "payment-proof-upload", label: "Uploading manual payment proof (screenshot, UTR, mark-as-paid + agreement checkboxes) works" },
|
|
@@ -140,14 +183,39 @@ const rawTesterChecklistItems = [
|
|
|
140
183
|
{ key: "checkout-back-navigation", label: "Using the browser back button mid-checkout does not lose cart state or double-submit the order" },
|
|
141
184
|
],
|
|
142
185
|
},
|
|
186
|
+
{
|
|
187
|
+
pageKey: "my-orders",
|
|
188
|
+
pageLabel: "My Orders — List & Dashboard",
|
|
189
|
+
cases: [
|
|
190
|
+
{
|
|
191
|
+
key: "orders-item-summary",
|
|
192
|
+
label: "Each row on My Orders shows the order's items (thumbnail, title, quantity) instead of just an order ID — and orders with more than 3 items show a \"+N more\" summary for the rest",
|
|
193
|
+
description: "Verify against the seeded \"order-tester-sandbox-multi-item\" fixture (5 items: Test Gadget — Standard Listing #1, Test Collectible — Standard Listing #2, Test Accessory — Carry Case, Display Stand ×2, Sticker Set ×3) — its card should show the first 3 items and a \"+2 more\" badge, plus the correct ₹602 total and delivered status.",
|
|
194
|
+
href: "/user/orders",
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
key: "orders-view-details-button",
|
|
198
|
+
label: "Each order row has a visible \"View Details\" button (in addition to the row itself being clickable) that opens that exact order's detail page",
|
|
199
|
+
href: "/user/orders",
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
key: "dashboard-recent-orders-linked",
|
|
203
|
+
label: "The My Account dashboard's \"Recent Orders\" widget rows are clickable and each shows a \"View Details\" button linking to the correct order — not a dead, non-interactive preview",
|
|
204
|
+
href: "/user",
|
|
205
|
+
},
|
|
206
|
+
],
|
|
207
|
+
},
|
|
143
208
|
{
|
|
144
209
|
pageKey: "bidding",
|
|
145
210
|
pageLabel: "Bidding",
|
|
146
211
|
cases: [
|
|
147
212
|
{ key: "place-bid", label: "Placing a bid on an auction works", href: "/user/bids" },
|
|
213
|
+
{ key: "place-bid-live-self", label: "After placing a bid, the current bid amount and bid count update immediately on the auction page for the bidder — no manual page refresh needed" },
|
|
214
|
+
{ key: "place-bid-live-other-viewer", label: "Opening the same auction in two browser tabs (or two accounts) and placing a bid in one updates the current bid and bid count in the other within a few seconds, without a manual refresh (realtime SSE)" },
|
|
148
215
|
{ key: "outbid-notification", label: "Getting outbid triggers a notification" },
|
|
149
216
|
{ key: "win-auction", label: "Winning an auction creates a payable order correctly" },
|
|
150
|
-
{ key: "bid-history", label: "My Bids page shows accurate bid history" },
|
|
217
|
+
{ key: "bid-history", label: "My Bids page shows accurate bid history, paginated with the most recent bid first", href: "/user/bids" },
|
|
218
|
+
{ key: "bid-history-auction-detail-pagination", label: "An auction detail page's Bid History section shows the most recent bid first and paginates once there are more bids than fit on one page (try the L-Drago auction — 13 seeded bids)" },
|
|
151
219
|
],
|
|
152
220
|
},
|
|
153
221
|
{
|
|
@@ -183,6 +251,7 @@ const rawTesterChecklistItems = [
|
|
|
183
251
|
{ key: "cart-persists-across-session", label: "Cart contents persist across a page reload and across login/logout for the same account" },
|
|
184
252
|
{ key: "cart-guest-to-login-merge", label: "A guest cart's items merge correctly into the account's cart after logging in" },
|
|
185
253
|
{ key: "cart-price-revalidated", label: "If a product's price changes after it was added to the cart, checkout uses the current price and shows the buyer the updated total" },
|
|
254
|
+
{ key: "cart-checkout-button-visible", label: "The \"Proceed to Checkout\" button renders as a clearly visible, prominently-colored primary button — not a dull/near-invisible one blending into the page background", href: "/cart" },
|
|
186
255
|
],
|
|
187
256
|
},
|
|
188
257
|
{
|
|
@@ -192,6 +261,12 @@ const rawTesterChecklistItems = [
|
|
|
192
261
|
{ key: "leave-review", label: "Leaving a review with rating + photo works" },
|
|
193
262
|
{ key: "view-seller-reviews", label: "Viewing a seller's reviews on their store page works" },
|
|
194
263
|
{ key: "seller-response", label: "Seller responding to a review works" },
|
|
264
|
+
{
|
|
265
|
+
key: "review-detail-related-sections",
|
|
266
|
+
label: "An individual review's permalink page (/reviews/[id]) shows \"More reviews for [product]\" and \"More reviews for [store]\" sections with real reviews, not empty",
|
|
267
|
+
description: "Verify against review-7 (the first review on \"Beyblade Original — Dranzer S\"), reachable via My Reviews or a store's Reviews tab.",
|
|
268
|
+
href: "/reviews",
|
|
269
|
+
},
|
|
195
270
|
],
|
|
196
271
|
},
|
|
197
272
|
{
|
|
@@ -214,7 +289,12 @@ const rawTesterChecklistItems = [
|
|
|
214
289
|
{ key: "my-prize-draws", label: "\"My Prize Draw Entries\" shows accurate entries and outcomes", href: "/user/prize-draws" },
|
|
215
290
|
{ key: "my-digital-codes", label: "\"My Digital Codes\" shows purchased codes correctly", href: "/user/digital-codes" },
|
|
216
291
|
{ key: "my-offers", label: "\"My Offers\" list shows accurate offer history", href: "/user/offers" },
|
|
217
|
-
{
|
|
292
|
+
{
|
|
293
|
+
key: "my-returns",
|
|
294
|
+
label: "Requesting and viewing a return works",
|
|
295
|
+
description: "Also confirm the page itself loads without an empty/error state on a fresh visit — it queries orders by status, which was silently broken site-wide by a 2026-08-19 missing-index bug (now fixed).",
|
|
296
|
+
href: "/user/returns",
|
|
297
|
+
},
|
|
218
298
|
{ key: "my-reviews", label: "\"My Reviews\" shows reviews the user has left", href: "/user/reviews" },
|
|
219
299
|
],
|
|
220
300
|
},
|
|
@@ -299,6 +379,12 @@ const rawTesterChecklistItems = [
|
|
|
299
379
|
{ key: "seller-live-crud", label: "Seller can create, edit, and list live-item listings", href: "/store/live" },
|
|
300
380
|
{ key: "seller-prizedraws-crud", label: "Seller can create, edit, and list prize-draw listings, and view entries", href: "/store/prize-draws" },
|
|
301
381
|
{ key: "seller-art-stickers-crud", label: "Seller can create, edit, and list art and sticker listings", href: "/store/art" },
|
|
382
|
+
{
|
|
383
|
+
key: "seller-products-auctions-default-load",
|
|
384
|
+
label: "The seller Products, Auctions, and Prize Draws dashboards each show real listings by default (not empty), and their \"Show sold\"/\"Show ended\"/\"Show closed\" toggles work",
|
|
385
|
+
description: "A 2026-08-19 index-shape bug broke the default storeId-scoped queries on these three seller dashboards. Confirm each loads populated on first visit and its toggle reveals the corresponding hidden fixture.",
|
|
386
|
+
href: "/store/products",
|
|
387
|
+
},
|
|
302
388
|
],
|
|
303
389
|
},
|
|
304
390
|
{
|
|
@@ -325,7 +411,12 @@ const rawTesterChecklistItems = [
|
|
|
325
411
|
pageKey: "seller-marketing-extras",
|
|
326
412
|
pageLabel: "Seller Offers, Features, Google Reviews & WhatsApp Catalog",
|
|
327
413
|
cases: [
|
|
328
|
-
{
|
|
414
|
+
{
|
|
415
|
+
key: "seller-offers-list",
|
|
416
|
+
label: "Seller offers list shows accurate buyer offers",
|
|
417
|
+
description: "Also confirm each status tab (Pending/Countered/Accepted/Declined/etc.) actually filters correctly rather than showing an empty or unchanged list — a 2026-08-19 missing-index bug affected the status-tab query.",
|
|
418
|
+
href: "/store/offers",
|
|
419
|
+
},
|
|
329
420
|
{ key: "seller-features-crud", label: "Seller can create and edit product features", href: "/store/features" },
|
|
330
421
|
{ key: "seller-google-reviews-sync", label: "Google Reviews sync pulls in real reviews correctly", href: "/store/google-reviews" },
|
|
331
422
|
{ key: "seller-whatsapp-catalog", label: "WhatsApp catalog import/push works", href: "/store/whatsapp" },
|
|
@@ -361,6 +452,14 @@ const rawTesterChecklistItems = [
|
|
|
361
452
|
cases: [
|
|
362
453
|
{ key: "read-post", label: "Reading a blog post renders correctly (images, formatting)" },
|
|
363
454
|
{ key: "blog-listing", label: "Blog listing page shows all published posts" },
|
|
455
|
+
{ key: "blog-cover-image-display", label: "A blog post's cover image displays on both its listing card and its detail page" },
|
|
456
|
+
{ key: "blog-youtube-embed", label: "A blog post with a YouTube video ID set shows a working embedded video player above the article content" },
|
|
457
|
+
{
|
|
458
|
+
key: "blog-related-posts-sections",
|
|
459
|
+
label: "A blog post detail page shows three \"related\" sections below the article — Related Posts (same category), You might also like (shared tags), and More from [author] — each populated with real posts, not empty",
|
|
460
|
+
description: "Verify against \"How to Spot a Genuine Takara-Tomy Beyblade\" (slug: spot-genuine-takara-tomy-beyblade) — the tag-overlap section should include \"Authenticating Beyblades — What to Check Before You Buy\" (shares \"authentication\"/\"counterfeit\" tags, different category), proving it's genuinely tag-based and not just re-showing the same-category list.",
|
|
461
|
+
href: "/blog",
|
|
462
|
+
},
|
|
364
463
|
],
|
|
365
464
|
},
|
|
366
465
|
{
|
|
@@ -368,11 +467,22 @@ const rawTesterChecklistItems = [
|
|
|
368
467
|
pageLabel: "Events, Raffles & Spin Wheel",
|
|
369
468
|
cases: [
|
|
370
469
|
{ key: "view-event", label: "Viewing an event detail page works", href: "/user/events" },
|
|
371
|
-
{ key: "
|
|
470
|
+
{ key: "events-listing-cards-images", label: "The events listing page shows each event's real cover image (not a generic icon placeholder) when one is set, and all cards in a row are the same height", href: "/events" },
|
|
471
|
+
{
|
|
472
|
+
key: "related-events-section",
|
|
473
|
+
label: "An event's Overview tab shows a \"Related Events\" carousel of other active events sharing at least one tag, with real events (not empty)",
|
|
474
|
+
description: "Verify against \"Beyblade Original Series Clearance Sale\" (event-original-series-clearance) — it shares the \"original-series\" tag with the favourite-blader poll event and the \"singles\" tag with the buy-3-get-1 offer event, so Related Events should show both.",
|
|
475
|
+
href: "/events",
|
|
476
|
+
},
|
|
477
|
+
{ key: "poll-vote-inline", label: "Voting in a poll event from the Overview tab works, shows a confirmation, and a repeat visit shows the already-voted state" },
|
|
478
|
+
{ key: "survey-feedback-submit", label: "Submitting a survey or feedback event's Participate form works, enforces required fields, and shows a success confirmation" },
|
|
479
|
+
{ key: "offer-coupon-display-copy", label: "An offer event shows its coupon code prominently on the Overview tab, and the \"Copy code\" button copies it to the clipboard with visible confirmation" },
|
|
480
|
+
{ key: "raffle-entry", label: "Entering an open_raffle event works and the Participate tab shows the prize hero, eligibility copy, and optional message field (not a blank form)" },
|
|
372
481
|
{ key: "raffle-entry-top-n-scorers", label: "Entering a top_n_scorers raffle event works and the leaderboard reflects entries" },
|
|
373
482
|
{ key: "raffle-entry-top-n-participants", label: "Entering a top_n_participants raffle event works" },
|
|
374
483
|
{ key: "spin-wheel", label: "Spinning the spin-wheel event works and shows the prize immediately (realtime)" },
|
|
375
484
|
{ key: "spin-wheel-window-blocked", label: "A second spin attempt within the same spinWindow is blocked with a clear message" },
|
|
485
|
+
{ key: "leaderboard-live-refresh", label: "After voting/entering an event, navigating to its Leaderboard tab shows the updated standings immediately rather than a stale cached ranking" },
|
|
376
486
|
],
|
|
377
487
|
},
|
|
378
488
|
{
|
|
@@ -437,6 +547,9 @@ const rawTesterChecklistItems = [
|
|
|
437
547
|
{ key: "loading-states", label: "Loading/skeleton states look correct while data fetches" },
|
|
438
548
|
{ key: "empty-states", label: "Empty states (no results, empty cart) look correct" },
|
|
439
549
|
{ key: "error-states", label: "Error states (404, form validation) look correct" },
|
|
550
|
+
{ key: "card-row-heights-aligned", label: "Product/auction/pre-order/event cards in the same row are the same height, with their action button (Reserve now, Place bid, View details, etc.) aligned along a common bottom edge even when one card's description is longer than another's" },
|
|
551
|
+
{ key: "image-watermark-subtle", label: "Product and listing images show a small, subtle watermark (not oversized or fully opaque, doesn't obscure the image)" },
|
|
552
|
+
{ key: "section-cta-buttons-visible", label: "Homepage section \"View all →\" / \"Go to…\" buttons use a solid primary-colored fill so they're clearly identifiable as clickable CTAs, not a plain white/outline box that blends into the page" },
|
|
440
553
|
],
|
|
441
554
|
},
|
|
442
555
|
{
|
|
@@ -471,6 +584,7 @@ const rawTesterChecklistItems = [
|
|
|
471
584
|
{ key: "sections-live-reserve-render", label: "Live auctions and \"Reserve Before It Ships\" (pre-orders) sections show real listings" },
|
|
472
585
|
{ key: "sections-social-proof-render", label: "Verified stores, tournaments & events, and collector reviews sections render correctly" },
|
|
473
586
|
{ key: "sections-social-feed-hidden", label: "The social-feed section type is correctly hidden on the homepage since it's disabled in Site Settings" },
|
|
587
|
+
{ key: "hero-welcome-logo-sized-padded", label: "The homepage welcome hero's brand mark/logo panel (desktop, right side) is appropriately sized and padded inside its card — not oversized, cramped, or touching/overflowing the card edges", href: "/" },
|
|
474
588
|
],
|
|
475
589
|
},
|
|
476
590
|
]),
|
|
@@ -486,6 +600,18 @@ const rawTesterChecklistItems = [
|
|
|
486
600
|
{ key: "bundles-listing-page", label: "The bundles listing page loads correctly", href: "/bundles" },
|
|
487
601
|
{ key: "categories-index", label: "The categories index page loads correctly", href: "/categories" },
|
|
488
602
|
{ key: "brands-index-detail", label: "The brands index and an individual brand detail page load correctly" },
|
|
603
|
+
{
|
|
604
|
+
key: "category-item-counts-accurate",
|
|
605
|
+
label: "Category cards on the categories index page show an accurate, non-zero item count matching what's actually inside each category",
|
|
606
|
+
description: "Verify \"Beyblade Burst\" shows 2 items (matching the 2 real products inside it when browsed), not \"0 items\". If any category shows 0 despite having published products, run `npm run categories:backfill-metrics` to recompute the counters.",
|
|
607
|
+
href: "/categories",
|
|
608
|
+
},
|
|
609
|
+
{
|
|
610
|
+
key: "category-brand-related-sections",
|
|
611
|
+
label: "A category detail page shows a \"Related Categories\" section (other categories sharing the same root) and a brand detail page shows a \"Related Brands\" section, each with real items",
|
|
612
|
+
description: "Verify against \"Beyblade Burst\" (category-beyblade-burst) — Related Categories should show Beyblade Original, Metal Fight, and Beyblade X (siblings under the \"Spinning Tops\" root).",
|
|
613
|
+
href: "/categories",
|
|
614
|
+
},
|
|
489
615
|
],
|
|
490
616
|
},
|
|
491
617
|
{
|
|
@@ -493,10 +619,16 @@ const rawTesterChecklistItems = [
|
|
|
493
619
|
pageLabel: "Store & Seller Directories",
|
|
494
620
|
cases: [
|
|
495
621
|
{ key: "store-directory", label: "The store directory page loads correctly", href: "/stores" },
|
|
496
|
-
{ key: "store-detail-tabs", label: "A store detail page's
|
|
622
|
+
{ key: "store-detail-tabs", label: "A store detail page's listing-type dropdown (Products/Auctions/Pre-Orders/Prize Draws/Bundles/Classifieds/Digital Codes/Live Items/Art & Stickers) switches correctly between listing types, and the separate Coupons/Reviews/About tabs next to it all load correctly", description: "The listing-type dropdown is always a dropdown (not just on narrow/mobile widths, unlike category/brand/product/event tabs) since a store can have up to 9 listing types — Coupons, Reviews, and About stay as standalone tabs beside it, never folded into the dropdown." },
|
|
497
623
|
{ key: "sellers-directory", label: "The sellers directory page loads correctly", href: "/sellers" },
|
|
498
624
|
{ key: "seller-detail-page", label: "An individual seller's public detail page loads correctly" },
|
|
499
625
|
{ key: "scams-registry", label: "The scams registry page and an individual scam detail page load correctly", href: "/scams" },
|
|
626
|
+
{
|
|
627
|
+
key: "scam-related-profiles-sections",
|
|
628
|
+
label: "A scammer profile page shows a \"Related Profiles\" section (explicit same-person cross-links) and a separately-labeled \"Similar Scam Reports\" section (same scam type, with a note that it does not imply the same person)",
|
|
629
|
+
description: "Verify against \"MetalFusion_PreorderAgent\" (scammer-fake-metal-fusion-preorder-agent) — Related Profiles should show \"MF_Backup_Store\" (confirmed same operator), and Similar Scam Reports should separately show \"Bey_King_India\" (a different person using the same advance-payment-ghost pattern). Confirm the two sections are visually distinct and the Similar Scam Reports caption is present.",
|
|
630
|
+
href: "/scams",
|
|
631
|
+
},
|
|
500
632
|
],
|
|
501
633
|
},
|
|
502
634
|
{
|
|
@@ -529,6 +661,20 @@ const rawTesterChecklistItems = [
|
|
|
529
661
|
{ key: "unauthorized-404-pages", label: "Unauthorized and 404 error pages render correctly instead of crashing" },
|
|
530
662
|
],
|
|
531
663
|
},
|
|
664
|
+
{
|
|
665
|
+
pageKey: "bug-hunters",
|
|
666
|
+
pageLabel: "Bug Hunters Leaderboard",
|
|
667
|
+
cases: [
|
|
668
|
+
{
|
|
669
|
+
key: "leaderboard-loads",
|
|
670
|
+
label: "The public Bug Hunters leaderboard (/bug-hunters) loads and lists testers ranked by confirmed-bug count, most bugs first",
|
|
671
|
+
description: "Verify against the seeded demo fixture — \"Mock User 18\" should appear on the leaderboard with 1 confirmed bug (from the \"Demo fixture\" case under Admin (Testing) → Bug Hunter Rewards).",
|
|
672
|
+
href: "/bug-hunters",
|
|
673
|
+
},
|
|
674
|
+
{ key: "leaderboard-empty-state", label: "If no bugs have been confirmed yet, the leaderboard shows a clear \"No confirmed bugs yet\" empty state instead of a blank page or error" },
|
|
675
|
+
{ key: "leaderboard-footer-link", label: "A \"Bug Hunters\" link is present in the site footer's Support column and navigates to /bug-hunters" },
|
|
676
|
+
],
|
|
677
|
+
},
|
|
532
678
|
]),
|
|
533
679
|
...group("admin", "Admin (Testing)", [
|
|
534
680
|
{
|
|
@@ -538,6 +684,18 @@ const rawTesterChecklistItems = [
|
|
|
538
684
|
{ key: "brands-crud", label: "Admin can create, edit, and list brands", href: "/admin/brands" },
|
|
539
685
|
{ key: "categories-crud", label: "Admin can create, edit, and list categories", href: "/admin/categories" },
|
|
540
686
|
{ key: "products-crud", label: "Admin can create, edit, and list products", href: "/admin/products" },
|
|
687
|
+
{
|
|
688
|
+
key: "products-default-listing-not-empty",
|
|
689
|
+
label: "The admin Products list loads real products by default (not empty), and the \"Show sold\" toggle + Auctions/Prize Draws type tabs each load correctly with their own \"Show ended\"/\"Show closed\" toggle",
|
|
690
|
+
description: "A 2026-08-19 index-shape bug broke the default isSold==false query (and its listingType/auctionEndDate/prizeRevealWindowEnd variants) — now fixed. Confirm the list isn't blank on first load and each Type tab + toggle combination returns results.",
|
|
691
|
+
href: "/admin/products",
|
|
692
|
+
},
|
|
693
|
+
{
|
|
694
|
+
key: "categories-toggle-filters",
|
|
695
|
+
label: "The admin Categories \"Active\" and \"Featured\" toggle filters both show results in either state",
|
|
696
|
+
description: "Verify against the seeded \"Test Inactive Category\" fixture (isActive:false) — it should only appear when the Active filter is switched to show inactive categories.",
|
|
697
|
+
href: "/admin/categories",
|
|
698
|
+
},
|
|
541
699
|
{ key: "sublisting-categories-crud", label: "Admin can create, edit, and list sublisting categories", href: "/admin/sublisting-categories" },
|
|
542
700
|
{ key: "carousel-crud", label: "Admin can create, edit, and reorder carousel slides", href: "/admin/carousel" },
|
|
543
701
|
{ key: "sections-crud", label: "Admin can create, edit, and reorder homepage sections", href: "/admin/sections" },
|
|
@@ -574,11 +732,11 @@ const rawTesterChecklistItems = [
|
|
|
574
732
|
pageKey: "prize-draws-lotteries",
|
|
575
733
|
pageLabel: "Prize Draws / Lotteries",
|
|
576
734
|
cases: [
|
|
577
|
-
{ key: "prizedraw-create", label: "Admin can create a prize-draw listing", href: "/admin/prize-draws" },
|
|
578
|
-
{ key: "prizedraw-reveal-winner", label: "
|
|
579
|
-
{ key: "prizedraw-
|
|
735
|
+
{ key: "prizedraw-create", label: "Admin can create a prize-draw listing, choosing instant or scheduled reveal mode and a 1–15 day duration", href: "/admin/prize-draws" },
|
|
736
|
+
{ key: "prizedraw-reveal-winner", label: "Winners are assigned automatically via crypto.randomInt (on payment confirmation for instant mode, or at expiry/sellout for scheduled mode) — never by a manual admin click" },
|
|
737
|
+
{ key: "prizedraw-scam-guard", label: "A prize draw with active entries cannot be unpublished, archived, or deleted, and an already-won item's details cannot be edited" },
|
|
580
738
|
{ key: "prizedraw-lock-on-reveal", label: "Lock-on-reveal correctly blocks further entries after the draw" },
|
|
581
|
-
{ key: "prizedraw-entries-view", label: "Admin can view prize-draw
|
|
739
|
+
{ key: "prizedraw-entries-view", label: "Admin and the owning seller can view the prize-draw winner mapping (which item went to which order), but it's never shown publicly", href: "/admin/lotteries" },
|
|
582
740
|
],
|
|
583
741
|
},
|
|
584
742
|
{
|
|
@@ -604,6 +762,7 @@ const rawTesterChecklistItems = [
|
|
|
604
762
|
pageLabel: "Blog & FAQs",
|
|
605
763
|
cases: [
|
|
606
764
|
{ key: "blog-create-edit-publish", label: "Admin can create, edit, and publish a blog post", href: "/admin/blog" },
|
|
765
|
+
{ key: "blog-media-step-save", label: "On the blog editor's Media step, setting a Cover Image and/or a YouTube Video ID and saving succeeds (no validation error) and both persist correctly when reopening the post for edit" },
|
|
607
766
|
{ key: "faq-create-edit-category", label: "Admin can create and edit an FAQ, including category assignment", href: "/admin/faqs" },
|
|
608
767
|
],
|
|
609
768
|
},
|
|
@@ -681,7 +840,83 @@ const rawTesterChecklistItems = [
|
|
|
681
840
|
{ key: "stores-admin-view", label: "Admin stores view shows accurate data", href: "/admin/stores" },
|
|
682
841
|
],
|
|
683
842
|
},
|
|
843
|
+
{
|
|
844
|
+
pageKey: "media-watermark",
|
|
845
|
+
pageLabel: "Media Watermark Settings",
|
|
846
|
+
cases: [
|
|
847
|
+
{ key: "watermark-size-opacity-controls", label: "Adjusting the watermark Size and Opacity sliders in Site Settings → Watermark visibly changes how prominent the watermark is on newly-loaded product images", href: "/admin/site" },
|
|
848
|
+
{ key: "watermark-position-presets", label: "Each of the 5 watermark Position presets (Center, Top left, Top right, Bottom left, Bottom right) correctly repositions the watermark on newly-loaded images" },
|
|
849
|
+
{ key: "watermark-custom-offset", label: "Enabling \"Use a custom X/Y offset instead\" and entering positive/negative X/Y values moves the watermark the correct direction and distance from center" },
|
|
850
|
+
{ key: "watermark-theme-recolor", label: "The default bundled watermark mark recolors to match the active site theme's brand gradient rather than a fixed hardcoded color" },
|
|
851
|
+
{ key: "watermark-video-overlay-parity", label: "A product video's live watermark overlay appears at the same position, size, and opacity as the image watermark pipeline" },
|
|
852
|
+
],
|
|
853
|
+
},
|
|
854
|
+
{
|
|
855
|
+
pageKey: "bug-hunter-rewards",
|
|
856
|
+
pageLabel: "Bug Hunter Rewards",
|
|
857
|
+
cases: [
|
|
858
|
+
{
|
|
859
|
+
key: "confirm-bug",
|
|
860
|
+
label: "Admin can mark a tester's \"No\" answer as a confirmed bug from the Main Issues tab (or the All Submissions tab) — the reporting tester is credited as the bug hunter and the case is disabled so no other tester can answer it",
|
|
861
|
+
description: "From /admin/tester-feedback → Main Issues, click \"Mark as Bug\" on any open issue. Confirm: (1) the confirmation dialog appears before it commits, (2) the issue's card now shows a \"🐛 Confirmed — credited to [tester name]\" badge, (3) re-opening the Tester Hub as any other tester no longer shows that case at all.",
|
|
862
|
+
href: "/admin/tester-feedback",
|
|
863
|
+
},
|
|
864
|
+
{
|
|
865
|
+
key: "confirm-bug-idempotent",
|
|
866
|
+
label: "Attempting to mark the same case as a bug a second time (e.g. from a duplicate \"No\" report on the same case) is rejected rather than silently re-crediting a different tester",
|
|
867
|
+
},
|
|
868
|
+
{
|
|
869
|
+
key: "reopen-case",
|
|
870
|
+
label: "Admin can reopen a bug-confirmed case as a new version for retest — the new version is active and answerable again, while the old case stays disabled in the catalog with its bug-hunter credit intact",
|
|
871
|
+
description: "Verify against the seeded \"Demo fixture — reported bug, already confirmed and reopened\" pair under this page: the v1 case should show Status \"Bug Confirmed\" and be hidden from the catalog's default (Active-only) view, while its v2 case shows Status \"Active\" and is answerable from the Tester Hub. From /admin/tester-checklist, switch the Status filter to \"Inactive\" or \"Bug status → Bug Confirmed\" to find v1, then use its \"Reopen as New Test Case\" row action on any other bug-confirmed case to try the flow live.",
|
|
872
|
+
href: "/admin/tester-checklist",
|
|
873
|
+
},
|
|
874
|
+
{
|
|
875
|
+
key: "catalog-default-active-filter",
|
|
876
|
+
label: "The Tester Checklist catalog (/admin/tester-checklist) shows only Active cases by default — bug-confirmed and reopened-away cases are hidden unless the Status filter is switched to \"Inactive\"/\"All\" or the \"Bug status\" filter is set to \"Bug Confirmed\"",
|
|
877
|
+
href: "/admin/tester-checklist",
|
|
878
|
+
},
|
|
879
|
+
],
|
|
880
|
+
},
|
|
684
881
|
], { adminOnly: true }),
|
|
882
|
+
// Demo fixture pair — deliberately outside group() since these two items
|
|
883
|
+
// exercise the confirm-bug → reopen-as-new-version lifecycle directly
|
|
884
|
+
// (bugConfirmed/bugHunterId/version/previousVersionId/supersededByItemId
|
|
885
|
+
// are not part of the CaseInput shape group() builds). Lets an admin/
|
|
886
|
+
// tester see the full state machine immediately after a fresh reseed,
|
|
887
|
+
// and gives the /bug-hunters leaderboard a non-empty first entry.
|
|
888
|
+
{
|
|
889
|
+
id: "checklist-admin-bug-hunter-rewards-demo-fixture",
|
|
890
|
+
groupKey: "admin",
|
|
891
|
+
groupLabel: "Admin (Testing)",
|
|
892
|
+
pageKey: "bug-hunter-rewards",
|
|
893
|
+
pageLabel: "Bug Hunter Rewards",
|
|
894
|
+
label: "Demo fixture — reported bug, already confirmed and reopened (v1, disabled)",
|
|
895
|
+
description: "Seed-only fixture demonstrating a confirmed bug: this v1 case is disabled (isActive:false) and credited to \"Mock User 18\". Its retest is \"Demo fixture — reported bug, already confirmed and reopened (v2, active)\" in this same page.",
|
|
896
|
+
order: 100,
|
|
897
|
+
isActive: false,
|
|
898
|
+
adminOnly: true,
|
|
899
|
+
bugConfirmed: true,
|
|
900
|
+
bugHunterId: "user-tester-qa",
|
|
901
|
+
bugHunterName: "Mock User 18",
|
|
902
|
+
bugConfirmedAt: new Date("2026-08-18T10:00:00.000Z"),
|
|
903
|
+
version: 1,
|
|
904
|
+
supersededByItemId: "checklist-admin-bug-hunter-rewards-demo-fixture-v2",
|
|
905
|
+
},
|
|
906
|
+
{
|
|
907
|
+
id: "checklist-admin-bug-hunter-rewards-demo-fixture-v2",
|
|
908
|
+
groupKey: "admin",
|
|
909
|
+
groupLabel: "Admin (Testing)",
|
|
910
|
+
pageKey: "bug-hunter-rewards",
|
|
911
|
+
pageLabel: "Bug Hunter Rewards",
|
|
912
|
+
label: "Demo fixture — reported bug, already confirmed and reopened (v2, active)",
|
|
913
|
+
description: "Seed-only fixture — the retest version reopened from the disabled v1 case in this same page. Active and answerable again; a fresh \"No\" answer here can be used to try Mark as Bug end-to-end.",
|
|
914
|
+
order: 101,
|
|
915
|
+
isActive: true,
|
|
916
|
+
adminOnly: true,
|
|
917
|
+
version: 2,
|
|
918
|
+
previousVersionId: "checklist-admin-bug-hunter-rewards-demo-fixture",
|
|
919
|
+
},
|
|
685
920
|
];
|
|
686
921
|
const defaultPhases = assignDefaultPhases(rawTesterChecklistItems);
|
|
687
922
|
export const testerChecklistSeedData = rawTesterChecklistItems.map((item, index) => ({
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CheckoutMessageInput, StatusNotificationInput, WebhookVerifyInput, IncomingWebhookPayload, SendWhatsAppInput, StatusMessageInput, WaBusinessSendInput, CatalogSyncInput, CatalogSyncResult, PurchaseAnnouncementInput, PaymentProofReviewMessageInput } from "../types";
|
|
1
|
+
import type { CheckoutMessageInput, StatusNotificationInput, WebhookVerifyInput, IncomingWebhookPayload, SendWhatsAppInput, StatusMessageInput, WaBusinessSendInput, WaBusinessTemplateSendInput, CatalogSyncInput, CatalogSyncResult, PurchaseAnnouncementInput, PaymentProofReviewMessageInput } from "../types";
|
|
2
2
|
export declare function buildCheckoutMessageURL(input: CheckoutMessageInput): string;
|
|
3
3
|
export declare function buildStatusNotificationURL(input: StatusNotificationInput): string;
|
|
4
4
|
export declare function verifyWebhookSignature(input: WebhookVerifyInput): boolean;
|
|
@@ -19,6 +19,15 @@ export declare function buildStatusMessage(input: StatusMessageInput, statusMess
|
|
|
19
19
|
* platform announcements. Returns true on success, false on any failure.
|
|
20
20
|
*/
|
|
21
21
|
export declare function sendWhatsAppBusinessMessage(input: WaBusinessSendInput): Promise<boolean>;
|
|
22
|
+
/**
|
|
23
|
+
* Send a pre-approved Meta message TEMPLATE via WhatsApp Business Cloud API.
|
|
24
|
+
* Required for business-initiated sends outside the 24h customer-service
|
|
25
|
+
* window (proactive order/status notifications) — Meta rejects free-form
|
|
26
|
+
* `sendWhatsAppBusinessMessage` `type: "text"` sends in that case. Returns
|
|
27
|
+
* true on success, false on any failure (including a missing/unapproved
|
|
28
|
+
* template — Meta returns a 4xx with an error body, not a network failure).
|
|
29
|
+
*/
|
|
30
|
+
export declare function sendWhatsAppTemplateMessage(input: WaBusinessTemplateSendInput): Promise<boolean>;
|
|
22
31
|
/**
|
|
23
32
|
* Sync a store's products to a Meta Commerce Catalog via items_batch API.
|
|
24
33
|
* Batches at 50 items per call (Meta limit). Returns aggregate results.
|
|
@@ -131,6 +131,53 @@ export async function sendWhatsAppBusinessMessage(input) {
|
|
|
131
131
|
return false;
|
|
132
132
|
}
|
|
133
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* Send a pre-approved Meta message TEMPLATE via WhatsApp Business Cloud API.
|
|
136
|
+
* Required for business-initiated sends outside the 24h customer-service
|
|
137
|
+
* window (proactive order/status notifications) — Meta rejects free-form
|
|
138
|
+
* `sendWhatsAppBusinessMessage` `type: "text"` sends in that case. Returns
|
|
139
|
+
* true on success, false on any failure (including a missing/unapproved
|
|
140
|
+
* template — Meta returns a 4xx with an error body, not a network failure).
|
|
141
|
+
*/
|
|
142
|
+
export async function sendWhatsAppTemplateMessage(input) {
|
|
143
|
+
const { toPhone, phoneNumberId, accessToken, templateName, languageCode, bodyParams } = input;
|
|
144
|
+
const cleanPhone = toPhone.replace(/\D/g, "");
|
|
145
|
+
if (!cleanPhone || !phoneNumberId || !accessToken || !templateName)
|
|
146
|
+
return false;
|
|
147
|
+
try {
|
|
148
|
+
const res = await fetch(`${META_GRAPH_BASE}/${phoneNumberId}/messages`, {
|
|
149
|
+
method: "POST",
|
|
150
|
+
headers: {
|
|
151
|
+
"Content-Type": "application/json",
|
|
152
|
+
Authorization: `Bearer ${accessToken}`,
|
|
153
|
+
},
|
|
154
|
+
body: JSON.stringify({
|
|
155
|
+
messaging_product: "whatsapp",
|
|
156
|
+
to: cleanPhone,
|
|
157
|
+
type: "template",
|
|
158
|
+
template: {
|
|
159
|
+
name: templateName,
|
|
160
|
+
language: { code: languageCode },
|
|
161
|
+
...(bodyParams?.length
|
|
162
|
+
? {
|
|
163
|
+
components: [
|
|
164
|
+
{
|
|
165
|
+
type: "body",
|
|
166
|
+
parameters: bodyParams.map((text) => ({ type: "text", text })),
|
|
167
|
+
},
|
|
168
|
+
],
|
|
169
|
+
}
|
|
170
|
+
: {}),
|
|
171
|
+
},
|
|
172
|
+
}),
|
|
173
|
+
});
|
|
174
|
+
return res.ok;
|
|
175
|
+
}
|
|
176
|
+
catch (_err) {
|
|
177
|
+
void normalizeError(_err);
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
134
181
|
/**
|
|
135
182
|
* Sync a store's products to a Meta Commerce Catalog via items_batch API.
|
|
136
183
|
* Batches at 50 items per call (Meta limit). Returns aggregate results.
|
|
@@ -50,6 +50,25 @@ export interface WaBusinessSendInput {
|
|
|
50
50
|
/** Meta system user access token */
|
|
51
51
|
accessToken: string;
|
|
52
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* Input for sending a pre-approved Meta message TEMPLATE. Required for any
|
|
55
|
+
* business-initiated WhatsApp send outside the 24h customer-service window —
|
|
56
|
+
* Meta rejects free-form `type: "text"` sends in that case.
|
|
57
|
+
*/
|
|
58
|
+
export interface WaBusinessTemplateSendInput {
|
|
59
|
+
/** Digits-only phone number with country code, e.g. "919876543210" */
|
|
60
|
+
toPhone: string;
|
|
61
|
+
/** Meta Phone Number ID (from Meta for Developers > App > WhatsApp > API Setup) */
|
|
62
|
+
phoneNumberId: string;
|
|
63
|
+
/** Meta system user access token */
|
|
64
|
+
accessToken: string;
|
|
65
|
+
/** Exact approved template name from Meta WhatsApp Manager (not the display label). */
|
|
66
|
+
templateName: string;
|
|
67
|
+
/** BCP-47 language code the template was approved in, e.g. "en" or "en_US". */
|
|
68
|
+
languageCode: string;
|
|
69
|
+
/** Positional {{1}}, {{2}}… body parameters, in order. */
|
|
70
|
+
bodyParams?: string[];
|
|
71
|
+
}
|
|
53
72
|
/** A product mapped to the Meta Commerce API catalog item format. */
|
|
54
73
|
export interface CatalogSyncProduct {
|
|
55
74
|
/** Firestore product ID — used as Meta `retailer_id` */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { useState, useEffect, useCallback } from "react";
|
|
3
|
-
import { getGuestWishlistItems, addToGuestWishlist, removeFromGuestWishlist, isInGuestWishlist, clearGuestWishlist, getGuestWishlistByType, } from "../utils/guest-wishlist";
|
|
3
|
+
import { getGuestWishlistItems, addToGuestWishlist, removeFromGuestWishlist, isInGuestWishlist, clearGuestWishlist, getGuestWishlistByType, GUEST_WISHLIST_CHANGE_EVENT, } from "../utils/guest-wishlist";
|
|
4
4
|
/**
|
|
5
5
|
* useGuestWishlist
|
|
6
6
|
*
|
|
@@ -12,6 +12,16 @@ export function useGuestWishlist() {
|
|
|
12
12
|
useEffect(() => {
|
|
13
13
|
setItems(getGuestWishlistItems());
|
|
14
14
|
setIsInitialized(true);
|
|
15
|
+
// Cross-instance sync within the same tab (e.g. a listing-page heart
|
|
16
|
+
// toggle writing storage while the header badge's own useGuestWishlist()
|
|
17
|
+
// instance is mounted elsewhere) + cross-tab via "storage".
|
|
18
|
+
const onChange = () => setItems(getGuestWishlistItems());
|
|
19
|
+
window.addEventListener(GUEST_WISHLIST_CHANGE_EVENT, onChange);
|
|
20
|
+
window.addEventListener("storage", onChange);
|
|
21
|
+
return () => {
|
|
22
|
+
window.removeEventListener(GUEST_WISHLIST_CHANGE_EVENT, onChange);
|
|
23
|
+
window.removeEventListener("storage", onChange);
|
|
24
|
+
};
|
|
15
25
|
}, []);
|
|
16
26
|
const add = useCallback((itemId, type, snapshot) => {
|
|
17
27
|
const updated = addToGuestWishlist(itemId, type, snapshot);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { useEffect, useRef } from "react";
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
3
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
|
4
4
|
import { usePathname } from "next/navigation";
|
|
5
5
|
import { apiClient } from "../../../http";
|
|
@@ -8,6 +8,7 @@ import { useGuestWishlist } from "./useGuestWishlist";
|
|
|
8
8
|
import { clearGuestWishlist, getGuestWishlistItems, } from "../utils/guest-wishlist";
|
|
9
9
|
import { WISHLIST_MAX } from "../../../constants/limits";
|
|
10
10
|
import { WISHLIST_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
11
|
+
import { WISHLIST_OPS_CHANGE_EVENT, getWishlistOpsDelta, } from "../../cart/utils/pending-ops";
|
|
11
12
|
/** Custom event fired when the server reports the wishlist is full during a merge. */
|
|
12
13
|
export const WISHLIST_CAP_EVENT = "appkit/wishlist/full";
|
|
13
14
|
function dispatchCapEvent(detail) {
|
|
@@ -39,6 +40,19 @@ export function useWishlistCount(userId) {
|
|
|
39
40
|
staleTime: 30000,
|
|
40
41
|
});
|
|
41
42
|
const serverTotal = wishlistData?.total ?? wishlistData?.items?.length ?? 0;
|
|
43
|
+
// Listing-page heart toggles write local-first (see useSyncManager) and
|
|
44
|
+
// queue an unsynced op that only reaches the server on the next ~30s
|
|
45
|
+
// background sync. Layer that pending delta on top of the server count so
|
|
46
|
+
// the badge updates the instant the toggle fires instead of lagging behind.
|
|
47
|
+
const [pendingDelta, setPendingDelta] = useState(0);
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
if (!userId)
|
|
50
|
+
return;
|
|
51
|
+
const recompute = () => setPendingDelta(getWishlistOpsDelta());
|
|
52
|
+
recompute();
|
|
53
|
+
window.addEventListener(WISHLIST_OPS_CHANGE_EVENT, recompute);
|
|
54
|
+
return () => window.removeEventListener(WISHLIST_OPS_CHANGE_EVENT, recompute);
|
|
55
|
+
}, [userId]);
|
|
42
56
|
// Merge on login: push guest items → server once, then clear local store.
|
|
43
57
|
useEffect(() => {
|
|
44
58
|
if (!userId) {
|
|
@@ -81,7 +95,7 @@ export function useWishlistCount(userId) {
|
|
|
81
95
|
}
|
|
82
96
|
}, [pathname, userId, queryClient]);
|
|
83
97
|
if (userId)
|
|
84
|
-
return serverTotal ?? 0;
|
|
98
|
+
return Math.max(0, (serverTotal ?? 0) + pendingDelta);
|
|
85
99
|
return guestCount;
|
|
86
100
|
}
|
|
87
101
|
/**
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/** Fired whenever guest wishlist storage is written, so every
|
|
2
|
+
* `useGuestWishlist()` instance in the tab (header badge, listing heart
|
|
3
|
+
* toggle, wishlist page) can re-read storage and stay in sync without
|
|
4
|
+
* waiting for a remount. */
|
|
5
|
+
export declare const GUEST_WISHLIST_CHANGE_EVENT = "appkit/wishlist/guest-changed";
|
|
1
6
|
export interface GuestWishlistItem {
|
|
2
7
|
itemId: string;
|
|
3
8
|
type: "product" | "auction" | "preorder" | "category" | "store" | "classified" | "digital-code" | "live";
|