@mohasinac/appkit 4.52.4 → 4.53.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/.build-stamp.json +4 -4
- package/dist/_internal/client/features/sticker-trace/canvas-codec.d.ts +2 -0
- package/dist/_internal/client/features/sticker-trace/canvas-codec.js +148 -0
- package/dist/_internal/server/features/auctions/actions.js +1 -1
- package/dist/_internal/server/features/categories/data.js +23 -7
- package/dist/_internal/server/features/checkout/actions.js +10 -3
- package/dist/_internal/server/features/checkout/data.d.ts +15 -0
- package/dist/_internal/server/features/checkout/data.js +20 -3
- package/dist/_internal/server/features/lottery/actions.js +9 -1
- package/dist/_internal/server/features/lottery/og.d.ts +40 -0
- package/dist/_internal/server/features/lottery/og.js +79 -0
- package/dist/_internal/server/features/messages/actions.js +4 -0
- package/dist/_internal/server/features/offers/adapters.js +17 -3
- package/dist/_internal/server/features/orders/actions.js +7 -2
- package/dist/_internal/server/features/raffle/actions.d.ts +19 -0
- package/dist/_internal/server/features/raffle/actions.js +25 -0
- package/dist/_internal/server/features/reviews/actions.js +1 -1
- package/dist/_internal/server/features/reviews/data.d.ts +1 -1
- package/dist/_internal/server/features/search/index-build/artifact-store.d.ts +89 -0
- package/dist/_internal/server/features/search/index-build/artifact-store.js +208 -0
- package/dist/_internal/server/features/search/index-build/build.d.ts +57 -0
- package/dist/_internal/server/features/search/index-build/build.js +285 -0
- package/dist/_internal/server/features/search/index-build/sources.d.ts +53 -0
- package/dist/_internal/server/features/search/index-build/sources.js +385 -0
- package/dist/_internal/server/features/search/search-sources.d.ts +0 -15
- package/dist/_internal/server/features/search/search-sources.js +142 -7
- package/dist/_internal/server/features/seo/robots.js +21 -1
- package/dist/_internal/server/features/seo/sitemap.js +1 -1
- package/dist/_internal/server/features/shared/listing-data-factory.js +16 -4
- package/dist/_internal/server/features/site-settings/adapters.d.ts +1 -1
- package/dist/_internal/server/features/site-settings/adapters.js +26 -0
- package/dist/_internal/server/features/sticker-trace/actions.d.ts +109 -0
- package/dist/_internal/server/features/sticker-trace/actions.js +172 -0
- package/dist/_internal/server/features/sticker-trace/adapters.d.ts +80 -0
- package/dist/_internal/server/features/sticker-trace/adapters.js +174 -0
- package/dist/_internal/server/features/sticker-trace/build-cut-file.d.ts +26 -0
- package/dist/_internal/server/features/sticker-trace/build-cut-file.js +127 -0
- package/dist/_internal/server/features/sticker-trace/coordinator.d.ts +27 -0
- package/dist/_internal/server/features/sticker-trace/coordinator.js +169 -0
- package/dist/_internal/server/features/sticker-trace/fanout.d.ts +43 -0
- package/dist/_internal/server/features/sticker-trace/fanout.js +180 -0
- package/dist/_internal/server/features/sticker-trace/finalizer.d.ts +25 -0
- package/dist/_internal/server/features/sticker-trace/finalizer.js +123 -0
- package/dist/_internal/server/features/sticker-trace/mosaic-inputs.d.ts +30 -0
- package/dist/_internal/server/features/sticker-trace/mosaic-inputs.js +112 -0
- package/dist/_internal/server/features/sticker-trace/progress.d.ts +81 -0
- package/dist/_internal/server/features/sticker-trace/progress.js +198 -0
- package/dist/_internal/server/features/sticker-trace/promote.d.ts +23 -0
- package/dist/_internal/server/features/sticker-trace/promote.js +193 -0
- package/dist/_internal/server/features/sticker-trace/shard.d.ts +50 -0
- package/dist/_internal/server/features/sticker-trace/shard.js +174 -0
- package/dist/_internal/server/features/sticker-trace/sheet-actions.d.ts +141 -0
- package/dist/_internal/server/features/sticker-trace/sheet-actions.js +279 -0
- package/dist/_internal/server/features/sticker-trace/storage.d.ts +135 -0
- package/dist/_internal/server/features/sticker-trace/storage.js +282 -0
- package/dist/_internal/server/functions/scheduled.d.ts +2 -1
- package/dist/_internal/server/functions/scheduled.js +25 -1
- package/dist/_internal/server/jobs/core/_event-ref.d.ts +51 -0
- package/dist/_internal/server/jobs/core/_event-ref.js +63 -0
- package/dist/_internal/server/jobs/core/assignSpinPrize.d.ts +18 -0
- package/dist/_internal/server/jobs/core/assignSpinPrize.js +77 -16
- package/dist/_internal/server/jobs/core/auctionSettlement.js +13 -4
- package/dist/_internal/server/jobs/core/autoPayoutEligibility.js +44 -15
- package/dist/_internal/server/jobs/core/bundleStockSync.js +3 -4
- package/dist/_internal/server/jobs/core/cleanupRtdbEvents.js +44 -0
- package/dist/_internal/server/jobs/core/event-draw-reasons.d.ts +78 -0
- package/dist/_internal/server/jobs/core/event-draw-reasons.js +133 -0
- package/dist/_internal/server/jobs/core/listingProcessor.js +28 -8
- package/dist/_internal/server/jobs/core/onOrderCreate.js +12 -1
- package/dist/_internal/server/jobs/core/onScamReportCreate.js +13 -2
- package/dist/_internal/server/jobs/core/onScamReportRejected.js +8 -2
- package/dist/_internal/server/jobs/core/onScamReportVerified.js +8 -2
- package/dist/_internal/server/jobs/core/onStoreWrite.js +11 -4
- package/dist/_internal/server/jobs/core/prizeDrawAssignWinner.js +5 -4
- package/dist/_internal/server/jobs/core/prizeDrawExpiryReveal.js +1 -4
- package/dist/_internal/server/jobs/core/prizeDrawSoldOutReveal.js +1 -2
- package/dist/_internal/server/jobs/core/promotions.js +7 -4
- package/dist/_internal/server/jobs/core/searchIndexBuild.d.ts +7 -0
- package/dist/_internal/server/jobs/core/searchIndexBuild.js +211 -0
- package/dist/_internal/server/jobs/core/triggerEventRaffle.d.ts +35 -0
- package/dist/_internal/server/jobs/core/triggerEventRaffle.js +213 -31
- package/dist/_internal/server/jobs/core/weeklyPayoutEligibility.js +20 -14
- package/dist/_internal/server/jobs/handlers/assignSpinPrize.d.ts +8 -0
- package/dist/_internal/server/jobs/handlers/assignSpinPrize.js +15 -15
- package/dist/_internal/server/jobs/handlers/index.d.ts +1 -0
- package/dist/_internal/server/jobs/handlers/index.js +1 -0
- package/dist/_internal/server/jobs/handlers/onCategoryWrite.js +18 -0
- package/dist/_internal/server/jobs/handlers/onProductWrite.js +18 -0
- package/dist/_internal/server/jobs/handlers/onReviewWrite.js +18 -0
- package/dist/_internal/server/jobs/handlers/onScamReportUpdate.js +18 -0
- package/dist/_internal/server/jobs/handlers/onStoreWrite.js +18 -0
- package/dist/_internal/server/jobs/handlers/searchIndexBuild.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/searchIndexBuild.js +2 -0
- package/dist/_internal/server/jobs/handlers/triggerEventRaffle.d.ts +8 -0
- package/dist/_internal/server/jobs/handlers/triggerEventRaffle.js +15 -6
- package/dist/_internal/shared/actions/action-registry.js +29 -0
- package/dist/_internal/shared/cache/revalidate-targets.d.ts +0 -1
- package/dist/_internal/shared/cache/revalidate-targets.js +10 -8
- package/dist/_internal/shared/features/events/config.d.ts +14 -0
- package/dist/_internal/shared/features/events/config.js +14 -0
- package/dist/_internal/shared/features/notifications/action-url.js +8 -0
- package/dist/_internal/shared/features/site-settings/types.d.ts +11 -0
- package/dist/_internal/shared/features/sticker-trace/codec.d.ts +81 -0
- package/dist/_internal/shared/features/sticker-trace/codec.js +30 -0
- package/dist/_internal/shared/features/sticker-trace/config.d.ts +81 -0
- package/dist/_internal/shared/features/sticker-trace/config.js +177 -0
- package/dist/_internal/shared/features/sticker-trace/cut-contour.d.ts +20 -0
- package/dist/_internal/shared/features/sticker-trace/cut-contour.js +60 -0
- package/dist/_internal/shared/features/sticker-trace/export/build-export-set.d.ts +65 -0
- package/dist/_internal/shared/features/sticker-trace/export/build-export-set.js +176 -0
- package/dist/_internal/shared/features/sticker-trace/export/cut-svg.d.ts +56 -0
- package/dist/_internal/shared/features/sticker-trace/export/cut-svg.js +120 -0
- package/dist/_internal/shared/features/sticker-trace/export/machine-profiles.d.ts +92 -0
- package/dist/_internal/shared/features/sticker-trace/export/machine-profiles.js +153 -0
- package/dist/_internal/shared/features/sticker-trace/export/nesting.d.ts +51 -0
- package/dist/_internal/shared/features/sticker-trace/export/nesting.js +110 -0
- package/dist/_internal/shared/features/sticker-trace/export/page-sizes.d.ts +68 -0
- package/dist/_internal/shared/features/sticker-trace/export/page-sizes.js +223 -0
- package/dist/_internal/shared/features/sticker-trace/export/part-tiling.d.ts +51 -0
- package/dist/_internal/shared/features/sticker-trace/export/part-tiling.js +178 -0
- package/dist/_internal/shared/features/sticker-trace/export/simplify-budget.d.ts +26 -0
- package/dist/_internal/shared/features/sticker-trace/export/simplify-budget.js +134 -0
- package/dist/_internal/shared/features/sticker-trace/export/vectorise-art.d.ts +40 -0
- package/dist/_internal/shared/features/sticker-trace/export/vectorise-art.js +195 -0
- package/dist/_internal/shared/features/sticker-trace/index.d.ts +2 -0
- package/dist/_internal/shared/features/sticker-trace/index.js +59 -0
- package/dist/_internal/shared/features/sticker-trace/math/catmull-rom.d.ts +11 -0
- package/dist/_internal/shared/features/sticker-trace/math/catmull-rom.js +66 -0
- package/dist/_internal/shared/features/sticker-trace/math/edt.d.ts +27 -0
- package/dist/_internal/shared/features/sticker-trace/math/edt.js +165 -0
- package/dist/_internal/shared/features/sticker-trace/math/label.d.ts +15 -0
- package/dist/_internal/shared/features/sticker-trace/math/label.js +156 -0
- package/dist/_internal/shared/features/sticker-trace/math/marching-squares.d.ts +16 -0
- package/dist/_internal/shared/features/sticker-trace/math/marching-squares.js +242 -0
- package/dist/_internal/shared/features/sticker-trace/math/morphology.d.ts +46 -0
- package/dist/_internal/shared/features/sticker-trace/math/morphology.js +168 -0
- package/dist/_internal/shared/features/sticker-trace/math/rasterize-path.d.ts +88 -0
- package/dist/_internal/shared/features/sticker-trace/math/rasterize-path.js +286 -0
- package/dist/_internal/shared/features/sticker-trace/math/rdp.d.ts +10 -0
- package/dist/_internal/shared/features/sticker-trace/math/rdp.js +74 -0
- package/dist/_internal/shared/features/sticker-trace/resume.d.ts +50 -0
- package/dist/_internal/shared/features/sticker-trace/resume.js +160 -0
- package/dist/_internal/shared/features/sticker-trace/trace/cluster.d.ts +20 -0
- package/dist/_internal/shared/features/sticker-trace/trace/cluster.js +132 -0
- package/dist/_internal/shared/features/sticker-trace/trace/cross-profile.d.ts +28 -0
- package/dist/_internal/shared/features/sticker-trace/trace/cross-profile.js +120 -0
- package/dist/_internal/shared/features/sticker-trace/trace/flood.d.ts +21 -0
- package/dist/_internal/shared/features/sticker-trace/trace/flood.js +104 -0
- package/dist/_internal/shared/features/sticker-trace/trace/geometry.d.ts +3 -0
- package/dist/_internal/shared/features/sticker-trace/trace/geometry.js +140 -0
- package/dist/_internal/shared/features/sticker-trace/trace/line-filter.d.ts +15 -0
- package/dist/_internal/shared/features/sticker-trace/trace/line-filter.js +138 -0
- package/dist/_internal/shared/features/sticker-trace/trace/merge.d.ts +27 -0
- package/dist/_internal/shared/features/sticker-trace/trace/merge.js +187 -0
- package/dist/_internal/shared/features/sticker-trace/trace/paper.d.ts +32 -0
- package/dist/_internal/shared/features/sticker-trace/trace/paper.js +147 -0
- package/dist/_internal/shared/features/sticker-trace/trace/prefilter.d.ts +30 -0
- package/dist/_internal/shared/features/sticker-trace/trace/prefilter.js +125 -0
- package/dist/_internal/shared/features/sticker-trace/trace/quantise.d.ts +10 -0
- package/dist/_internal/shared/features/sticker-trace/trace/quantise.js +153 -0
- package/dist/_internal/shared/features/sticker-trace/trace/ridge.d.ts +88 -0
- package/dist/_internal/shared/features/sticker-trace/trace/ridge.js +344 -0
- package/dist/_internal/shared/features/sticker-trace/trace/segment.d.ts +10 -0
- package/dist/_internal/shared/features/sticker-trace/trace/segment.js +251 -0
- package/dist/_internal/shared/features/sticker-trace/types.d.ts +197 -0
- package/dist/_internal/shared/features/sticker-trace/types.js +49 -0
- package/dist/_internal/shared/listing-types/_registry.d.ts +11 -0
- package/dist/_internal/shared/listing-types/art/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/art/config.js +1 -0
- package/dist/_internal/shared/listing-types/auction/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/auction/config.js +1 -0
- package/dist/_internal/shared/listing-types/classified/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/classified/config.js +1 -0
- package/dist/_internal/shared/listing-types/digital-code/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/digital-code/config.js +1 -0
- package/dist/_internal/shared/listing-types/feature-flags.d.ts +7 -0
- package/dist/_internal/shared/listing-types/feature-flags.js +9 -0
- package/dist/_internal/shared/listing-types/live/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/live/config.js +1 -0
- package/dist/_internal/shared/listing-types/pre-order/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/pre-order/config.js +1 -0
- package/dist/_internal/shared/listing-types/prize-draw/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/prize-draw/config.js +1 -0
- package/dist/_internal/shared/listing-types/standard/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/standard/config.js +1 -0
- package/dist/_internal/shared/listing-types/stickers/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/stickers/config.js +1 -0
- package/dist/_internal/shared/media/limits.d.ts +12 -2
- package/dist/_internal/shared/media/limits.js +12 -2
- package/dist/_internal/shared/search/artifact-url.d.ts +35 -0
- package/dist/_internal/shared/search/artifact-url.js +119 -0
- package/dist/_internal/shared/search/index-record.d.ts +68 -0
- package/dist/_internal/shared/search/index-record.js +96 -0
- package/dist/_internal/shared/search/match.d.ts +65 -0
- package/dist/_internal/shared/search/match.js +263 -0
- package/dist/client.d.ts +8 -7
- package/dist/client.js +12 -5
- package/dist/configs/eslint.js +22 -2
- package/dist/constants/api-endpoints.d.ts +68 -0
- package/dist/constants/api-endpoints.js +32 -0
- package/dist/constants/field-names.d.ts +7 -1
- package/dist/constants/field-names.js +36 -1
- package/dist/contracts/repository.d.ts +8 -7
- package/dist/contracts/search.d.ts +2 -2
- package/dist/contracts/table.d.ts +3 -3
- package/dist/contracts/table.js +3 -3
- package/dist/core/contact-submissions.repository.js +2 -2
- package/dist/core/integration-keys.js +7 -1
- package/dist/core/newsletter.repository.js +2 -2
- package/dist/core/site-config.js +1 -1
- package/dist/errors/error-codes.d.ts +30 -0
- package/dist/errors/error-codes.js +30 -0
- package/dist/errors/error-display-map.js +40 -1
- package/dist/features/account/api/[userId]/addresses/route.js +1 -1
- package/dist/features/account/components/UserBidsView.js +1 -1
- package/dist/features/account/components/index.d.ts +0 -8
- package/dist/features/account/components/index.js +0 -4
- package/dist/features/addresses/repository/addresses.repository.d.ts +17 -6
- package/dist/features/addresses/repository/addresses.repository.js +65 -9
- package/dist/features/addresses/schemas/firestore.d.ts +17 -6
- package/dist/features/admin/actions/notification-actions.js +15 -6
- package/dist/features/admin/api/bids/route.js +1 -1
- package/dist/features/admin/api/coupons/route.js +1 -1
- package/dist/features/admin/api/products/route.js +1 -1
- package/dist/features/admin/api/reviews/route.js +1 -1
- package/dist/features/admin/components/AdminAdEditorView.js +19 -5
- package/dist/features/admin/components/AdminAuditLogView.js +1 -1
- package/dist/features/admin/components/AdminBlogEditorView.js +0 -1
- package/dist/features/admin/components/AdminBlogView.js +1 -1
- package/dist/features/admin/components/AdminBrandsView.js +17 -1
- package/dist/features/admin/components/AdminCategoryEditorView.js +0 -1
- package/dist/features/admin/components/AdminContactView.js +1 -1
- package/dist/features/admin/components/AdminEmployeeEditorView.js +1 -3
- package/dist/features/admin/components/AdminNewsletterView.js +1 -1
- package/dist/features/admin/components/AdminOrdersView.js +9 -2
- package/dist/features/admin/components/AdminPayoutsView.js +1 -1
- package/dist/features/admin/components/AdminPrizeDrawsView.js +11 -0
- package/dist/features/admin/components/AdminProductEditorView.d.ts +10 -1
- package/dist/features/admin/components/AdminProductEditorView.js +24 -37
- package/dist/features/admin/components/AdminProductsView.js +10 -3
- package/dist/features/admin/components/AdminScammersView.js +1 -1
- package/dist/features/admin/components/AdminSectionsView.js +21 -5
- package/dist/features/admin/components/AdminSessionsView.js +1 -1
- package/dist/features/admin/components/AdminSublistingCategoriesView.js +22 -1
- package/dist/features/admin/components/AdminTeamView.js +1 -1
- package/dist/features/admin/components/AdminTesterChecklistItemEditorView.js +0 -1
- package/dist/features/admin/components/AdminUsersView.js +1 -1
- package/dist/features/admin/components/DataListingView.d.ts +2 -1
- package/dist/features/admin/components/DataListingView.js +9 -4
- package/dist/features/admin/components/analytics/AdminPageViewsReportView.js +0 -2
- package/dist/features/admin/hooks/useAdminListing.d.ts +1 -1
- package/dist/features/admin/repository/site-settings.repository.js +8 -3
- package/dist/features/admin/schemas/firestore.d.ts +62 -1
- package/dist/features/admin/schemas/firestore.js +32 -0
- package/dist/features/admin/types/index.d.ts +1 -1
- package/dist/features/auctions/actions/bid-actions.d.ts +2 -2
- package/dist/features/auctions/actions/bid-actions.js +16 -4
- package/dist/features/auctions/api/route.js +1 -1
- package/dist/features/auctions/components/AuctionBottomActions.js +4 -0
- package/dist/features/auctions/repository/bid.repository.d.ts +46 -4
- package/dist/features/auctions/repository/bid.repository.js +114 -13
- package/dist/features/auctions/schemas/firestore.d.ts +24 -4
- package/dist/features/auctions/schemas/firestore.js +1 -0
- package/dist/features/auth/actions/profile-actions.js +30 -4
- package/dist/features/auth/components/index.d.ts +0 -1
- package/dist/features/auth/components/index.js +0 -1
- package/dist/features/auth/repository/session.repository.d.ts +17 -3
- package/dist/features/auth/repository/session.repository.js +46 -5
- package/dist/features/auth/repository/token.repository.d.ts +27 -4
- package/dist/features/auth/repository/token.repository.js +65 -11
- package/dist/features/auth/repository/user.repository.d.ts +33 -7
- package/dist/features/auth/repository/user.repository.js +100 -15
- package/dist/features/auth/schemas/firestore.d.ts +63 -12
- package/dist/features/auth/schemas/firestore.js +11 -0
- package/dist/features/before-after/api/route.js +1 -1
- package/dist/features/blog/components/BlogListView.js +16 -3
- package/dist/features/blog/components/BlogPostView.js +1 -1
- package/dist/features/blog/schemas/index.d.ts +2 -2
- package/dist/features/cart/api/route.js +1 -1
- package/dist/features/catalogue/repository/catalogue.repository.d.ts +7 -7
- package/dist/features/catalogue/repository/catalogue.repository.js +28 -10
- package/dist/features/catalogue/schemas/firestore.d.ts +11 -0
- package/dist/features/categories/api/route.js +10 -10
- package/dist/features/categories/components/CategoryDetailPageView.js +35 -5
- package/dist/features/categories/components/CategorySelectorCreate.js +0 -1
- package/dist/features/categories/components/index.d.ts +0 -2
- package/dist/features/categories/components/index.js +0 -1
- package/dist/features/categories/repository/categories.repository.d.ts +1 -0
- package/dist/features/categories/repository/categories.repository.js +31 -1
- package/dist/features/categories/schemas/index.d.ts +2 -2
- package/dist/features/categories/utils/taxonomy-visual.d.ts +21 -0
- package/dist/features/categories/utils/taxonomy-visual.js +58 -0
- package/dist/features/checkout/actions/checkout-value-otp-actions.js +8 -1
- package/dist/features/collections/api/[slug]/route.js +3 -3
- package/dist/features/collections/api/route.js +1 -1
- package/dist/features/consultation/api/route.js +1 -1
- package/dist/features/consultation/repository/consultations.repository.js +1 -1
- package/dist/features/consultation/types/index.d.ts +1 -1
- package/dist/features/contact/components/ContactForm.js +1 -3
- package/dist/features/corporate/api/route.js +1 -1
- package/dist/features/corporate/repository/corporate.repository.js +1 -1
- package/dist/features/corporate/types/index.d.ts +1 -1
- package/dist/features/cron/jobs/auction-expiry.job.js +1 -1
- package/dist/features/cron/jobs/preorder-reminder.job.js +1 -1
- package/dist/features/events/actions/event-actions.d.ts +14 -0
- package/dist/features/events/actions/event-actions.js +82 -3
- package/dist/features/events/api/[id]/route.js +2 -2
- package/dist/features/events/components/AdminEventEditorView.js +22 -3
- package/dist/features/events/components/AdminEventsView.js +32 -8
- package/dist/features/events/components/EventRafflesSection.d.ts +34 -1
- package/dist/features/events/components/EventRafflesSection.js +52 -3
- package/dist/features/events/components/SpinWheelView.d.ts +2 -2
- package/dist/features/events/components/SpinWheelView.js +41 -4
- package/dist/features/events/repository/event-entry.repository.d.ts +14 -2
- package/dist/features/events/repository/event-entry.repository.js +41 -7
- package/dist/features/events/schemas/firestore.d.ts +17 -4
- package/dist/features/events/types/index.d.ts +37 -0
- package/dist/features/faq/repository/faqs.repository.js +1 -1
- package/dist/features/faq/schemas/index.d.ts +5 -2
- package/dist/features/faq/schemas/index.js +6 -0
- package/dist/features/faq/types/index.d.ts +0 -1
- package/dist/features/grouped/components/GroupedListingEditorView.js +17 -2
- package/dist/features/homepage/api/carousel/[id]/route.js +1 -1
- package/dist/features/homepage/api/carousel/route.js +4 -4
- package/dist/features/homepage/api/route.js +2 -2
- package/dist/features/homepage/api/sections/route.js +1 -1
- package/dist/features/homepage/components/AnnouncementBar.d.ts +1 -9
- package/dist/features/homepage/components/AnnouncementBar.js +13 -19
- package/dist/features/homepage/components/MarketplaceHomepageView.js +6 -1
- package/dist/features/homepage/components/index.d.ts +0 -2
- package/dist/features/homepage/components/index.js +0 -1
- package/dist/features/homepage/repository/homepage.repository.js +1 -1
- package/dist/features/homepage/schemas/firestore.d.ts +6 -0
- package/dist/features/homepage/schemas/index.d.ts +10 -10
- package/dist/features/layout/AppLayoutShell.d.ts +12 -3
- package/dist/features/layout/AppLayoutShell.js +65 -13
- package/dist/features/layout/BottomActionsContext.d.ts +16 -0
- package/dist/features/layout/BottomActionsContext.js +5 -0
- package/dist/features/layout/BottomChrome.js +48 -9
- package/dist/features/layout/BottomNavbar.d.ts +14 -0
- package/dist/features/layout/BottomNavbar.js +43 -3
- package/dist/features/layout/MainNavbar.d.ts +11 -0
- package/dist/features/layout/NavbarLayout.d.ts +3 -0
- package/dist/features/layout/NavbarLayout.js +88 -11
- package/dist/features/layout/SideActionDock.d.ts +1 -0
- package/dist/features/layout/SideActionDock.js +219 -0
- package/dist/features/layout/TitleBarLayout.d.ts +1 -3
- package/dist/features/layout/TitleBarLayout.js +3 -4
- package/dist/features/layout/cta-anchor.d.ts +31 -0
- package/dist/features/layout/cta-anchor.js +31 -0
- package/dist/features/layout/hooks/useBottomActions.d.ts +7 -0
- package/dist/features/layout/hooks/useBottomActions.js +6 -1
- package/dist/features/layout/index.d.ts +9 -1
- package/dist/features/layout/index.js +9 -1
- package/dist/features/layout/useCtaReveal.d.ts +24 -0
- package/dist/features/layout/useCtaReveal.js +82 -0
- package/dist/features/layout/useHoldToOpen.d.ts +41 -0
- package/dist/features/layout/useHoldToOpen.js +84 -0
- package/dist/features/lottery/repository/lottery-entry.repository.d.ts +18 -3
- package/dist/features/lottery/repository/lottery-entry.repository.js +48 -6
- package/dist/features/lottery/schemas/firestore.d.ts +33 -5
- package/dist/features/loyalty/api/balance/route.js +1 -1
- package/dist/features/loyalty/api/history/route.js +1 -1
- package/dist/features/media/MediaImage.js +0 -1
- package/dist/features/media/MediaPickerModal.js +7 -1
- package/dist/features/media/modals/ImageEditor.js +5 -1
- package/dist/features/media/modals/VideoThumbnailSelector.js +17 -1
- package/dist/features/media/repository/media-assets.repository.d.ts +19 -0
- package/dist/features/media/upload/CameraCapture.js +16 -1
- package/dist/features/media/upload/ImageUpload.js +42 -4
- package/dist/features/media/upload/MediaUploadField.js +69 -13
- package/dist/features/media/upload/downscale-image.d.ts +56 -0
- package/dist/features/media/upload/downscale-image.js +150 -0
- package/dist/features/messages/components/ConversationThreadView.js +45 -2
- package/dist/features/messages/components/MessageSellerButton.d.ts +15 -1
- package/dist/features/messages/components/MessageSellerButton.js +10 -2
- package/dist/features/messages/schemas/firestore.d.ts +15 -0
- package/dist/features/orders/hooks/useOrders.js +4 -2
- package/dist/features/orders/repository/orders.repository.d.ts +15 -4
- package/dist/features/orders/repository/orders.repository.js +52 -20
- package/dist/features/orders/schemas/firestore.d.ts +15 -4
- package/dist/features/orders/schemas/index.d.ts +8 -8
- package/dist/features/orders/types/index.d.ts +2 -2
- package/dist/features/payments/repository/payments.repository.js +2 -2
- package/dist/features/payments/repository/payout.repository.d.ts +23 -6
- package/dist/features/payments/repository/payout.repository.js +63 -23
- package/dist/features/payments/repository/saved-payment-methods.repository.d.ts +17 -6
- package/dist/features/payments/repository/saved-payment-methods.repository.js +59 -11
- package/dist/features/payments/schemas/firestore.d.ts +14 -3
- package/dist/features/payments/schemas/saved-methods-firestore.d.ts +12 -1
- package/dist/features/pre-orders/api/[slug]/route.js +1 -1
- package/dist/features/pre-orders/api/admin/route.js +1 -1
- package/dist/features/pre-orders/api/route.js +1 -1
- package/dist/features/pre-orders/repository/pre-orders.repository.d.ts +3 -3
- package/dist/features/pre-orders/repository/pre-orders.repository.js +6 -6
- package/dist/features/products/api/[id]/route.js +1 -1
- package/dist/features/products/components/ListingBottomActions.js +4 -0
- package/dist/features/products/components/ProductDetailPageView.js +2 -1
- package/dist/features/products/components/ProductGalleryClient.js +2 -2
- package/dist/features/products/components/ProductGrid.js +12 -1
- package/dist/features/products/components/index.d.ts +0 -2
- package/dist/features/products/components/index.js +0 -1
- package/dist/features/products/config/listing-type-listing-config.d.ts +0 -3
- package/dist/features/products/config/listing-type-listing-config.js +27 -2
- package/dist/features/products/constants/listing-tabs.d.ts +9 -0
- package/dist/features/products/constants/listing-tabs.js +3 -2
- package/dist/features/products/hooks/useProducts.js +2 -2
- package/dist/features/products/repository/products.repository.js +13 -1
- package/dist/features/products/schemas/index.d.ts +2 -2
- package/dist/features/products/types/index.d.ts +1 -1
- package/dist/features/promotions/api/route.js +1 -1
- package/dist/features/reviews/actions/review-actions.d.ts +6 -6
- package/dist/features/reviews/actions/review-actions.js +22 -8
- package/dist/features/reviews/components/ReviewDetailPageView.js +13 -1
- package/dist/features/reviews/components/ReviewsIndexPageView.js +21 -4
- package/dist/features/reviews/repository/reviews.repository.d.ts +19 -7
- package/dist/features/reviews/repository/reviews.repository.js +63 -12
- package/dist/features/reviews/schemas/firestore.d.ts +13 -2
- package/dist/features/reviews/schemas/index.d.ts +2 -2
- package/dist/features/scams/actions/scam-actions.js +16 -4
- package/dist/features/search/components/SearchResultsPageView.js +2 -1
- package/dist/features/search/hooks/useSearchIndex.d.ts +34 -0
- package/dist/features/search/hooks/useSearchIndex.js +172 -0
- package/dist/features/search/repository/search.repository.js +2 -2
- package/dist/features/seller/actions/offer-actions.d.ts +5 -5
- package/dist/features/seller/actions/offer-actions.js +38 -8
- package/dist/features/seller/actions/seller-actions.js +34 -7
- package/dist/features/seller/api/coupons/route.js +1 -1
- package/dist/features/seller/api/products/route.js +1 -1
- package/dist/features/seller/api/store/route.js +1 -1
- package/dist/features/seller/components/SellerBidsView.js +1 -1
- package/dist/features/seller/components/SellerCouponsView.js +1 -1
- package/dist/features/seller/components/SellerListingTypeView.js +9 -1
- package/dist/features/seller/components/SellerOrdersView.js +1 -1
- package/dist/features/seller/components/SellerPayoutSettingsView.js +0 -1
- package/dist/features/seller/components/SellerPayoutsView.js +1 -1
- package/dist/features/seller/components/SellerProductShell.js +0 -1
- package/dist/features/seller/components/SellerProductsView.js +1 -1
- package/dist/features/seller/components/SellerShippingView.js +0 -1
- package/dist/features/seller/components/SellerStorefrontView.js +0 -1
- package/dist/features/seller/components/index.d.ts +0 -4
- package/dist/features/seller/components/index.js +0 -2
- package/dist/features/seller/repository/offer.repository.d.ts +15 -3
- package/dist/features/seller/repository/offer.repository.js +43 -7
- package/dist/features/seller/schemas/firestore.d.ts +14 -3
- package/dist/features/seller/schemas/index.d.ts +4 -4
- package/dist/features/seller/schemas/order-forms.d.ts +39 -0
- package/dist/features/seller/schemas/order-forms.js +37 -0
- package/dist/features/shell/FormShell.js +6 -2
- package/dist/features/shell/QuickFormDrawer.js +15 -1
- package/dist/features/shipments/components/AdminShipmentEditorView.js +0 -1
- package/dist/features/shipments/components/AdminShipmentsView.js +1 -1
- package/dist/features/sticker-trace/components/StickerCommitPanel.d.ts +30 -0
- package/dist/features/sticker-trace/components/StickerCommitPanel.js +93 -0
- package/dist/features/sticker-trace/components/StickerPrintPanel.d.ts +33 -0
- package/dist/features/sticker-trace/components/StickerPrintPanel.js +104 -0
- package/dist/features/sticker-trace/components/StickerRetryPanel.d.ts +20 -0
- package/dist/features/sticker-trace/components/StickerRetryPanel.js +128 -0
- package/dist/features/sticker-trace/components/StickerRunHistoryPanel.d.ts +45 -0
- package/dist/features/sticker-trace/components/StickerRunHistoryPanel.js +84 -0
- package/dist/features/sticker-trace/components/StickerTraceStudio.d.ts +27 -0
- package/dist/features/sticker-trace/components/StickerTraceStudio.js +171 -0
- package/dist/features/sticker-trace/constants/help-content.d.ts +23 -0
- package/dist/features/sticker-trace/constants/help-content.js +172 -0
- package/dist/features/sticker-trace/hooks/useLiveTrace.d.ts +27 -0
- package/dist/features/sticker-trace/hooks/useLiveTrace.js +160 -0
- package/dist/features/sticker-trace/hooks/useStickerTraceProgress.d.ts +22 -0
- package/dist/features/sticker-trace/hooks/useStickerTraceProgress.js +106 -0
- package/dist/features/sticker-trace/repository/sticker-sheets.repository.d.ts +113 -0
- package/dist/features/sticker-trace/repository/sticker-sheets.repository.js +146 -0
- package/dist/features/sticker-trace/repository/sticker-trace-runs.repository.d.ts +132 -0
- package/dist/features/sticker-trace/repository/sticker-trace-runs.repository.js +174 -0
- package/dist/features/sticker-trace/schemas/firestore.d.ts +248 -0
- package/dist/features/sticker-trace/schemas/firestore.js +79 -0
- package/dist/features/sticker-trace/server.d.ts +8 -0
- package/dist/features/sticker-trace/server.js +51 -0
- package/dist/features/stores/actions/store-query-actions.d.ts +2 -2
- package/dist/features/stores/actions/store-query-actions.js +7 -2
- package/dist/features/stores/api/[storeSlug]/auctions/route.js +3 -3
- package/dist/features/stores/api/[storeSlug]/products/route.js +3 -3
- package/dist/features/stores/api/[storeSlug]/reviews/route.js +1 -1
- package/dist/features/stores/api/[storeSlug]/route.js +1 -1
- package/dist/features/stores/components/StoreNavTabs.js +17 -2
- package/dist/features/stores/manifest.js +8 -4
- package/dist/features/support/repository/support.repository.d.ts +14 -3
- package/dist/features/support/repository/support.repository.js +93 -13
- package/dist/features/support/schemas/firestore.d.ts +16 -5
- package/dist/features/tester/seed-data/authored/admin__events-raffles-spin.js +61 -19
- package/dist/features/tester/seed-data/authored/buying__browsing-search.js +19 -3
- package/dist/features/tester/seed-data/authored/content-discovery__authenticity-checker.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/content-discovery__authenticity-checker.js +243 -0
- package/dist/features/tester/seed-data/authored/content-discovery__events.js +36 -10
- package/dist/features/tester/seed-data/authored/content-discovery__overflowing-pill-rows.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/content-discovery__overflowing-pill-rows.js +209 -0
- package/dist/features/tester/seed-data/authored/design-ux__header-brand-mark-centring.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/design-ux__header-brand-mark-centring.js +90 -0
- package/dist/features/tester/seed-data/authored/design-ux__header-controls-and-messages.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/design-ux__header-controls-and-messages.js +85 -0
- package/dist/features/tester/seed-data/authored/index.js +10 -0
- package/dist/features/tester/seed-data/authored/selling__media-limits.js +19 -17
- package/dist/features/tester/seed-data/authored/selling__product-upload-details.js +10 -5
- package/dist/features/tester/seed-data/authored/tools__sticker-tracer.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/tools__sticker-tracer.js +283 -0
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +235 -22
- package/dist/index.d.ts +10 -23
- package/dist/index.js +27 -19
- package/dist/next/api/routeHandler.js +9 -0
- package/dist/next/routing/require-record.d.ts +13 -0
- package/dist/next/routing/require-record.js +62 -0
- package/dist/next/routing/route-map.d.ts +4 -4
- package/dist/next/routing/route-map.js +25 -3
- package/dist/providers/db-firebase/base.d.ts +4 -2
- package/dist/providers/db-firebase/base.js +21 -5
- package/dist/providers/db-firebase/base.repository.d.ts +11 -9
- package/dist/providers/db-firebase/base.repository.js +19 -0
- package/dist/providers/db-firebase/rtdb-paths.d.ts +12 -0
- package/dist/providers/db-firebase/rtdb-paths.js +12 -0
- package/dist/react/ErrorBoundary.js +6 -1
- package/dist/react/hooks/realtime-event-constants.d.ts +1 -0
- package/dist/react/hooks/realtime-event-constants.js +8 -0
- package/dist/react/hooks/useEntityDelete.js +5 -1
- package/dist/react/hooks/useMessage.js +5 -1
- package/dist/repositories/index.d.ts +2 -2
- package/dist/repositories/index.js +6 -1
- package/dist/schemas/registry.d.ts +13 -20
- package/dist/schemas/registry.js +31 -14
- package/dist/schemas/webhooks/google-oauth.d.ts +2 -2
- package/dist/security/encrypted.d.ts +117 -0
- package/dist/security/encrypted.js +100 -0
- package/dist/security/index.d.ts +5 -2
- package/dist/security/index.js +16 -2
- package/dist/security/pii-accessors.d.ts +71 -0
- package/dist/security/pii-accessors.js +150 -0
- package/dist/security/pii-encrypt.d.ts +0 -8
- package/dist/security/pii-encrypt.js +22 -15
- package/dist/security/pii-mask.d.ts +18 -0
- package/dist/security/pii-mask.js +27 -0
- package/dist/seed/_helpers/search-txt-wrappers.d.ts +6 -6
- package/dist/seed/_helpers/search-txt-wrappers.js +22 -0
- package/dist/seed/addresses-seed-data.d.ts +2 -2
- package/dist/seed/authenticity-knowledge-seed-data.d.ts +34 -0
- package/dist/seed/authenticity-knowledge-seed-data.js +1277 -0
- package/dist/seed/bids-seed-data.d.ts +2 -2
- package/dist/seed/bids-seed-data.js +23 -2
- package/dist/seed/events-seed-data.d.ts +2 -2
- package/dist/seed/events-seed-data.js +5 -0
- package/dist/seed/faq-seed-data.js +175 -5
- package/dist/seed/homepage-sections-seed-data.js +39 -23
- package/dist/seed/index.d.ts +1 -0
- package/dist/seed/index.js +1 -0
- package/dist/seed/lottery-entries-seed-data.d.ts +2 -2
- package/dist/seed/offers-seed-data.d.ts +2 -2
- package/dist/seed/offers-seed-data.js +1 -1
- package/dist/seed/orders-seed-data.d.ts +2 -2
- package/dist/seed/payouts-seed-data.d.ts +2 -2
- package/dist/seed/payouts-seed-data.js +1 -1
- package/dist/seed/reviews-seed-data.d.ts +1 -2
- package/dist/seed/seed-purpose.js +7 -0
- package/dist/seed/sessions-seed-data.d.ts +2 -2
- package/dist/seed/sessions-seed-data.js +5 -0
- package/dist/seed/site-settings-seed-data.js +1 -1
- package/dist/seed/support-tickets-seed-data.d.ts +2 -2
- package/dist/seed/support-tickets-seed-data.js +1 -1
- package/dist/seed/users-seed-data.d.ts +2 -2
- package/dist/seed/users-seed-data.js +1 -1
- package/dist/seo/json-ld.js +5 -1
- package/dist/server.d.ts +18 -3
- package/dist/server.js +59 -3
- package/dist/styles.css +237 -1
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/Button.js +7 -1
- package/dist/ui/components/FilterChipGroup.d.ts +11 -0
- package/dist/ui/components/FilterChipGroup.js +1 -1
- package/dist/ui/components/ImageGallery.js +6 -1
- package/dist/ui/components/ListingLayout.js +14 -8
- package/dist/ui/components/OverflowRow.d.ts +92 -0
- package/dist/ui/components/OverflowRow.js +203 -0
- package/dist/ui/components/OverflowRow.style.css +235 -0
- package/dist/ui/components/RecordDetailModal.js +14 -1
- package/dist/ui/components/RowActionMenu.js +5 -1
- package/dist/ui/components/SideDrawer.js +5 -1
- package/dist/ui/components/SideModal.js +9 -1
- package/dist/ui/components/TabBarShell.d.ts +12 -1
- package/dist/ui/components/TabBarShell.js +3 -2
- package/dist/ui/components/Tabs.js +36 -24
- package/dist/ui/components/Toast.d.ts +3 -24
- package/dist/ui/components/Toast.js +2 -19
- package/dist/ui/components/TopBannerPortal.d.ts +15 -0
- package/dist/ui/components/TopBannerPortal.js +19 -0
- package/dist/ui/components/UnsavedChangesModal.js +5 -1
- package/dist/ui/components/index.style.css +1 -0
- package/dist/ui/components/row-collapse.d.ts +15 -0
- package/dist/ui/components/row-collapse.js +127 -0
- package/dist/ui/components/toast-context.d.ts +63 -0
- package/dist/ui/components/toast-context.js +48 -0
- package/dist/ui/components/top-banner-slot.d.ts +37 -0
- package/dist/ui/components/top-banner-slot.js +45 -0
- package/dist/ui/icons/icon-registry.d.ts +6 -0
- package/dist/ui/icons/icon-registry.js +17 -1
- package/dist/ui/index.d.ts +2 -0
- package/dist/ui/index.js +1 -0
- package/dist/utils/array.helper.d.ts +2 -2
- package/dist/utils/array.helper.js +5 -5
- package/dist/utils/id-generators.d.ts +6 -0
- package/dist/utils/id-generators.js +10 -0
- package/dist/utils/media-url.js +0 -2
- package/dist/utils/search-txt-builders.d.ts +28 -4
- package/dist/utils/search-txt-builders.js +41 -1
- package/dist/validation/schemas.d.ts +8 -8
- package/package.json +6 -2
- package/scripts/seed-cli.mjs +3 -0
- package/dist/features/account/components/UserAddressesView.d.ts +0 -13
- package/dist/features/account/components/UserAddressesView.js +0 -5
- package/dist/features/account/components/UserNotificationsView.d.ts +0 -15
- package/dist/features/account/components/UserNotificationsView.js +0 -12
- package/dist/features/account/components/UserSettingsView.d.ts +0 -16
- package/dist/features/account/components/UserSettingsView.js +0 -13
- package/dist/features/account/components/UserSupportView.d.ts +0 -3
- package/dist/features/account/components/UserSupportView.js +0 -166
- package/dist/features/admin/components/DemoSeedView.d.ts +0 -11
- package/dist/features/admin/components/DemoSeedView.js +0 -5
- package/dist/features/auth/components/AuthStatusPanel.d.ts +0 -10
- package/dist/features/auth/components/AuthStatusPanel.js +0 -22
- package/dist/features/categories/components/CategoryProductsView.d.ts +0 -45
- package/dist/features/categories/components/CategoryProductsView.js +0 -21
- package/dist/features/homepage/components/HowItWorksInfoView.d.ts +0 -36
- package/dist/features/homepage/components/HowItWorksInfoView.js +0 -10
- package/dist/features/layout/BottomActions.d.ts +0 -1
- package/dist/features/layout/BottomActions.js +0 -311
- package/dist/features/products/components/ProductsView.d.ts +0 -6
- package/dist/features/products/components/ProductsView.js +0 -5
- package/dist/features/seller/components/SellerStoreView.d.ts +0 -8
- package/dist/features/seller/components/SellerStoreView.js +0 -8
|
@@ -12,11 +12,11 @@ import { eventEntryRepository } from "../repository/event-entry.repository.js";
|
|
|
12
12
|
import { ERROR_MESSAGES, AuthorizationError, ValidationError, NotFoundError, } from "../../../errors/index.js";
|
|
13
13
|
import { resolveDate } from "../../../utils/index.js";
|
|
14
14
|
import { hashGuestIdentity } from "../../../security/rate-limit.js";
|
|
15
|
+
import { RAFFLE_MAX_ENTRIES_PER_USER } from "../../../_internal/shared/features/events/config.js";
|
|
15
16
|
const ERR_EVENT_NOT_FOUND = "Event not found";
|
|
16
17
|
import { coerceMediaField, getMediaUrl } from "../../../utils/index.js";
|
|
17
18
|
import { finalizeStagedMediaField, finalizeStagedMediaObject, finalizeStagedMediaObjectArray, } from "../../media/finalize.js";
|
|
18
19
|
import { hidePublicTestData } from "../../../_internal/server/features/tester/visibility.js";
|
|
19
|
-
import { maskName } from "../../../security/pii-mask.js";
|
|
20
20
|
// --- Admin: Create Event --------------------------------------------------
|
|
21
21
|
export async function createEvent(adminId, input) {
|
|
22
22
|
const finalizedCoverUrl = await finalizeStagedMediaField(getMediaUrl(input.coverImage) ??
|
|
@@ -193,6 +193,11 @@ export async function getEventSpinResults(eventId, limit = 10) {
|
|
|
193
193
|
return [];
|
|
194
194
|
const prizeTitleById = new Map((event.spinPrizes ?? []).map((p) => [p.id, p.label]));
|
|
195
195
|
const entries = await eventEntryRepository.getRecentSpinResults(event.id, limit);
|
|
196
|
+
/*
|
|
197
|
+
* Spin results are shown publicly, so the entrant name is MASKED — and it
|
|
198
|
+
* has to be masked by `maskedEntry`, which decrypts first. `maskName` on the
|
|
199
|
+
* stored value answers "Anonymous" for every row by design.
|
|
200
|
+
*/
|
|
196
201
|
return entries.map((entry) => ({
|
|
197
202
|
id: entry.id,
|
|
198
203
|
/*
|
|
@@ -211,7 +216,15 @@ export async function getEventSpinResults(eventId, limit = 10) {
|
|
|
211
216
|
* on for its own label. A caller's OWN spins are served unmasked by
|
|
212
217
|
* `getUserSpinResultsForEvent` — masking is for other people's.
|
|
213
218
|
*/
|
|
214
|
-
|
|
219
|
+
/*
|
|
220
|
+
* `maskedEntry`, not `maskName` on the stored value. `maskName`
|
|
221
|
+
* refuses ciphertext by design and answers "Anonymous", so this
|
|
222
|
+
* public list showed every entrant as Anonymous. `maskedEntry`
|
|
223
|
+
* decrypts first, then masks.
|
|
224
|
+
*/
|
|
225
|
+
userDisplayName: entry.userId
|
|
226
|
+
? eventEntryRepository.maskedEntry(entry).userDisplayName
|
|
227
|
+
: undefined,
|
|
215
228
|
isGuest: !entry.userId,
|
|
216
229
|
spinPrizeId: entry.spinPrizeId,
|
|
217
230
|
spinPrizeTitle: entry.spinPrizeId ? prizeTitleById.get(entry.spinPrizeId) : undefined,
|
|
@@ -238,13 +251,35 @@ export async function getUserSpinResultsForEvent(eventId, userId) {
|
|
|
238
251
|
const entries = await eventEntryRepository.getUserSpinResults(event.id, userId);
|
|
239
252
|
return entries.map((entry) => ({
|
|
240
253
|
id: entry.id,
|
|
241
|
-
userDisplayName: entry.userDisplayName,
|
|
254
|
+
userDisplayName: eventEntryRepository.maskedEntry(entry).userDisplayName,
|
|
242
255
|
isGuest: false,
|
|
243
256
|
spinPrizeId: entry.spinPrizeId,
|
|
244
257
|
spinPrizeTitle: entry.spinPrizeId ? prizeTitleById.get(entry.spinPrizeId) : undefined,
|
|
258
|
+
// Safe here and ONLY here — `userId` comes from a verified session, so
|
|
259
|
+
// this is the caller's own prize. The public feed must not carry it.
|
|
260
|
+
spinPrizeCouponCode: entry.spinPrizeCouponCode ?? undefined,
|
|
245
261
|
spinWonAt: entry.spinWonAt ? new Date(entry.spinWonAt).toISOString() : undefined,
|
|
246
262
|
}));
|
|
247
263
|
}
|
|
264
|
+
/**
|
|
265
|
+
* Has this signed-in user already entered this event?
|
|
266
|
+
*
|
|
267
|
+
* Exists so the participate page can render a raffle's already-entered state on
|
|
268
|
+
* FIRST PAINT. The form tracked it in `useState(0)`, which a reload reset — so
|
|
269
|
+
* a returning entrant saw an inviting empty form, submitted, and was refused.
|
|
270
|
+
* A refusal the page could have predicted is a worse experience than a form
|
|
271
|
+
* that says so up front.
|
|
272
|
+
*
|
|
273
|
+
* Reuses `hasUserEntered` (one `(eventId, userId)` equality pair, an index that
|
|
274
|
+
* is already proven by `countUserEntries`) rather than counting, because the
|
|
275
|
+
* limit is one — see `RAFFLE_MAX_ENTRIES_PER_USER`.
|
|
276
|
+
*/
|
|
277
|
+
export async function hasUserEnteredEvent(eventId, userId) {
|
|
278
|
+
const event = await eventRepository.findByIdOrSlug(eventId);
|
|
279
|
+
if (!event)
|
|
280
|
+
return false;
|
|
281
|
+
return eventEntryRepository.hasUserEntered(event.id, userId);
|
|
282
|
+
}
|
|
248
283
|
/**
|
|
249
284
|
* Per-option vote tally for a poll event — replaces the voter-ranked
|
|
250
285
|
* leaderboard for event.type === "poll", where ranking "who happened to
|
|
@@ -398,6 +433,33 @@ export async function enterEvent(eventId, input, user, request) {
|
|
|
398
433
|
throw new ValidationError(ERROR_MESSAGES.EVENT.ALREADY_ENTERED);
|
|
399
434
|
}
|
|
400
435
|
}
|
|
436
|
+
/*
|
|
437
|
+
* 🛑 The same hole, one event type over: a signed-in user could enter the
|
|
438
|
+
* SAME RAFFLE as many times as they liked, and every entry joined the
|
|
439
|
+
* winner pool.
|
|
440
|
+
*
|
|
441
|
+
* Guests were deduped by hashed IP, surveys enforced `maxEntriesPerUser`,
|
|
442
|
+
* polls got the guard documented above — and raffle, the one type where a
|
|
443
|
+
* duplicate entry is a direct and quantifiable unfairness, had none. The
|
|
444
|
+
* client-side guard was inert in two independent ways: `submissionCount` is
|
|
445
|
+
* `useState(0)` that is never hydrated from the server, so a reload resets
|
|
446
|
+
* it; and `maxEntriesPerUser` was not passed to `EventRaffleEntryForm` at
|
|
447
|
+
* all, so it defaulted to 1 while nothing enforced it.
|
|
448
|
+
*
|
|
449
|
+
* The limit is `RAFFLE_MAX_ENTRIES_PER_USER`, shared with the form that
|
|
450
|
+
* renders "you've used all N entry slots" — there is no raffle config to
|
|
451
|
+
* consult (`maxEntriesPerUser` lives on `SurveyConfig` only), and two
|
|
452
|
+
* mirrored literals is how the UI came to claim a limit nothing enforced.
|
|
453
|
+
*
|
|
454
|
+
* Reuses the same `countUserEntries` as the survey and poll paths, so the
|
|
455
|
+
* read cost and the `(eventId, userId)` index it needs are already proven.
|
|
456
|
+
*/
|
|
457
|
+
if (user && event.type === "raffle") {
|
|
458
|
+
const priorEntries = await eventEntryRepository.countUserEntries(resolvedEventId, user.uid);
|
|
459
|
+
if (priorEntries >= RAFFLE_MAX_ENTRIES_PER_USER) {
|
|
460
|
+
throw new ValidationError(ERROR_MESSAGES.EVENT.ALREADY_ENTERED);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
401
463
|
if (event.type === "poll" && event.pollConfig) {
|
|
402
464
|
const validOptionIds = event.pollConfig.options.map((o) => o.id);
|
|
403
465
|
const votes = input.pollVotes ?? [];
|
|
@@ -421,8 +483,25 @@ export async function enterEvent(eventId, input, user, request) {
|
|
|
421
483
|
}
|
|
422
484
|
}
|
|
423
485
|
}
|
|
486
|
+
/*
|
|
487
|
+
* 🛑 `raffle` was absent here, and that is what kept the raffle draw broken
|
|
488
|
+
* even after its pool query was fixed.
|
|
489
|
+
*
|
|
490
|
+
* `runTriggerEventRaffle` builds its pool from `reviewStatus == "approved"`.
|
|
491
|
+
* With raffle missing from this list every live entry was written `pending`,
|
|
492
|
+
* so a freshly-entered raffle still drew from an empty pool — the fix to the
|
|
493
|
+
* query alone would have changed nothing a user could see.
|
|
494
|
+
*
|
|
495
|
+
* A raffle entry is a name and an optional message. There is no submitted
|
|
496
|
+
* content to moderate, which is why it belongs with `poll` and `feedback`
|
|
497
|
+
* rather than behind `survey`'s `entryReviewRequired` opt-in. An admin can
|
|
498
|
+
* still FLAG an individual entry, and flagging correctly drops it from the
|
|
499
|
+
* pool — that is the moderation path, not a standing review queue of every
|
|
500
|
+
* entrant.
|
|
501
|
+
*/
|
|
424
502
|
const autoApprove = event.type === "poll" ||
|
|
425
503
|
event.type === "feedback" ||
|
|
504
|
+
event.type === "raffle" ||
|
|
426
505
|
(event.type === "survey" &&
|
|
427
506
|
!event.surveyConfig?.entryReviewRequired);
|
|
428
507
|
const reviewStatus = autoApprove ? "approved" : "pending";
|
|
@@ -47,7 +47,7 @@ export async function GET(_request, context) {
|
|
|
47
47
|
event.pollConfig?.resultsVisibility === "always") {
|
|
48
48
|
const allEntries = await entriesRepo.findAll({
|
|
49
49
|
filters: `eventId==${id}`,
|
|
50
|
-
|
|
50
|
+
pageSize: 1000,
|
|
51
51
|
});
|
|
52
52
|
const voteCounts = {};
|
|
53
53
|
for (const entry of allEntries.data) {
|
|
@@ -70,7 +70,7 @@ export async function GET(_request, context) {
|
|
|
70
70
|
filters: `eventId==${id},reviewStatus==approved`,
|
|
71
71
|
sort: "points",
|
|
72
72
|
order: "desc",
|
|
73
|
-
|
|
73
|
+
pageSize: 10,
|
|
74
74
|
});
|
|
75
75
|
leaderboard = leaderboardResult.data.map((entry) => {
|
|
76
76
|
const { userId: _userId, userEmail: _userEmail, ...rest } = entry;
|
|
@@ -3,11 +3,14 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
3
3
|
import { normalizeError } from "../../../errors/normalize.js";
|
|
4
4
|
import { useApiMutation } from "@mohasinac/appkit/client";
|
|
5
5
|
import React, { useEffect } from "react";
|
|
6
|
+
import { useTranslations } from "next-intl";
|
|
6
7
|
import { useQuery } from "@tanstack/react-query";
|
|
7
8
|
import { Alert, Button, Div, Grid, Heading, Input, Label, RichTextEditor, Row, Select, Stack, StackedViewShell, TagInput, Text, Textarea, Toggle, } from "../../../ui/index.js";
|
|
8
9
|
import { FormShellContext, useFormShellState, applyZodIssues, FormErrorSummary } from "../../../ui/forms/index.js";
|
|
9
10
|
import { apiClient } from "../../../http/index.js";
|
|
10
11
|
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints.js";
|
|
12
|
+
import { ACTIONS } from "../../../_internal/shared/actions/action-registry.js";
|
|
13
|
+
import { toUserMessage } from "../../../errors/error-display-map.js";
|
|
11
14
|
import { ALL_EVENT_TYPES, EVENT_TYPE_LABELS } from "../types/index.js";
|
|
12
15
|
import { SectionForm, useSectionFormNav } from "../../shell/index.js";
|
|
13
16
|
import { eventDraftSchema } from "../schemas/event-form.js";
|
|
@@ -214,13 +217,16 @@ export function AdminEventEditorView({ eventId, onSaved, embedded, allowedTypes,
|
|
|
214
217
|
const [slugManual, setSlugManual] = React.useState(false);
|
|
215
218
|
const [saveMessage, setSaveMessage] = React.useState(null);
|
|
216
219
|
const [triggerMessage, setTriggerMessage] = React.useState(null);
|
|
220
|
+
// Root translator (no namespace) — `toUserMessage` passes fully-qualified
|
|
221
|
+
// `errors.codes.*` keys and resolves them itself. Same precedent as
|
|
222
|
+
// `PlaceBidFormClient` and `SpinWheelView`.
|
|
223
|
+
const t = useTranslations();
|
|
217
224
|
const [raffleWinnerName, setRaffleWinnerName] = React.useState("");
|
|
218
225
|
const [raffleEntryCount, setRaffleEntryCount] = React.useState(null);
|
|
219
226
|
const [raffleGithubUrl, setRaffleGithubUrl] = React.useState("");
|
|
220
227
|
const { shellCtx, setFieldError, validate } = useFormShellState(eventDraftSchema);
|
|
221
228
|
useEffect(() => {
|
|
222
229
|
validate(draft);
|
|
223
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
224
230
|
}, [draft, validate]);
|
|
225
231
|
const update = React.useCallback((partial) => {
|
|
226
232
|
setDraft((prev) => ({ ...prev, ...partial }));
|
|
@@ -338,7 +344,20 @@ export function AdminEventEditorView({ eventId, onSaved, embedded, allowedTypes,
|
|
|
338
344
|
setRaffleEntryCount(data.raffleEntryCount);
|
|
339
345
|
},
|
|
340
346
|
onError: (err) => {
|
|
341
|
-
|
|
347
|
+
/*
|
|
348
|
+
* 🛑 `err.message` was printed straight into the panel — the shape Root
|
|
349
|
+
* Cause #86 is about, and the ternary was inverted besides: the authored
|
|
350
|
+
* sentence only showed when the value was NOT an Error, i.e. almost
|
|
351
|
+
* never, so the normal case rendered raw server text to an admin.
|
|
352
|
+
*
|
|
353
|
+
* The route now returns a stable `code` for every refusal, so the copy
|
|
354
|
+
* resolves through `errors.codes.*`. The one that matters is
|
|
355
|
+
* `RAFFLE_ALREADY_DRAWN` — "a winner has already been drawn" is a
|
|
356
|
+
* completely different instruction from the old generic failure, and it
|
|
357
|
+
* is what a second click gets now that the draw is transactional.
|
|
358
|
+
*/
|
|
359
|
+
const e = normalizeError(err);
|
|
360
|
+
setTriggerMessage(toUserMessage(e.code, t, { fallback: "Could not draw a winner." }));
|
|
342
361
|
},
|
|
343
362
|
});
|
|
344
363
|
const isLoading = saveMutation.isPending || eventQuery.isLoading;
|
|
@@ -392,7 +411,7 @@ export function AdminEventEditorView({ eventId, onSaved, embedded, allowedTypes,
|
|
|
392
411
|
const isRaffleType = values.type === "raffle" || values.type === "spin_wheel";
|
|
393
412
|
const showRaffleConfig = isRaffleType || values.hasRaffle;
|
|
394
413
|
const isSpinMode = values.type === "spin_wheel" || values.raffleType === "spin_wheel";
|
|
395
|
-
return (_jsxs(Stack, { gap: "md", children: [_jsx(Heading, { level: 3, className: "mb-2", children: "Raffle & Spin Wheel" }), !isRaffleType && (_jsx(Toggle, { checked: values.hasRaffle, onChange: (v) => onChange({ hasRaffle: v }), label: "Attach a raffle to this event" })), !isRaffleType && !values.hasRaffle && (_jsx(Text, { size: "sm", color: "muted", children: "Enable the toggle above to attach a raffle draw to this event. Not applicable for this event type by default." })), showRaffleConfig && (_jsxs(Stack, { gap: "md", rounded: "xl", border: "default", padding: "md", children: [_jsxs(Grid, { cols: 2, gap: "sm", children: [_jsx(Select, { label: "Raffle type", value: values.type === "spin_wheel" ? "spin_wheel" : values.raffleType, options: RAFFLE_TYPE_OPTIONS, onChange: (e) => onChange({ raffleType: e.target.value }), disabled: values.type === "spin_wheel" }), _jsx(Input, { label: "Top N (for top-N raffle types)", type: "number", value: values.raffleTopN, onChange: (e) => onChange({ raffleTopN: e.target.value }), placeholder: "10" })] }), _jsx(Input, { label: "Prize description", value: values.rafflePrize, onChange: (e) => onChange({ rafflePrize: e.target.value }), placeholder: "\u20B92,000 store credit + exclusive Pikachu sticker" }), _jsxs(Div, { children: [_jsx(Text, { size: "sm", weight: "medium", className: "mb-1", children: "Coupon for winner (optional)" }), _jsx(CouponInlineSelect, { scope: "admin", value: values.rafflePrizeCouponId, onChange: (id) => onChange({ rafflePrizeCouponId: id ?? "" }), placeholder: "Search coupons\u2026", allowCreate: false })] }), _jsxs(Div, { children: [_jsx(Text, { size: "sm", weight: "medium", className: "mb-1", children: "Prize products (optional)" }), _jsx(ProductInlineSelect, { scope: "admin", multiple: true, value: values.rafflePrizeProductIds, onChange: (ids) => onChange({ rafflePrizeProductIds: ids }), placeholder: "Search products to feature as prizes\u2026" }), _jsx(Text, { size: "xs", color: "muted", className: "mt-1", children: "Products shown to winners or displayed as the prize showcase." })] }), isSpinMode && _jsx(SpinPrizesEditor, { values: values, onChange: onChange }), isEdit && (_jsxs(Stack, { gap: "xs", rounded: "lg", padding: "sm", className: CLS_RAFFLE_PANEL, children: [_jsxs(Row, { justify: "between", gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", className: CLS_RAFFLE_HEADING, children: raffleWinnerName ? `Winner: ${raffleWinnerName}` : "Raffle not yet triggered" }), _jsx(Button, { type: "button",
|
|
414
|
+
return (_jsxs(Stack, { gap: "md", children: [_jsx(Heading, { level: 3, className: "mb-2", children: "Raffle & Spin Wheel" }), !isRaffleType && (_jsx(Toggle, { checked: values.hasRaffle, onChange: (v) => onChange({ hasRaffle: v }), label: "Attach a raffle to this event" })), !isRaffleType && !values.hasRaffle && (_jsx(Text, { size: "sm", color: "muted", children: "Enable the toggle above to attach a raffle draw to this event. Not applicable for this event type by default." })), showRaffleConfig && (_jsxs(Stack, { gap: "md", rounded: "xl", border: "default", padding: "md", children: [_jsxs(Grid, { cols: 2, gap: "sm", children: [_jsx(Select, { label: "Raffle type", value: values.type === "spin_wheel" ? "spin_wheel" : values.raffleType, options: RAFFLE_TYPE_OPTIONS, onChange: (e) => onChange({ raffleType: e.target.value }), disabled: values.type === "spin_wheel" }), _jsx(Input, { label: "Top N (for top-N raffle types)", type: "number", value: values.raffleTopN, onChange: (e) => onChange({ raffleTopN: e.target.value }), placeholder: "10" })] }), _jsx(Input, { label: "Prize description", value: values.rafflePrize, onChange: (e) => onChange({ rafflePrize: e.target.value }), placeholder: "\u20B92,000 store credit + exclusive Pikachu sticker" }), _jsxs(Div, { children: [_jsx(Text, { size: "sm", weight: "medium", className: "mb-1", children: "Coupon for winner (optional)" }), _jsx(CouponInlineSelect, { scope: "admin", value: values.rafflePrizeCouponId, onChange: (id) => onChange({ rafflePrizeCouponId: id ?? "" }), placeholder: "Search coupons\u2026", allowCreate: false })] }), _jsxs(Div, { children: [_jsx(Text, { size: "sm", weight: "medium", className: "mb-1", children: "Prize products (optional)" }), _jsx(ProductInlineSelect, { scope: "admin", multiple: true, value: values.rafflePrizeProductIds, onChange: (ids) => onChange({ rafflePrizeProductIds: ids }), placeholder: "Search products to feature as prizes\u2026" }), _jsx(Text, { size: "xs", color: "muted", className: "mt-1", children: "Products shown to winners or displayed as the prize showcase." })] }), isSpinMode && _jsx(SpinPrizesEditor, { values: values, onChange: onChange }), isEdit && (_jsxs(Stack, { gap: "xs", rounded: "lg", padding: "sm", className: CLS_RAFFLE_PANEL, children: [_jsxs(Row, { justify: "between", gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", className: CLS_RAFFLE_HEADING, children: raffleWinnerName ? `Winner: ${raffleWinnerName}` : "Raffle not yet triggered" }), _jsx(Button, { type: "button", action: ACTIONS.EVENT["draw-raffle-winner"], size: "sm", onClick: () => triggerRaffleMutation.mutate(), disabled: triggerRaffleMutation.isPending || Boolean(raffleWinnerName), children: triggerRaffleMutation.isPending ? "Triggering…" : raffleWinnerName ? "Already triggered" : "Trigger Raffle Now" })] }), raffleEntryCount !== null && _jsxs(Text, { size: "xs", className: CLS_RAFFLE_BODY, children: ["Pool size: ", raffleEntryCount] }), raffleGithubUrl && _jsxs(Text, { size: "xs", className: `break-all ${CLS_RAFFLE_BODY}`, children: ["Fairness proof: ", raffleGithubUrl] }), triggerMessage && _jsx(Text, { size: "xs", className: CLS_RAFFLE_HEADING, children: triggerMessage })] }))] }))] }));
|
|
396
415
|
},
|
|
397
416
|
},
|
|
398
417
|
];
|
|
@@ -10,7 +10,8 @@ import { ALL_EVENT_TYPES, EVENT_TYPE_LABELS } from "../types/index.js";
|
|
|
10
10
|
import { toRecordArray, toRelativeDate, toStringValue, } from "../../admin/hooks/useAdminListingData.js";
|
|
11
11
|
import { DataListingView } from "../../admin/components/DataListingView.js";
|
|
12
12
|
import { AdminEventEditorView } from "./AdminEventEditorView.js";
|
|
13
|
-
import { ADMIN_BULK_ACTIONS, ROW_ACTION_META } from "../../products/constants/action-defs.js";
|
|
13
|
+
import { ADMIN_BULK_ACTIONS, ROW_ACTION_ID, ROW_ACTION_META } from "../../products/constants/action-defs.js";
|
|
14
|
+
import { apiClient } from "../../../http/index.js";
|
|
14
15
|
/**
|
|
15
16
|
* Derived from the `EventType` union (2026-08-24).
|
|
16
17
|
*
|
|
@@ -74,13 +75,36 @@ export function AdminEventsView({ children, ...props }) {
|
|
|
74
75
|
label: "Add Event",
|
|
75
76
|
onClick: ({ openCreatePanel }) => openCreatePanel(),
|
|
76
77
|
},
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
78
|
+
/*
|
|
79
|
+
* Rule #7: bulk-action array sourced from the ADMIN_BULK_ACTIONS preset.
|
|
80
|
+
*
|
|
81
|
+
* 🛑 This used to map every preset id to `onClick: () => clearSelection()`
|
|
82
|
+
* — so the bulk bar rendered a red "Delete" that selected rows, asked
|
|
83
|
+
* nothing, deleted nothing, and then cleared the checkboxes, which reads
|
|
84
|
+
* exactly like a successful delete until the list refreshes. A destructive
|
|
85
|
+
* control that silently no-ops is worse than an absent one (Rule #8).
|
|
86
|
+
*
|
|
87
|
+
* Wired self-contained against `DELETE /api/admin/events/[id]`, following
|
|
88
|
+
* `AdminBundlesView`; the `.filter` keeps `AdminFaqsView`'s discipline that
|
|
89
|
+
* an unwired preset id is OMITTED rather than shown inert, so the next id
|
|
90
|
+
* added to the preset cannot reintroduce this.
|
|
91
|
+
*/
|
|
92
|
+
buildBulkActions: (selection) => {
|
|
93
|
+
const handlers = {
|
|
94
|
+
[ROW_ACTION_ID.DELETE]: async () => {
|
|
95
|
+
await Promise.all(selection.selectedIds.map((id) => apiClient.delete(ADMIN_ENDPOINTS.EVENT_BY_ID(id))));
|
|
96
|
+
selection.clearSelection();
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
return ADMIN_BULK_ACTIONS.events
|
|
100
|
+
.filter((id) => handlers[id])
|
|
101
|
+
.map((id) => ({
|
|
102
|
+
id,
|
|
103
|
+
label: ROW_ACTION_META[id].label,
|
|
104
|
+
destructive: ROW_ACTION_META[id].destructive,
|
|
105
|
+
onClick: handlers[id],
|
|
106
|
+
}));
|
|
107
|
+
},
|
|
84
108
|
renderFilterPanel: ({ pendingFilters, setPendingFilters }) => (_jsxs(_Fragment, { children: [_jsx(FilterChipGroup, { label: "Status", tabs: ADMIN_EVENT_STATUS_TABS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) }), _jsx(FilterChipGroup, { label: "Type", tabs: TYPE_OPTIONS, value: pendingFilters.type ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, type: id })) })] })),
|
|
85
109
|
renderEditor: ({ editId, closePanel }) => (_jsx(AdminEventEditorView, { eventId: editId ?? undefined, onSaved: closePanel, embedded: true })),
|
|
86
110
|
resolveEditorTitle: ({ isCreate }) => (isCreate ? "Add Event" : "Edit Event"),
|
|
@@ -4,6 +4,39 @@ export interface EventRafflesSectionProps {
|
|
|
4
4
|
}
|
|
5
5
|
/**
|
|
6
6
|
* Renders a live-raffle/spin-wheel strip on the homepage.
|
|
7
|
-
*
|
|
7
|
+
*
|
|
8
|
+
* ## It was showing the wrong events, and nothing could tell
|
|
9
|
+
*
|
|
10
|
+
* The filter was `hasRaffle == "true"` — and **`hasRaffle` is not in
|
|
11
|
+
* `EventRepository.SIEVE_FIELDS`**. `sieve.ts` sets `throwExceptions: false`,
|
|
12
|
+
* so an unallowlisted field does not raise: `findField` returns undefined and
|
|
13
|
+
* the clause is **silently discarded** (Root Cause #62, EMITTED_BUT_UNFILTERABLE).
|
|
14
|
+
*
|
|
15
|
+
* So this did not return zero rows and it did not disappear. It ran
|
|
16
|
+
* `status == active` sorted `-startsAt` and rendered **the six most
|
|
17
|
+
* recently-started active events of ANY type** — sales, polls, feedback forms —
|
|
18
|
+
* under a heading reading "Live Raffles & Spin Wheels". Every card was a real,
|
|
19
|
+
* live event, which is exactly why it never looked broken.
|
|
20
|
+
*
|
|
21
|
+
* ## Why `type`, and why one query
|
|
22
|
+
*
|
|
23
|
+
* `config.raffleType` is already `"raffle" | "spin_wheel" | "all"` — the config
|
|
24
|
+
* has always spoken the `type` vocabulary and already answers "both belong
|
|
25
|
+
* here", so the title is truthful and `type` is the honest field to match.
|
|
26
|
+
* `type` IS in `SIEVE_FIELDS` and is indexed `(type, status, startsAt DESC)`,
|
|
27
|
+
* so this needs no new composite index.
|
|
28
|
+
*
|
|
29
|
+
* 🛑 The `"all"` case is a PIPE-joined value, not `sieveMultiEq`. Sieve parses
|
|
30
|
+
* `type==a|b` as one OR-group, and the Firebase adapter upgrades a same-field
|
|
31
|
+
* OR-group of equalities into a single Firestore `in` query. `sieveMultiEq`
|
|
32
|
+
* comma-joins instead, and a comma is an AND: `type==raffle,type==spin_wheel`
|
|
33
|
+
* is two equalities on one field, which no document can satisfy (Root
|
|
34
|
+
* Cause #59).
|
|
35
|
+
*
|
|
36
|
+
* Accepted limitation, deliberately: `hasRaffle` is an optional bolt-on that
|
|
37
|
+
* *could* sit on a `sale` event, and such an event would not appear here. No
|
|
38
|
+
* seeded event is shaped that way — all three `hasRaffle: true` rows are
|
|
39
|
+
* already `type: "raffle"` — and a sale with a side raffle belongs in the sale
|
|
40
|
+
* strip. If that changes, the fix is a second clause, not a wider title.
|
|
8
41
|
*/
|
|
9
42
|
export declare function EventRafflesSection({ config, }: EventRafflesSectionProps): Promise<import("react").JSX.Element | null>;
|
|
@@ -12,18 +12,67 @@ import { Container, Grid, Heading, Section, Stack, Text } from "../../../ui/inde
|
|
|
12
12
|
import { eventRepository } from "../repository/events.repository.js";
|
|
13
13
|
import { EventCard } from "./EventCard.js";
|
|
14
14
|
import { hidePublicTestData } from "../../../_internal/server/features/tester/visibility.js";
|
|
15
|
+
import { EVENT_FIELDS } from "../../../constants/field-names.js";
|
|
16
|
+
/*
|
|
17
|
+
* `config.raffleType`'s two concrete values happen to equal the two
|
|
18
|
+
* `EventType` members this strip spans. Sourced from `EVENT_FIELDS.TYPE_VALUES`
|
|
19
|
+
* rather than written as literals so a rename of either type is a compile
|
|
20
|
+
* error here instead of a clause that matches nothing.
|
|
21
|
+
*/
|
|
22
|
+
const EVENT_TYPE_RAFFLE = EVENT_FIELDS.TYPE_VALUES.RAFFLE;
|
|
23
|
+
const EVENT_TYPE_SPIN_WHEEL = EVENT_FIELDS.TYPE_VALUES.SPIN_WHEEL;
|
|
15
24
|
/**
|
|
16
25
|
* Renders a live-raffle/spin-wheel strip on the homepage.
|
|
17
|
-
*
|
|
26
|
+
*
|
|
27
|
+
* ## It was showing the wrong events, and nothing could tell
|
|
28
|
+
*
|
|
29
|
+
* The filter was `hasRaffle == "true"` — and **`hasRaffle` is not in
|
|
30
|
+
* `EventRepository.SIEVE_FIELDS`**. `sieve.ts` sets `throwExceptions: false`,
|
|
31
|
+
* so an unallowlisted field does not raise: `findField` returns undefined and
|
|
32
|
+
* the clause is **silently discarded** (Root Cause #62, EMITTED_BUT_UNFILTERABLE).
|
|
33
|
+
*
|
|
34
|
+
* So this did not return zero rows and it did not disappear. It ran
|
|
35
|
+
* `status == active` sorted `-startsAt` and rendered **the six most
|
|
36
|
+
* recently-started active events of ANY type** — sales, polls, feedback forms —
|
|
37
|
+
* under a heading reading "Live Raffles & Spin Wheels". Every card was a real,
|
|
38
|
+
* live event, which is exactly why it never looked broken.
|
|
39
|
+
*
|
|
40
|
+
* ## Why `type`, and why one query
|
|
41
|
+
*
|
|
42
|
+
* `config.raffleType` is already `"raffle" | "spin_wheel" | "all"` — the config
|
|
43
|
+
* has always spoken the `type` vocabulary and already answers "both belong
|
|
44
|
+
* here", so the title is truthful and `type` is the honest field to match.
|
|
45
|
+
* `type` IS in `SIEVE_FIELDS` and is indexed `(type, status, startsAt DESC)`,
|
|
46
|
+
* so this needs no new composite index.
|
|
47
|
+
*
|
|
48
|
+
* 🛑 The `"all"` case is a PIPE-joined value, not `sieveMultiEq`. Sieve parses
|
|
49
|
+
* `type==a|b` as one OR-group, and the Firebase adapter upgrades a same-field
|
|
50
|
+
* OR-group of equalities into a single Firestore `in` query. `sieveMultiEq`
|
|
51
|
+
* comma-joins instead, and a comma is an AND: `type==raffle,type==spin_wheel`
|
|
52
|
+
* is two equalities on one field, which no document can satisfy (Root
|
|
53
|
+
* Cause #59).
|
|
54
|
+
*
|
|
55
|
+
* Accepted limitation, deliberately: `hasRaffle` is an optional bolt-on that
|
|
56
|
+
* *could* sit on a `sale` event, and such an event would not appear here. No
|
|
57
|
+
* seeded event is shaped that way — all three `hasRaffle: true` rows are
|
|
58
|
+
* already `type: "raffle"` — and a sale with a side raffle belongs in the sale
|
|
59
|
+
* strip. If that changes, the fix is a second clause, not a wider title.
|
|
18
60
|
*/
|
|
19
61
|
export async function EventRafflesSection({ config, }) {
|
|
20
62
|
const title = config.title ?? "Live Raffles & Spin Wheels";
|
|
21
63
|
const subtitle = config.subtitle ?? "Participate in community events and win prizes";
|
|
22
|
-
|
|
64
|
+
// `maxItems` was declared in the config, set by the seed, and ignored here in
|
|
65
|
+
// favour of a hardcoded 6 (`audit-homepage-config-coverage` passed only
|
|
66
|
+
// because the component *mentions* `config`).
|
|
67
|
+
const limit = config.maxItems && config.maxItems > 0 ? config.maxItems : 6;
|
|
68
|
+
const raffleType = config.raffleType ?? "all";
|
|
69
|
+
const typeClause = raffleType === "all"
|
|
70
|
+
? sieveFilter(EVENT_FIELDS.TYPE, SIEVE_OP.EQ, `${EVENT_TYPE_RAFFLE}|${EVENT_TYPE_SPIN_WHEEL}`)
|
|
71
|
+
: sieveFilter(EVENT_FIELDS.TYPE, SIEVE_OP.EQ, raffleType);
|
|
23
72
|
let events = [];
|
|
24
73
|
try {
|
|
25
74
|
const result = await eventRepository.list({
|
|
26
|
-
filters: sieveAnd(
|
|
75
|
+
filters: sieveAnd(typeClause, sieveFilter(EVENT_FIELDS.STATUS, SIEVE_OP.EQ, EVENT_FIELDS.STATUS_VALUES.ACTIVE)),
|
|
27
76
|
sorts: sortBy("startsAt", "DESC"),
|
|
28
77
|
pageSize: limit,
|
|
29
78
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { DisplaySpinPrize } from "../types";
|
|
2
2
|
export interface SpinWheelViewProps {
|
|
3
3
|
eventId: string;
|
|
4
|
-
prizes:
|
|
4
|
+
prizes: DisplaySpinPrize[];
|
|
5
5
|
alreadyUsed?: boolean;
|
|
6
6
|
initialPrizeId?: string;
|
|
7
7
|
initialCouponCode?: string;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { useCallback, useMemo, useState } from "react";
|
|
4
|
+
import { useTranslations } from "next-intl";
|
|
4
5
|
import { Button, ClaimCouponButton, Div, Heading, LoginRequiredModal, Row, Span, Stack, Text, useToast } from "../../../ui/index.js";
|
|
5
6
|
import { isAuthError } from "../../../utils/auth-error.js";
|
|
6
7
|
import { normalizeError } from "../../../errors/normalize.js";
|
|
@@ -32,6 +33,10 @@ export function SpinWheelView({ eventId, prizes, alreadyUsed, initialPrizeId, in
|
|
|
32
33
|
const [error, setError] = useState(null);
|
|
33
34
|
const [showLoginModal, setShowLoginModal] = useState(false);
|
|
34
35
|
const { showToast } = useToast();
|
|
36
|
+
// Root translator (no namespace) — `toUserMessage` passes fully-qualified
|
|
37
|
+
// `errors.codes.*` keys and resolves them itself, so a missing key degrades
|
|
38
|
+
// to generic copy rather than throwing MISSING_MESSAGE at render time.
|
|
39
|
+
const t = useTranslations();
|
|
35
40
|
const now = Date.now();
|
|
36
41
|
const startMs = windowStart ? new Date(windowStart).getTime() : null;
|
|
37
42
|
const endMs = windowEnd ? new Date(windowEnd).getTime() : null;
|
|
@@ -51,6 +56,12 @@ export function SpinWheelView({ eventId, prizes, alreadyUsed, initialPrizeId, in
|
|
|
51
56
|
setResultCoupon(result.spinPrizeCouponCode);
|
|
52
57
|
}
|
|
53
58
|
else {
|
|
59
|
+
/*
|
|
60
|
+
* A 2xx with no prize. The server now refuses with a real status and
|
|
61
|
+
* code for every reason it knows (`reasonToHttp`), so reaching here
|
|
62
|
+
* means the response was shaped unexpectedly rather than refused —
|
|
63
|
+
* the generic fallback is the honest answer.
|
|
64
|
+
*/
|
|
54
65
|
setError(l.errorFallback);
|
|
55
66
|
}
|
|
56
67
|
}, ANIMATION_MS);
|
|
@@ -62,13 +73,39 @@ export function SpinWheelView({ eventId, prizes, alreadyUsed, initialPrizeId, in
|
|
|
62
73
|
setShowLoginModal(true);
|
|
63
74
|
}
|
|
64
75
|
else {
|
|
65
|
-
|
|
66
|
-
|
|
76
|
+
/*
|
|
77
|
+
* 🛑 Show the SERVER'S reason, not one sentence for all of them.
|
|
78
|
+
*
|
|
79
|
+
* Both lines below used to be `l.errorFallback` — "Spin failed. Please
|
|
80
|
+
* try again." — so a closed window, an exhausted per-user cap, a
|
|
81
|
+
* misconfigured wheel and a genuine fault were indistinguishable, and
|
|
82
|
+
* the retry that sentence invites could never succeed for most of them.
|
|
83
|
+
*
|
|
84
|
+
* `t` is the ROOT, unnamespaced translator: `toUserMessage` resolves
|
|
85
|
+
* absolute `errors.codes.*` keys, which a namespaced translator cannot
|
|
86
|
+
* reach. Omitting it (as this did) makes `toUserMessage` return the
|
|
87
|
+
* fallback unconditionally — so the per-reason copy existed in
|
|
88
|
+
* `messages/en.json` and was unreachable. Same reasoning as
|
|
89
|
+
* `PlaceBidFormClient`.
|
|
90
|
+
*
|
|
91
|
+
* This only resolves because `onSpin` now throws an `ApiError` carrying
|
|
92
|
+
* the response's `code` and `status`; while it threw a native `Error`,
|
|
93
|
+
* `normalizeError` produced no code and `isAuthError` saw no status, so
|
|
94
|
+
* a 401 never opened the login modal either.
|
|
95
|
+
*/
|
|
96
|
+
const message = toUserMessage(e.code, t, { fallback: l.errorFallback });
|
|
97
|
+
setError(message);
|
|
98
|
+
showToast(message, "error");
|
|
67
99
|
}
|
|
68
100
|
}
|
|
69
|
-
}, [disabled, eventId, l.errorFallback, onSpin, showToast]);
|
|
101
|
+
}, [disabled, eventId, l.errorFallback, onSpin, showToast, t]);
|
|
102
|
+
/*
|
|
103
|
+
* Looked up in the FULL prize list, not `activePrizes`. A prize deactivated
|
|
104
|
+
* after somebody won it would otherwise render neither the prize box nor an
|
|
105
|
+
* error — the win simply vanished from the page.
|
|
106
|
+
*/
|
|
70
107
|
const wonPrize = resultPrizeId
|
|
71
|
-
?
|
|
108
|
+
? prizes.find((p) => p.id === resultPrizeId)
|
|
72
109
|
: undefined;
|
|
73
110
|
return (_jsxs(Stack, { gap: "md", children: [_jsx(LoginRequiredModal, { isOpen: showLoginModal, onClose: () => setShowLoginModal(false), message: "You need to be signed in to spin the wheel. Please log in or create an account to continue." }), _jsxs(Heading, { level: 2, size: "xl", weight: "semibold", color: "primary", children: ["\uD83C\uDFA1 ", l.heading] }), _jsxs(Div, { className: CLS_WHEEL, "aria-label": "Spin wheel", children: [_jsx(Row, { className: "absolute inset-0", align: "center", justify: "center", style: {
|
|
74
111
|
animation: spinning
|
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
import type { FirebaseSieveFields, FirebaseSieveResult, SieveModel } from "../../../providers/db-firebase";
|
|
2
2
|
import { BaseRepository } from "../../../providers/db-firebase";
|
|
3
|
-
import { type EventEntryCreateInput, type EventEntryDocument } from "../schemas";
|
|
3
|
+
import { type EventEntryCreateInput, type EventEntryDocument, type EventEntryPlain } from "../schemas";
|
|
4
4
|
declare class EventEntryRepository extends BaseRepository<EventEntryDocument> {
|
|
5
5
|
static readonly SIEVE_FIELDS: FirebaseSieveFields;
|
|
6
6
|
constructor();
|
|
7
|
-
|
|
7
|
+
/** Plaintext PII. **Opt-in and named** — shows up in review and in a grep. */
|
|
8
|
+
revealEntry(doc: EventEntryDocument): EventEntryPlain;
|
|
9
|
+
/** As `revealEntry`, for a list read. */
|
|
10
|
+
revealEntrys(docs: readonly EventEntryDocument[]): EventEntryPlain[];
|
|
11
|
+
/**
|
|
12
|
+
* Masked PII — **the default for any surface that only needs to identify a
|
|
13
|
+
* row to a human.** Decrypts first, then masks: the mask helpers refuse
|
|
14
|
+
* ciphertext by design, so passing a stored field straight in would return
|
|
15
|
+
* "Anonymous" for every row.
|
|
16
|
+
*/
|
|
17
|
+
maskedEntry(doc: EventEntryDocument): EventEntryPlain;
|
|
18
|
+
/** As `maskedEntry`, for a list read. */
|
|
19
|
+
maskedEntrys(docs: readonly EventEntryDocument[]): EventEntryPlain[];
|
|
8
20
|
listForEvent(eventId: string, model: SieveModel): Promise<FirebaseSieveResult<EventEntryDocument>>;
|
|
9
21
|
listForUser(userId: string, model: SieveModel): Promise<FirebaseSieveResult<EventEntryDocument>>;
|
|
10
22
|
/**
|
|
@@ -2,7 +2,7 @@ import { normalizeError } from "../../../errors/normalize.js";
|
|
|
2
2
|
import { DatabaseError } from "../../../errors/index.js";
|
|
3
3
|
import { serverLogger } from "../../../monitoring/index.js";
|
|
4
4
|
import { BaseRepository, prepareForFirestore, parseSieveDateValue, } from "../../../providers/db-firebase/index.js";
|
|
5
|
-
import {
|
|
5
|
+
import { encryptPiiFields, maskPii, revealPii, EVENT_ENTRY_PII_FIELDS, } from "../../../security/index.js";
|
|
6
6
|
import { EVENT_ENTRIES_COLLECTION, EVENT_ENTRY_FIELDS, } from "../schemas/index.js";
|
|
7
7
|
/** Extracted from getExportReport's poll branch to stay under the max nesting depth. */
|
|
8
8
|
function formatPollResultLines(pollOptions, countByOption, total) {
|
|
@@ -20,11 +20,38 @@ class EventEntryRepository extends BaseRepository {
|
|
|
20
20
|
constructor() {
|
|
21
21
|
super(EVENT_ENTRIES_COLLECTION);
|
|
22
22
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
/*
|
|
24
|
+
* 🛑 `mapDoc` is NOT overridden, and that absence IS the change.
|
|
25
|
+
*
|
|
26
|
+
* It used to decrypt on every read, so ciphertext never left the database
|
|
27
|
+
* and every caller handled plaintext without ever saying so. Following
|
|
28
|
+
* `SiteSettingsRepository`: reads return what Firestore holds, and plaintext
|
|
29
|
+
* requires one of the two named methods below.
|
|
30
|
+
*
|
|
31
|
+
* A convention would not hold this. `EventEntryDocument`'s PII fields are
|
|
32
|
+
* `Encrypted<string>`, assignable to nothing that wants a plain `string`, so
|
|
33
|
+
* a reader that skips both accessors is a compile error naming file and line.
|
|
34
|
+
*/
|
|
35
|
+
/** Plaintext PII. **Opt-in and named** — shows up in review and in a grep. */
|
|
36
|
+
revealEntry(doc) {
|
|
37
|
+
return revealPii(doc, EVENT_ENTRY_PII_FIELDS);
|
|
38
|
+
}
|
|
39
|
+
/** As `revealEntry`, for a list read. */
|
|
40
|
+
revealEntrys(docs) {
|
|
41
|
+
return docs.map((d) => this.revealEntry(d));
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Masked PII — **the default for any surface that only needs to identify a
|
|
45
|
+
* row to a human.** Decrypts first, then masks: the mask helpers refuse
|
|
46
|
+
* ciphertext by design, so passing a stored field straight in would return
|
|
47
|
+
* "Anonymous" for every row.
|
|
48
|
+
*/
|
|
49
|
+
maskedEntry(doc) {
|
|
50
|
+
return maskPii(doc, EVENT_ENTRY_PII_FIELDS);
|
|
51
|
+
}
|
|
52
|
+
/** As `maskedEntry`, for a list read. */
|
|
53
|
+
maskedEntrys(docs) {
|
|
54
|
+
return docs.map((d) => this.maskedEntry(d));
|
|
28
55
|
}
|
|
29
56
|
async listForEvent(eventId, model) {
|
|
30
57
|
return this.sieveQuery(model, EventEntryRepository.SIEVE_FIELDS, {
|
|
@@ -150,7 +177,14 @@ class EventEntryRepository extends BaseRepository {
|
|
|
150
177
|
// Aggregate per user: sum all approved entry points
|
|
151
178
|
const byUser = new Map();
|
|
152
179
|
for (const doc of snapshot.docs) {
|
|
153
|
-
|
|
180
|
+
/*
|
|
181
|
+
* MASKED, not decrypted. This builds a public LEADERBOARD, so it needs
|
|
182
|
+
* a name to show and has no business holding the real one — the rows
|
|
183
|
+
* go straight into a response. `maskPii` decrypts and then masks, which
|
|
184
|
+
* is required here: `maskName` refuses ciphertext by design, so masking
|
|
185
|
+
* the stored value directly would render every competitor "Anonymous".
|
|
186
|
+
*/
|
|
187
|
+
const raw = maskPii({ id: doc.id, ...doc.data() }, EVENT_ENTRY_PII_FIELDS);
|
|
154
188
|
if (!raw.userId)
|
|
155
189
|
continue;
|
|
156
190
|
const existing = byUser.get(raw.userId);
|
|
@@ -6,6 +6,7 @@ import type { JsonValue } from "@mohasinac/appkit";
|
|
|
6
6
|
import type { BaseDocument } from "../../../_internal/shared/types/base-document";
|
|
7
7
|
import type { EventType, EventStatus, EntryReviewStatus, SaleConfig, OfferConfig, PollConfig, SurveyConfig, FeedbackConfig, RaffleType, SpinPrize } from "../types";
|
|
8
8
|
import type { LotteryConfig } from "../../lottery/types";
|
|
9
|
+
import type { Encrypted, Plaintext } from "../../../security/encrypted";
|
|
9
10
|
export declare const EVENTS_COLLECTION: "events";
|
|
10
11
|
export declare const EVENT_ENTRIES_COLLECTION: "eventEntries";
|
|
11
12
|
export interface EventDocument extends BaseDocument {
|
|
@@ -68,12 +69,24 @@ export interface EventDocument extends BaseDocument {
|
|
|
68
69
|
};
|
|
69
70
|
createdBy: string;
|
|
70
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* `EventEntryDocument` with its PII as plain `string` — what the named repository
|
|
74
|
+
* accessors return, and what a WRITE path accepts (the repository encrypts).
|
|
75
|
+
*
|
|
76
|
+
* `Plaintext<T>` is the one definition of this shape; see `security/encrypted.ts`.
|
|
77
|
+
* It is deliberately NOT distinguishable from a MASKED value — `maskPii`
|
|
78
|
+
* returns plain strings too, and no type can express "these characters are
|
|
79
|
+
* asterisks". The distinction is which named method you called.
|
|
80
|
+
*/
|
|
81
|
+
export type EventEntryPlain = Plaintext<EventEntryDocument>;
|
|
71
82
|
export type EventEntryDocument = {
|
|
72
83
|
id: string;
|
|
73
84
|
eventId: string;
|
|
74
85
|
userId?: string;
|
|
75
|
-
|
|
76
|
-
|
|
86
|
+
createdAt?: Date;
|
|
87
|
+
updatedAt?: Date;
|
|
88
|
+
userDisplayName?: Encrypted<string>;
|
|
89
|
+
userEmail?: Encrypted<string>;
|
|
77
90
|
pollVotes?: string[];
|
|
78
91
|
pollComment?: string;
|
|
79
92
|
formResponses?: Record<string, JsonValue>;
|
|
@@ -91,7 +104,7 @@ export type EventEntryDocument = {
|
|
|
91
104
|
spinPrizeId?: string;
|
|
92
105
|
spinPrizeCouponCode?: string | null;
|
|
93
106
|
spinWonAt?: Date;
|
|
94
|
-
ipAddress?: string
|
|
107
|
+
ipAddress?: Encrypted<string>;
|
|
95
108
|
/**
|
|
96
109
|
* HMAC-SHA256 blind index of a guest (non-logged-in) participant's IP,
|
|
97
110
|
* scoped per event — `hmacBlindIndex(eventId + ":" + rawIp)`. Set only
|
|
@@ -182,7 +195,7 @@ export declare const EVENT_ENTRY_FIELDS: {
|
|
|
182
195
|
};
|
|
183
196
|
export type EventCreateInput = Omit<EventDocument, "id" | "createdAt" | "updatedAt" | "stats">;
|
|
184
197
|
export type EventUpdateInput = Partial<Omit<EventDocument, "id" | "createdAt" | "createdBy">>;
|
|
185
|
-
export type EventEntryCreateInput = Omit<
|
|
198
|
+
export type EventEntryCreateInput = Omit<EventEntryPlain, "id" | "submittedAt">;
|
|
186
199
|
export declare const eventQueryHelpers: {
|
|
187
200
|
readonly active: () => readonly ["status", "==", EventStatus];
|
|
188
201
|
readonly byType: (type: EventType) => readonly ["type", "==", EventType];
|