@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
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useCallback, useEffect, useRef } from "react";
|
|
3
|
+
/**
|
|
4
|
+
* How long a press has to last before it counts as "hold to open" rather than
|
|
5
|
+
* "tap to follow the link". 400ms is the usual long-press threshold — short
|
|
6
|
+
* enough not to feel like a stall, long enough that an ordinary tap never
|
|
7
|
+
* trips it.
|
|
8
|
+
*/
|
|
9
|
+
export const HOLD_TO_OPEN_MS = 400;
|
|
10
|
+
/**
|
|
11
|
+
* useHoldToOpen — press-and-hold a navigation link to open its sub-menu
|
|
12
|
+
* instead of following it.
|
|
13
|
+
*
|
|
14
|
+
* The shared half of the same affordance on two surfaces: the desktop navbar's
|
|
15
|
+
* dropdown and the mobile bottom nav's sheet. Both wrap a REAL link whose tap
|
|
16
|
+
* must still navigate, and both need the `click` that a long press still fires
|
|
17
|
+
* to be swallowed — otherwise the menu opens and the browser immediately
|
|
18
|
+
* navigates away from it, which reads as the gesture doing nothing.
|
|
19
|
+
*
|
|
20
|
+
* 🛑 `held` is a ref, not state. The click handler reads it in the same tick
|
|
21
|
+
* the timer wrote it, and a state update would not have landed yet — the link
|
|
22
|
+
* would follow through on every hold.
|
|
23
|
+
*
|
|
24
|
+
* @param onHold called once the press passes the threshold (open the menu)
|
|
25
|
+
* @param enabled false for a link with no children — then every handler is a
|
|
26
|
+
* no-op and the link behaves exactly as it did before
|
|
27
|
+
*/
|
|
28
|
+
export function useHoldToOpen(onHold, enabled = true) {
|
|
29
|
+
const timerRef = useRef(null);
|
|
30
|
+
const heldRef = useRef(false);
|
|
31
|
+
const onHoldRef = useRef(onHold);
|
|
32
|
+
// Keep the callback current without re-creating the handlers (and therefore
|
|
33
|
+
// without restarting a press in progress on every parent re-render).
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
onHoldRef.current = onHold;
|
|
36
|
+
}, [onHold]);
|
|
37
|
+
const cancel = useCallback(() => {
|
|
38
|
+
if (timerRef.current) {
|
|
39
|
+
clearTimeout(timerRef.current);
|
|
40
|
+
timerRef.current = null;
|
|
41
|
+
}
|
|
42
|
+
}, []);
|
|
43
|
+
// Clear a pending timer if the item unmounts mid-press — the nav re-renders
|
|
44
|
+
// its item list whenever the listing-type flags or the session resolve.
|
|
45
|
+
useEffect(() => cancel, [cancel]);
|
|
46
|
+
const start = useCallback(() => {
|
|
47
|
+
heldRef.current = false;
|
|
48
|
+
cancel();
|
|
49
|
+
timerRef.current = setTimeout(() => {
|
|
50
|
+
heldRef.current = true;
|
|
51
|
+
onHoldRef.current();
|
|
52
|
+
}, HOLD_TO_OPEN_MS);
|
|
53
|
+
}, [cancel]);
|
|
54
|
+
/**
|
|
55
|
+
* Spread onto the element that wraps (or is) the link. `onClickCapture` is
|
|
56
|
+
* deliberate rather than `onClick`: on the bottom nav the handlers sit on a
|
|
57
|
+
* wrapper around `<NavItem>`, and by the time a bubbled click arrives the
|
|
58
|
+
* anchor's own default is already queued — capture runs before the anchor
|
|
59
|
+
* sees it, so one set of handlers works whether it is spread onto the link
|
|
60
|
+
* itself or onto a container.
|
|
61
|
+
*/
|
|
62
|
+
const handlers = enabled
|
|
63
|
+
? {
|
|
64
|
+
onPointerDown: start,
|
|
65
|
+
onPointerUp: cancel,
|
|
66
|
+
onPointerLeave: cancel,
|
|
67
|
+
onPointerCancel: cancel,
|
|
68
|
+
onClickCapture: (e) => {
|
|
69
|
+
if (!heldRef.current)
|
|
70
|
+
return;
|
|
71
|
+
e.preventDefault();
|
|
72
|
+
e.stopPropagation();
|
|
73
|
+
heldRef.current = false;
|
|
74
|
+
},
|
|
75
|
+
// A long press on touch otherwise raises the OS link callout over the
|
|
76
|
+
// menu the same gesture just opened.
|
|
77
|
+
onContextMenu: (e) => {
|
|
78
|
+
if (heldRef.current)
|
|
79
|
+
e.preventDefault();
|
|
80
|
+
},
|
|
81
|
+
}
|
|
82
|
+
: {};
|
|
83
|
+
return handlers;
|
|
84
|
+
}
|
|
@@ -1,10 +1,25 @@
|
|
|
1
1
|
import type { FirebaseSieveFields, FirebaseSieveResult, SieveModel } from "../../../providers/db-firebase";
|
|
2
2
|
import { BaseRepository } from "../../../providers/db-firebase";
|
|
3
|
-
import { type LotteryEntryDocument } from "../schemas/firestore";
|
|
3
|
+
import { type LotteryEntryDocument, type LotteryEntryPlain } from "../schemas/firestore";
|
|
4
4
|
declare class LotteryEntryRepository extends BaseRepository<LotteryEntryDocument> {
|
|
5
5
|
static readonly SIEVE_FIELDS: FirebaseSieveFields;
|
|
6
6
|
constructor();
|
|
7
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Plaintext PII. **Opt-in and named** — for the admin entries table and the
|
|
9
|
+
* draw notification, where the real address is the point.
|
|
10
|
+
*/
|
|
11
|
+
revealEntry(doc: LotteryEntryDocument): LotteryEntryPlain;
|
|
12
|
+
/** As `revealEntry`, for a list read. */
|
|
13
|
+
revealEntries(docs: readonly LotteryEntryDocument[]): LotteryEntryPlain[];
|
|
14
|
+
/**
|
|
15
|
+
* Masked PII — **the default for any surface that only needs to identify a
|
|
16
|
+
* row to a human.** Decrypts first, then masks: handing a stored field
|
|
17
|
+
* straight to `maskName` would return "Anonymous" for every row, because
|
|
18
|
+
* those helpers refuse ciphertext by design.
|
|
19
|
+
*/
|
|
20
|
+
maskedEntry(doc: LotteryEntryDocument): LotteryEntryPlain;
|
|
21
|
+
/** As `maskedEntry`, for a list read. */
|
|
22
|
+
maskedEntries(docs: readonly LotteryEntryDocument[]): LotteryEntryPlain[];
|
|
8
23
|
/**
|
|
9
24
|
* Get the next sequential userLotteryNumber for a source atomically.
|
|
10
25
|
* Returns current count + 1.
|
|
@@ -25,7 +40,7 @@ declare class LotteryEntryRepository extends BaseRepository<LotteryEntryDocument
|
|
|
25
40
|
/** List entries for a specific user across all sources. */
|
|
26
41
|
listForUser(userId: string, model: SieveModel): Promise<FirebaseSieveResult<LotteryEntryDocument>>;
|
|
27
42
|
/** Create a lottery entry with PII encryption. */
|
|
28
|
-
createEntry(data: Omit<
|
|
43
|
+
createEntry(data: Omit<LotteryEntryPlain, "id">): Promise<LotteryEntryDocument>;
|
|
29
44
|
/** Flag a lottery entry as fraudulent. */
|
|
30
45
|
flagEntry(entryId: string, flagNote: string, flaggedBy: string): Promise<LotteryEntryDocument>;
|
|
31
46
|
}
|
|
@@ -2,17 +2,48 @@ 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, LOTTERY_ENTRY_PII_FIELDS, } from "../../../security/index.js";
|
|
6
6
|
import { LOTTERY_ENTRIES_COLLECTION, LOTTERY_ENTRY_FIELDS, } from "../schemas/firestore.js";
|
|
7
7
|
class LotteryEntryRepository extends BaseRepository {
|
|
8
8
|
constructor() {
|
|
9
9
|
super(LOTTERY_ENTRIES_COLLECTION);
|
|
10
10
|
}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
/*
|
|
12
|
+
* 🛑 `mapDoc` is NOT overridden, and that is the whole change.
|
|
13
|
+
*
|
|
14
|
+
* It used to call `decryptPiiFields` on every read, so ciphertext never left
|
|
15
|
+
* the database and all five consumers handled plaintext without ever saying
|
|
16
|
+
* so. Following `SiteSettingsRepository`: reads return what Firestore holds,
|
|
17
|
+
* and plaintext requires one of the two named methods below.
|
|
18
|
+
*
|
|
19
|
+
* The compiler enforces it rather than a convention doing so —
|
|
20
|
+
* `LotteryEntryDocument`'s PII fields are `Encrypted<string>`, assignable to
|
|
21
|
+
* nothing that wants a plain `string`, so a reader that skips both accessors
|
|
22
|
+
* is a compile error naming the file and line.
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* Plaintext PII. **Opt-in and named** — for the admin entries table and the
|
|
26
|
+
* draw notification, where the real address is the point.
|
|
27
|
+
*/
|
|
28
|
+
revealEntry(doc) {
|
|
29
|
+
return revealPii(doc, LOTTERY_ENTRY_PII_FIELDS);
|
|
30
|
+
}
|
|
31
|
+
/** As `revealEntry`, for a list read. */
|
|
32
|
+
revealEntries(docs) {
|
|
33
|
+
return docs.map((d) => this.revealEntry(d));
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Masked PII — **the default for any surface that only needs to identify a
|
|
37
|
+
* row to a human.** Decrypts first, then masks: handing a stored field
|
|
38
|
+
* straight to `maskName` would return "Anonymous" for every row, because
|
|
39
|
+
* those helpers refuse ciphertext by design.
|
|
40
|
+
*/
|
|
41
|
+
maskedEntry(doc) {
|
|
42
|
+
return maskPii(doc, LOTTERY_ENTRY_PII_FIELDS);
|
|
43
|
+
}
|
|
44
|
+
/** As `maskedEntry`, for a list read. */
|
|
45
|
+
maskedEntries(docs) {
|
|
46
|
+
return docs.map((d) => this.maskedEntry(d));
|
|
16
47
|
}
|
|
17
48
|
/**
|
|
18
49
|
* Get the next sequential userLotteryNumber for a source atomically.
|
|
@@ -78,6 +109,17 @@ class LotteryEntryRepository extends BaseRepository {
|
|
|
78
109
|
});
|
|
79
110
|
}
|
|
80
111
|
/** Create a lottery entry with PII encryption. */
|
|
112
|
+
/*
|
|
113
|
+
* 🛑 Takes PLAINTEXT (`LotteryEntryPlain`), returns CIPHERTEXT
|
|
114
|
+
* (`LotteryEntryDocument`), and the asymmetry is the point.
|
|
115
|
+
*
|
|
116
|
+
* The caller holds a real email and phone; this method encrypts them. Typing
|
|
117
|
+
* the parameter `Omit<LotteryEntryDocument, "id">` would have demanded the
|
|
118
|
+
* caller hand over `Encrypted<string>` it does not have, and the only way to
|
|
119
|
+
* satisfy that is `asEncrypted(email)` — which labels plaintext as ciphertext
|
|
120
|
+
* and is exactly the misuse `asEncrypted`'s own docstring forbids. The
|
|
121
|
+
* encryption happens here; so does the type change.
|
|
122
|
+
*/
|
|
81
123
|
async createEntry(data) {
|
|
82
124
|
try {
|
|
83
125
|
const encrypted = encryptPiiFields(data, [...LOTTERY_ENTRY_PII_FIELDS]);
|
|
@@ -2,7 +2,23 @@
|
|
|
2
2
|
* LotteryEntry Firestore Document Type & Constants
|
|
3
3
|
*/
|
|
4
4
|
import type { LotteryEntryStatus } from "../types";
|
|
5
|
+
import type { Encrypted, Plaintext } from "../../../security/encrypted";
|
|
5
6
|
export declare const LOTTERY_ENTRIES_COLLECTION: "lotteryEntries";
|
|
7
|
+
/**
|
|
8
|
+
* The same entry with its PII as plain `string` — what the two named
|
|
9
|
+
* repository accessors return.
|
|
10
|
+
*
|
|
11
|
+
* 🛑 Derived with a mapped type, never hand-written. A second literal listing
|
|
12
|
+
* the same ~18 fields would drift the first time one was added to only one of
|
|
13
|
+
* them, and the drift is invisible: both compile, and the one nobody updated
|
|
14
|
+
* silently drops the new field from every reveal/mask call site.
|
|
15
|
+
*
|
|
16
|
+
* It is deliberately NOT distinguishable from a MASKED entry. `maskPii` also
|
|
17
|
+
* returns plain strings, and a type cannot express "these characters are
|
|
18
|
+
* asterisks" — the distinction lives in WHICH named method you called, which
|
|
19
|
+
* is why they are two methods rather than one taking a boolean.
|
|
20
|
+
*/
|
|
21
|
+
export type LotteryEntryPlain = Plaintext<LotteryEntryDocument>;
|
|
6
22
|
export type LotteryEntryDocument = {
|
|
7
23
|
id: string;
|
|
8
24
|
sourceType: "event" | "product";
|
|
@@ -11,11 +27,23 @@ export type LotteryEntryDocument = {
|
|
|
11
27
|
/** Set when sourceType === "product" */
|
|
12
28
|
productId?: string;
|
|
13
29
|
userId: string;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
30
|
+
/**
|
|
31
|
+
* PII — CIPHERTEXT as it sits here.
|
|
32
|
+
*
|
|
33
|
+
* 🛑 `Encrypted<string>` is a promise about the VALUE, kept by
|
|
34
|
+
* `LotteryEntryRepository` no longer decrypting in `mapDoc`. Branding a field
|
|
35
|
+
* that is still decrypted on read asserts something false, and `decryptValue`
|
|
36
|
+
* passes a non-`enc:v1:` string straight through — so the lie would silently
|
|
37
|
+
* "work" and surface only on the rows that were genuinely encrypted.
|
|
38
|
+
*
|
|
39
|
+
* Readers call `lotteryEntryRepository.maskedEntry(doc)` (the UI default) or
|
|
40
|
+
* `revealEntry(doc)` (opt-in, named, greppable).
|
|
41
|
+
*/
|
|
42
|
+
userDisplayName?: Encrypted<string>;
|
|
43
|
+
/** PII — ciphertext at rest; see `userDisplayName` */
|
|
44
|
+
userEmail?: Encrypted<string>;
|
|
45
|
+
/** PII — ciphertext at rest; required. See `userDisplayName` */
|
|
46
|
+
userPhone: Encrypted<string>;
|
|
19
47
|
/** User-provided external payment TX ID */
|
|
20
48
|
transactionId: string;
|
|
21
49
|
/** User-provided time of payment */
|
|
@@ -89,7 +89,6 @@ export function MediaImage({ src, alt, size = "card", priority = false, loading,
|
|
|
89
89
|
retryTimeoutRef.current = null;
|
|
90
90
|
}
|
|
91
91
|
};
|
|
92
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
93
92
|
}, [resolvedSrc]);
|
|
94
93
|
const wrapperExtra = [
|
|
95
94
|
rounded ? ROUNDED_MAP[rounded] : "",
|
|
@@ -11,7 +11,13 @@ import { Row, Stack } from "../../ui/components/Layout.js";
|
|
|
11
11
|
import { Alert } from "../../ui/components/Alert.js";
|
|
12
12
|
import { MediaImage } from "./MediaImage.js";
|
|
13
13
|
import { MediaUploadField } from "./upload/MediaUploadField.js";
|
|
14
|
-
|
|
14
|
+
/*
|
|
15
|
+
* The DEFINING module, not `./index` — Root Cause #18. The barrel re-exports
|
|
16
|
+
* this very component, so importing back through it made a two-module import
|
|
17
|
+
* cycle (`audit-import-cycles` component 4), and a cycle makes "which module
|
|
18
|
+
* owns this symbol" unanswerable for every tool that walks the graph.
|
|
19
|
+
*/
|
|
20
|
+
import { useMediaUpload, useMediaCleanup } from "./hooks/useMedia.js";
|
|
15
21
|
import { ADMIN_ENDPOINTS } from "../../constants/api-endpoints.js";
|
|
16
22
|
const MEDIA_LIST_ENDPOINT = ADMIN_ENDPOINTS.MEDIA_LIST;
|
|
17
23
|
const EXISTING_GRID_MAX_HEIGHT = "max-h-96";
|
|
@@ -9,6 +9,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
9
9
|
import { useState, useRef, useCallback } from "react";
|
|
10
10
|
import { useTranslations } from "next-intl";
|
|
11
11
|
import { Cropper } from "react-advanced-cropper";
|
|
12
|
+
import { IMAGE_DOWNSCALE_FORMAT, IMAGE_DOWNSCALE_MAX_EDGE, IMAGE_DOWNSCALE_QUALITY, } from "../upload/downscale-image.js";
|
|
12
13
|
/*
|
|
13
14
|
* 🛑 NO `import "react-advanced-cropper/dist/style.css"` HERE.
|
|
14
15
|
*
|
|
@@ -27,7 +28,10 @@ import { Cropper } from "react-advanced-cropper";
|
|
|
27
28
|
*/
|
|
28
29
|
import { Button, Div, Modal, Row, Stack, Text, useToast } from "../../../ui/index.js";
|
|
29
30
|
import { normalizeError } from "../../../errors/normalize.js";
|
|
30
|
-
export function ImageEditor({ isOpen, imageUrl, onClose, onSave, aspectRatio,
|
|
31
|
+
export function ImageEditor({ isOpen, imageUrl, onClose, onSave, aspectRatio,
|
|
32
|
+
// Shared with the always-on upload downscaler so the crop path and the
|
|
33
|
+
// non-crop path cannot produce differently-sized masters.
|
|
34
|
+
outputFormat = IMAGE_DOWNSCALE_FORMAT, outputQuality = IMAGE_DOWNSCALE_QUALITY, maxOutputWidth = IMAGE_DOWNSCALE_MAX_EDGE, }) {
|
|
31
35
|
const t = useTranslations("mediaEditor");
|
|
32
36
|
const { showToast } = useToast();
|
|
33
37
|
const cropperRef = useRef(null);
|
|
@@ -8,7 +8,23 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
8
8
|
*/
|
|
9
9
|
import { useState, useRef } from "react";
|
|
10
10
|
import { useTranslations } from "next-intl";
|
|
11
|
-
|
|
11
|
+
/*
|
|
12
|
+
* Defining modules, not the `ui` barrel — Root Cause #18.
|
|
13
|
+
*
|
|
14
|
+
* `ui/index.ts` re-exports `FormField`, `PaginatedSelect` and
|
|
15
|
+
* `RecordDetailModal`, and those three import back INTO `features/media` and
|
|
16
|
+
* `features/shell`. So reaching a primitive through the barrel closed a
|
|
17
|
+
* ten-module cycle (`audit-import-cycles` component 1) — while the deep paths
|
|
18
|
+
* below have one direction only. `ui → features/media` is the real dependency
|
|
19
|
+
* and it stays; the barrel hop is what did not need to exist.
|
|
20
|
+
*/
|
|
21
|
+
import { Alert } from "../../../ui/components/Alert.js";
|
|
22
|
+
import { Button } from "../../../ui/components/Button.js";
|
|
23
|
+
import { Div } from "../../../ui/components/Div.js";
|
|
24
|
+
import { Row, Stack } from "../../../ui/components/Layout.js";
|
|
25
|
+
import { Modal } from "../../../ui/components/Modal.js";
|
|
26
|
+
import { Spinner } from "../../../ui/components/Spinner.js";
|
|
27
|
+
import { Span, Text } from "../../../ui/components/Typography.js";
|
|
12
28
|
import { normalizeError } from "../../../errors/normalize.js";
|
|
13
29
|
import { toUserMessage } from "../../../errors/error-display-map.js";
|
|
14
30
|
const __O = {
|
|
@@ -21,6 +21,25 @@ export interface MediaAssetDocument {
|
|
|
21
21
|
* any other upload without parsing filename strings.
|
|
22
22
|
*/
|
|
23
23
|
contextType?: string;
|
|
24
|
+
/**
|
|
25
|
+
* When the watermark was composited INTO the stored bytes, at upload time.
|
|
26
|
+
*
|
|
27
|
+
* 🛑 Absent means "legacy" and is the load-bearing default. Until 2026-10-19
|
|
28
|
+
* `/api/media/[...slug]` sharp-decoded, composited and re-encoded EVERY image
|
|
29
|
+
* response — the cost profile behind the HTTP 402 Fast Origin Transfer
|
|
30
|
+
* suspension (Root Cause #104). Now `/api/media/finalize` bakes the mark once
|
|
31
|
+
* and stamps this field, and the proxy skips the composite when it is set.
|
|
32
|
+
*
|
|
33
|
+
* So an asset uploaded before this existed has no field, the proxy watermarks
|
|
34
|
+
* it on read exactly as it always did, and **no backfill is required** for the
|
|
35
|
+
* 504 rows that predate it. A backfill can run later via the Async Job
|
|
36
|
+
* Primitive, or never.
|
|
37
|
+
*
|
|
38
|
+
* It is written in the SAME operation that stores the watermarked bytes, so a
|
|
39
|
+
* row can never claim a mark the object does not carry — which would show an
|
|
40
|
+
* unwatermarked image, or double-watermark it the other way round.
|
|
41
|
+
*/
|
|
42
|
+
watermarkedAt?: Date;
|
|
24
43
|
createdAt: Date;
|
|
25
44
|
}
|
|
26
45
|
export type MediaAssetCreateInput = Omit<MediaAssetDocument, "id" | "createdAt">;
|
|
@@ -9,7 +9,22 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
9
9
|
import { useEffect, useState } from "react";
|
|
10
10
|
import { useTranslations } from "next-intl";
|
|
11
11
|
import { useCamera } from "../../../react/index.js";
|
|
12
|
-
|
|
12
|
+
/*
|
|
13
|
+
* Defining modules, not the `ui` barrel — Root Cause #18.
|
|
14
|
+
*
|
|
15
|
+
* `ui/index.ts` re-exports `FormField`, `PaginatedSelect` and
|
|
16
|
+
* `RecordDetailModal`, and those three import back INTO `features/media` and
|
|
17
|
+
* `features/shell`. So reaching a primitive through the barrel closed a
|
|
18
|
+
* ten-module cycle (`audit-import-cycles` component 1) — while the deep paths
|
|
19
|
+
* below have one direction only. `ui → features/media` is the real dependency
|
|
20
|
+
* and it stays; the barrel hop is what did not need to exist.
|
|
21
|
+
*/
|
|
22
|
+
import { Alert } from "../../../ui/components/Alert.js";
|
|
23
|
+
import { Button } from "../../../ui/components/Button.js";
|
|
24
|
+
import { Div } from "../../../ui/components/Div.js";
|
|
25
|
+
import { Row, Stack } from "../../../ui/components/Layout.js";
|
|
26
|
+
import { Spinner } from "../../../ui/components/Spinner.js";
|
|
27
|
+
import { Span } from "../../../ui/components/Typography.js";
|
|
13
28
|
export default function CameraCapture({ mode, facingMode = "environment", onCapture, onError, className, }) {
|
|
14
29
|
const t = useTranslations("camera");
|
|
15
30
|
const camera = useCamera();
|
|
@@ -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
|
-
import { IMAGE_ACCEPT_ATTR } from "../../../_internal/shared/media/limits.js";
|
|
3
|
+
import { IMAGE_ACCEPT_ATTR, MAX_IMAGE_BYTES, MEGABYTE, } from "../../../_internal/shared/media/limits.js";
|
|
4
|
+
import { downscaleImageFile } from "./downscale-image.js";
|
|
4
5
|
import { normalizeError } from "../../../errors/normalize.js";
|
|
5
6
|
/**
|
|
6
7
|
* ImageUpload — canonical image upload component for @mohasinac/feat-media.
|
|
@@ -13,7 +14,24 @@ import { useState, useRef } from "react";
|
|
|
13
14
|
import dynamic from "next/dynamic.js";
|
|
14
15
|
import { useTranslations } from "next-intl";
|
|
15
16
|
import { useCamera } from "../../../react/index.js";
|
|
16
|
-
|
|
17
|
+
/*
|
|
18
|
+
* Defining modules, not the `ui` barrel — Root Cause #18.
|
|
19
|
+
*
|
|
20
|
+
* `ui/index.ts` re-exports `FormField`, `PaginatedSelect` and
|
|
21
|
+
* `RecordDetailModal`, and those three import back INTO `features/media` and
|
|
22
|
+
* `features/shell`. So reaching a primitive through the barrel closed a
|
|
23
|
+
* ten-module cycle (`audit-import-cycles` component 1) — while the deep paths
|
|
24
|
+
* below have one direction only. `ui → features/media` is the real dependency
|
|
25
|
+
* and it stays; the barrel hop is what did not need to exist.
|
|
26
|
+
*/
|
|
27
|
+
import { Alert } from "../../../ui/components/Alert.js";
|
|
28
|
+
import { Button } from "../../../ui/components/Button.js";
|
|
29
|
+
import { Div } from "../../../ui/components/Div.js";
|
|
30
|
+
import { Row, Stack } from "../../../ui/components/Layout.js";
|
|
31
|
+
import { Progress } from "../../../ui/components/Progress.js";
|
|
32
|
+
import { Spinner } from "../../../ui/components/Spinner.js";
|
|
33
|
+
import { Label, Text } from "../../../ui/components/Typography.js";
|
|
34
|
+
import { useToast } from "../../../ui/components/toast-context.js";
|
|
17
35
|
import { MediaImage } from "../MediaImage.js";
|
|
18
36
|
import CameraCapture from "./CameraCapture.js";
|
|
19
37
|
// Crop/editor modals pull in image-manipulation libraries (react-advanced-cropper
|
|
@@ -40,7 +58,12 @@ function matchesMimeAccept(fileType, accept) {
|
|
|
40
58
|
return pSub === "*" || pSub === fSub;
|
|
41
59
|
});
|
|
42
60
|
}
|
|
43
|
-
export function ImageUpload({ currentImage, onUpload, onChange,
|
|
61
|
+
export function ImageUpload({ currentImage, onUpload, onChange,
|
|
62
|
+
// 🛑 Never `image/*` — that wildcard matches `image/svg+xml`, which
|
|
63
|
+
// `ALLOWED_IMAGE_MIMES` deliberately excludes as an XSS surface and which the
|
|
64
|
+
// server 422s only after the crop editor has already opened on it. See
|
|
65
|
+
// `IMAGE_ACCEPT_ATTR`'s own docstring in `_internal/shared/media/limits.ts`.
|
|
66
|
+
accept = IMAGE_ACCEPT_ATTR, maxSizeMB = MAX_IMAGE_BYTES / MEGABYTE, label = "Upload Image", helperText, captureSource = "both", enableCrop = true, enableAdvancedCrop = false, cropAspectRatio, onCropDataChange, }) {
|
|
44
67
|
const [uploading, setUploading] = useState(false);
|
|
45
68
|
const [preview, setPreview] = useState(currentImage || "");
|
|
46
69
|
const [error, setError] = useState("");
|
|
@@ -65,7 +88,22 @@ export function ImageUpload({ currentImage, onUpload, onChange, accept = "image/
|
|
|
65
88
|
try {
|
|
66
89
|
setUploading(true);
|
|
67
90
|
setProgress(30);
|
|
68
|
-
|
|
91
|
+
/*
|
|
92
|
+
* The single choke point for all six upload paths (direct, crop-save,
|
|
93
|
+
* crop-close, advanced-save, advanced-close, camera), so bounding the
|
|
94
|
+
* master here covers every one.
|
|
95
|
+
*
|
|
96
|
+
* 🛑 Safe to apply even when `cropData` is present: `ImageCropData` is
|
|
97
|
+
* `{ position, zoom }` in CSS PERCENTAGES — the modal's own header says
|
|
98
|
+
* "Returns CSS position/zoom percentages" and applies them as
|
|
99
|
+
* `left: ${position.x}%` — so it is scale-invariant and a uniform
|
|
100
|
+
* downscale cannot misalign a crop. Pixel-rect crop data would not be.
|
|
101
|
+
*
|
|
102
|
+
* A file already produced by `ImageEditor` comes back untouched (it is
|
|
103
|
+
* webp within 2048 already), so there is no double re-encode.
|
|
104
|
+
*/
|
|
105
|
+
const prepared = await downscaleImageFile(file);
|
|
106
|
+
const url = await onUpload(prepared);
|
|
69
107
|
setProgress(100);
|
|
70
108
|
setPreview(url);
|
|
71
109
|
onChange?.(url);
|
|
@@ -9,14 +9,32 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
9
9
|
import { useState, useRef, useEffect } from "react";
|
|
10
10
|
import { useTranslations } from "next-intl";
|
|
11
11
|
import { useCamera } from "../../../react/index.js";
|
|
12
|
-
|
|
12
|
+
/*
|
|
13
|
+
* Defining modules, not the `ui` barrel — Root Cause #18.
|
|
14
|
+
*
|
|
15
|
+
* `ui/index.ts` re-exports `FormField`, `PaginatedSelect` and
|
|
16
|
+
* `RecordDetailModal`, and those three import back INTO `features/media` and
|
|
17
|
+
* `features/shell`. So reaching a primitive through the barrel closed a
|
|
18
|
+
* ten-module cycle (`audit-import-cycles` component 1) — while the deep paths
|
|
19
|
+
* below have one direction only. `ui → features/media` is the real dependency
|
|
20
|
+
* and it stays; the barrel hop is what did not need to exist.
|
|
21
|
+
*/
|
|
22
|
+
import { Alert } from "../../../ui/components/Alert.js";
|
|
23
|
+
import { Anchor } from "../../../ui/components/Anchor.js";
|
|
24
|
+
import { Button } from "../../../ui/components/Button.js";
|
|
25
|
+
import { Div } from "../../../ui/components/Div.js";
|
|
26
|
+
import { Row, Stack } from "../../../ui/components/Layout.js";
|
|
27
|
+
import { Spinner } from "../../../ui/components/Spinner.js";
|
|
28
|
+
import { Label, Span, Text } from "../../../ui/components/Typography.js";
|
|
13
29
|
import { MediaImage } from "../MediaImage.js";
|
|
14
30
|
import { MediaVideo } from "../MediaVideo.js";
|
|
15
31
|
import dynamic from "next/dynamic.js";
|
|
16
32
|
import { VideoThumbnailSelector } from "../modals/VideoThumbnailSelector.js";
|
|
17
33
|
import CameraCapture from "./CameraCapture.js";
|
|
18
34
|
import { extractVideoPosterFrame, posterBlobAsFile } from "./video-poster.js";
|
|
35
|
+
import { downscaleImageFile } from "./downscale-image.js";
|
|
19
36
|
import { inferMediaTypeFromMime } from "../types/index.js";
|
|
37
|
+
import { ALLOWED_DOC_MIMES, ALLOWED_MIMES, ALLOWED_VIDEO_MIMES, IMAGE_ACCEPT_ATTR, MAX_IMAGE_BYTES, MAX_PDF_BYTES, MAX_VIDEO_BYTES, MEGABYTE, } from "../../../_internal/shared/media/limits.js";
|
|
20
38
|
import { normalizeError } from "../../../errors/normalize.js";
|
|
21
39
|
// Pulls in video-processing libraries most upload sessions never touch —
|
|
22
40
|
// lazy-load rather than bundle into every page rendering a MediaUploadField.
|
|
@@ -24,14 +42,35 @@ const VideoTrimModal = dynamic(() => import("../modals/VideoTrimModal.js").then(
|
|
|
24
42
|
const CLS_PDF_BADGE = "flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-error-surface text-error dark:bg-error-surface dark:text-error";
|
|
25
43
|
const CLS_PDF_NAME = "text-[length:var(--appkit-text-sm)] font-medium text-info underline break-all dark:text-info";
|
|
26
44
|
const CLS_PDF_LINK = "text-[length:var(--appkit-text-sm)] underline break-all text-info dark:text-info";
|
|
45
|
+
/*
|
|
46
|
+
* 🛑 DERIVED from the canonical registry, not restated.
|
|
47
|
+
*
|
|
48
|
+
* The `kind` docstring above has always claimed these "auto-derive … from the
|
|
49
|
+
* canonical media-limits registry". They did not — they were hand-written, and
|
|
50
|
+
* every one of them was MORE PERMISSIVE than the server, which enforces
|
|
51
|
+
* `MAX_BYTES[kind]` in BOTH `/api/media/sign` and `/api/media/finalize`. So the
|
|
52
|
+
* picker accepted work the upload could never complete:
|
|
53
|
+
*
|
|
54
|
+
* image 25 MB → server 10 MB
|
|
55
|
+
* video 200 MB → server 50 MB
|
|
56
|
+
* accept "image/*" → matches `image/svg+xml`, which `ALLOWED_IMAGE_MIMES`
|
|
57
|
+
* deliberately excludes as an XSS surface, and which the
|
|
58
|
+
* server 422s only AFTER the crop editor has opened on it
|
|
59
|
+
* (limits.ts's own `IMAGE_ACCEPT_ATTR` docstring).
|
|
60
|
+
*
|
|
61
|
+
* Deriving makes the refusal happen in the file picker, where it is a rule,
|
|
62
|
+
* rather than at the end, where it reads as a failure.
|
|
63
|
+
*/
|
|
27
64
|
const KIND_DEFAULTS = {
|
|
28
|
-
image: { accept:
|
|
29
|
-
video: { accept: "
|
|
30
|
-
pdf: { accept: "
|
|
65
|
+
image: { accept: IMAGE_ACCEPT_ATTR, maxSizeMB: MAX_IMAGE_BYTES / MEGABYTE },
|
|
66
|
+
video: { accept: ALLOWED_VIDEO_MIMES.join(","), maxSizeMB: MAX_VIDEO_BYTES / MEGABYTE },
|
|
67
|
+
pdf: { accept: ALLOWED_DOC_MIMES.join(","), maxSizeMB: MAX_PDF_BYTES / MEGABYTE },
|
|
31
68
|
};
|
|
32
69
|
const AUTO_KIND_DEFAULTS = {
|
|
33
|
-
accept: "
|
|
34
|
-
|
|
70
|
+
accept: ALLOWED_MIMES.join(","),
|
|
71
|
+
// The most permissive of the three, because `auto` genuinely does not know
|
|
72
|
+
// the kind yet — the per-kind cap is re-checked server-side once it does.
|
|
73
|
+
maxSizeMB: Math.max(MAX_IMAGE_BYTES, MAX_VIDEO_BYTES, MAX_PDF_BYTES) / MEGABYTE,
|
|
35
74
|
};
|
|
36
75
|
function isVideo(url) {
|
|
37
76
|
return /\.(mp4|webm|ogg|mov|avi)(\?|$)/i.test(url);
|
|
@@ -277,14 +316,24 @@ kind, accept: acceptProp, maxSizeMB: maxSizeMBProp, disabled = false, helperText
|
|
|
277
316
|
setPendingVideoUrl(null);
|
|
278
317
|
};
|
|
279
318
|
const uploadSingleFile = async (file) => {
|
|
280
|
-
|
|
319
|
+
/*
|
|
320
|
+
* Bound the pixel dimensions BEFORE the byte check, deliberately.
|
|
321
|
+
*
|
|
322
|
+
* The cap is about what we STORE, and a 9 MB phone photo becomes a few
|
|
323
|
+
* hundred KB here — so checking first would refuse an upload we were about
|
|
324
|
+
* to make acceptable. Non-images and anything undecodable come back
|
|
325
|
+
* untouched (see `downscale-image.ts`), so the check below still does its
|
|
326
|
+
* job for video and PDF.
|
|
327
|
+
*/
|
|
328
|
+
const prepared = await downscaleImageFile(file);
|
|
329
|
+
const fileSizeMB = prepared.size / 1024 / 1024;
|
|
281
330
|
if (fileSizeMB > maxSizeMB) {
|
|
282
|
-
setError(`${
|
|
331
|
+
setError(`${prepared.name}: exceeds ${maxSizeMB}MB limit`);
|
|
283
332
|
return null;
|
|
284
333
|
}
|
|
285
|
-
const url = await onUpload(
|
|
334
|
+
const url = await onUpload(prepared);
|
|
286
335
|
stageUrl(url);
|
|
287
|
-
return { url, type:
|
|
336
|
+
return { url, type: prepared.type };
|
|
288
337
|
};
|
|
289
338
|
/**
|
|
290
339
|
* Client-side first-frame extraction for video uploads. Runs after the
|
|
@@ -373,10 +422,17 @@ kind, accept: acceptProp, maxSizeMB: maxSizeMBProp, disabled = false, helperText
|
|
|
373
422
|
setError(null);
|
|
374
423
|
setIsLoading(true);
|
|
375
424
|
try {
|
|
376
|
-
|
|
377
|
-
|
|
425
|
+
/*
|
|
426
|
+
* Routed through `uploadSingleFile` rather than calling `onUpload`
|
|
427
|
+
* directly, which is what this did until 2026-10-19 — so a camera capture
|
|
428
|
+
* skipped BOTH the `maxSizeMB` check and (once added) the downscale. A
|
|
429
|
+
* full-sensor capture is precisely the large image both exist for.
|
|
430
|
+
*/
|
|
431
|
+
const result = await uploadSingleFile(file);
|
|
432
|
+
if (!result)
|
|
433
|
+
return;
|
|
378
434
|
const poster = await maybeCaptureVideoPoster(file);
|
|
379
|
-
afterUpload(url,
|
|
435
|
+
afterUpload(result.url, result.type, poster?.posterUrl, poster?.duration);
|
|
380
436
|
}
|
|
381
437
|
catch (err) {
|
|
382
438
|
void normalizeError(err);
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* downscaleImageFile — bound an image's pixel dimensions and re-encode it
|
|
3
|
+
* before it ever leaves the browser.
|
|
4
|
+
*
|
|
5
|
+
* ## Why this exists
|
|
6
|
+
*
|
|
7
|
+
* Until 2026-10-19 the ONLY client-side resize in the whole upload path was
|
|
8
|
+
* inside `ImageEditor` (`modals/ImageEditor.tsx`), which runs only when the
|
|
9
|
+
* user opens the crop editor. Everything else stored the original bytes: a 9 MB
|
|
10
|
+
* phone photo was stored at 9 MB, watermarked at 9 MB, and served at 9 MB into
|
|
11
|
+
* a 64px thumbnail, because `/api/media/[...slug]` resizes nothing and
|
|
12
|
+
* `next/image` optimisation was off.
|
|
13
|
+
*
|
|
14
|
+
* That is the shape of the cost that suspended this project with HTTP 402 for
|
|
15
|
+
* Fast Origin Transfer at 5.25x its cap (Root Cause #104). Bounding the master
|
|
16
|
+
* at upload is the one fix that helps every downstream consumer at once.
|
|
17
|
+
*
|
|
18
|
+
* ## Why 2048
|
|
19
|
+
*
|
|
20
|
+
* Measured (docs/research/competitor-crawl-2026-10/images.md): worldhobbyshop
|
|
21
|
+
* serves a 2048x2048 master to photoswipe for zoom, and WordPress's own
|
|
22
|
+
* `-scaled` threshold is 2560. 2048 survives our lightbox's 300% zoom over a
|
|
23
|
+
* ~680px display box with detail to spare, and is 1/4 the pixels of a 4096px
|
|
24
|
+
* phone capture.
|
|
25
|
+
*
|
|
26
|
+
* ## Four cases where it deliberately returns the ORIGINAL file
|
|
27
|
+
*
|
|
28
|
+
* 1. **Not a raster image we own the pipeline for** — anything outside
|
|
29
|
+
* `CONVERTIBLE_MIMES`. SVG is never rasterised (it is also not in
|
|
30
|
+
* `ALLOWED_IMAGE_MIMES`), and GIF is never touched because re-encoding an
|
|
31
|
+
* animated GIF to a still WebP silently destroys the animation.
|
|
32
|
+
* 2. **Already small enough AND already a compact format** — re-encoding a
|
|
33
|
+
* 1200px WebP buys nothing and loses a generation of quality.
|
|
34
|
+
* 3. **The re-encode came out BIGGER.** A small, already-optimised JPEG can
|
|
35
|
+
* grow under WebP q0.9. Shipping the larger file would make this helper a
|
|
36
|
+
* pessimisation, so the original wins.
|
|
37
|
+
* 4. **Anything threw.** 🛑 Fail OPEN. A canvas decode failure, a tainted
|
|
38
|
+
* canvas, a browser without `createImageBitmap` — none of those are a reason
|
|
39
|
+
* to block a seller's upload. The server still enforces `MAX_BYTES[kind]`,
|
|
40
|
+
* so the worst case is the pre-2026-10-19 behaviour, not a hole.
|
|
41
|
+
*/
|
|
42
|
+
/** Longest edge, in CSS pixels, of the stored master. */
|
|
43
|
+
export declare const IMAGE_DOWNSCALE_MAX_EDGE = 2048;
|
|
44
|
+
/** Output MIME for a re-encoded master. */
|
|
45
|
+
export declare const IMAGE_DOWNSCALE_FORMAT = "image/webp";
|
|
46
|
+
/** Output quality for a re-encoded master. */
|
|
47
|
+
export declare const IMAGE_DOWNSCALE_QUALITY = 0.9;
|
|
48
|
+
export interface DownscaleImageOptions {
|
|
49
|
+
/** Longest edge of the output. Default `IMAGE_DOWNSCALE_MAX_EDGE`. */
|
|
50
|
+
maxEdge?: number;
|
|
51
|
+
/** Output MIME. Default `IMAGE_DOWNSCALE_FORMAT`. */
|
|
52
|
+
format?: string;
|
|
53
|
+
/** Output quality, 0..1. Default `IMAGE_DOWNSCALE_QUALITY`. */
|
|
54
|
+
quality?: number;
|
|
55
|
+
}
|
|
56
|
+
export declare function downscaleImageFile(file: File, options?: DownscaleImageOptions): Promise<File>;
|