@mohasinac/appkit 4.27.0 → 4.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_internal/client/features/lottery/LotteryAdminEditView.d.ts +16 -16
- package/dist/_internal/client/features/lottery/LotteryAdminEditView.js +29 -6
- package/dist/_internal/client/features/lottery/LotteryListView.js +13 -1
- package/dist/_internal/server/features/checkout/actions.js +6 -4
- package/dist/_internal/server/features/classified/metadata.js +3 -1
- package/dist/_internal/server/features/digital-code/metadata.js +3 -1
- package/dist/_internal/server/features/live/metadata.js +3 -1
- package/dist/_internal/server/features/refunds/actions.js +3 -2
- package/dist/_internal/server/features/reviews/actions.js +3 -2
- package/dist/_internal/server/features/seo/sitemap.js +189 -40
- package/dist/_internal/server/features/stores/adapters.d.ts +1 -1
- package/dist/_internal/server/features/stores/adapters.js +5 -0
- package/dist/_internal/server/jobs/core/listingProcessor.d.ts +0 -1
- package/dist/_internal/server/jobs/core/listingProcessor.js +19 -1
- package/dist/_internal/shared/actions/action-registry.js +14 -0
- package/dist/_internal/shared/checkout/order-math.d.ts +25 -3
- package/dist/_internal/shared/features/checkout/config.d.ts +3 -3
- package/dist/_internal/shared/features/checkout/config.js +6 -3
- package/dist/client.d.ts +32 -2
- package/dist/client.js +37 -2
- package/dist/constants/api-endpoints.d.ts +21 -3
- package/dist/constants/api-endpoints.js +14 -1
- package/dist/constants/field-names.d.ts +2 -2
- package/dist/constants/field-names.js +2 -2
- package/dist/core/newsletter.repository.js +14 -1
- package/dist/features/about/components/FAQPageView.js +1 -1
- package/dist/features/about/components/FeesView.js +1 -1
- package/dist/features/account/components/AddressForm.d.ts +2 -0
- package/dist/features/account/components/AddressForm.js +5 -1
- package/dist/features/account/components/UserBidsView.js +1 -1
- package/dist/features/account/components/UserSidebar.js +1 -1
- package/dist/features/account/components/UserSupportView.js +24 -2
- package/dist/features/account/hooks/useAddresses.d.ts +29 -1
- package/dist/features/account/hooks/useAddresses.js +4 -1
- package/dist/features/account/hooks/useProfile.d.ts +1 -1
- package/dist/features/account/hooks/useProfile.js +1 -1
- package/dist/features/addresses/repository/addresses.repository.d.ts +13 -0
- package/dist/features/addresses/repository/addresses.repository.js +19 -0
- package/dist/features/admin/actions/chat-actions.js +5 -1
- package/dist/features/admin/components/AdminAdsView.js +1 -1
- package/dist/features/admin/components/AdminBidsView.js +6 -1
- package/dist/features/admin/components/AdminCarouselGroupEditorView.d.ts +9 -1
- package/dist/features/admin/components/AdminCarouselGroupEditorView.js +42 -10
- package/dist/features/admin/components/AdminCarouselView.js +1 -1
- package/dist/features/admin/components/AdminContentGuideView.js +1 -1
- package/dist/features/admin/components/AdminFeatureEditorView.js +1 -1
- package/dist/features/admin/components/AdminGroupedListingsView.js +14 -2
- package/dist/features/admin/components/AdminSidebar.js +1 -1
- package/dist/features/admin/components/AdminViewCards.js +2 -2
- package/dist/features/admin/components/DataTable.js +2 -2
- package/dist/features/admin/schemas/firestore.js +9 -3
- package/dist/features/auctions/actions/bid-actions.js +45 -4
- package/dist/features/auctions/components/AuctionBottomActions.d.ts +21 -6
- package/dist/features/auctions/components/AuctionBottomActions.js +42 -11
- package/dist/features/auctions/components/AuctionDetailPageView.d.ts +8 -3
- package/dist/features/auctions/components/AuctionDetailPageView.js +2 -2
- package/dist/features/auctions/components/MarketplaceAuctionCard.js +4 -5
- package/dist/features/auctions/components/PlaceBidFormClient.d.ts +27 -2
- package/dist/features/auctions/components/PlaceBidFormClient.js +42 -20
- package/dist/features/auth/components/SocialAuthButtons.js +1 -1
- package/dist/features/auth/hooks/useAuth.d.ts +23 -1
- package/dist/features/auth/hooks/useAuth.js +27 -1
- package/dist/features/auth/permissions/constants.d.ts +1 -1
- package/dist/features/auth/permissions/constants.js +3 -0
- package/dist/features/auth/repository/session.repository.d.ts +16 -1
- package/dist/features/auth/repository/session.repository.js +23 -0
- package/dist/features/auth/repository/user.repository.js +13 -2
- package/dist/features/blog/components/BlogListView.js +2 -2
- package/dist/features/blog/repository/blog.repository.d.ts +4 -0
- package/dist/features/blog/repository/blog.repository.js +1 -0
- package/dist/features/blog/schemas/firestore.d.ts +3 -1
- package/dist/features/blog/schemas/firestore.js +1 -0
- package/dist/features/cart/components/CartPriceBreakdown.d.ts +11 -1
- package/dist/features/cart/components/CartPriceBreakdown.js +3 -3
- package/dist/features/cart/components/StoreAddonsPicker.d.ts +9 -0
- package/dist/features/cart/components/StoreAddonsPicker.js +15 -5
- package/dist/features/cart/components/index.d.ts +1 -1
- package/dist/features/cart/components/index.js +1 -1
- package/dist/features/cart/hooks/useGuestCartMerge.d.ts +8 -1
- package/dist/features/cart/hooks/useGuestCartMerge.js +2 -1
- package/dist/features/cart/utils/line-total.d.ts +25 -0
- package/dist/features/cart/utils/line-total.js +26 -0
- package/dist/features/catalogue/repository/catalogue.repository.d.ts +12 -0
- package/dist/features/catalogue/repository/catalogue.repository.js +15 -0
- package/dist/features/categories/components/CategoryTree.js +2 -2
- package/dist/features/contact/components/ContactForm.js +1 -1
- package/dist/features/copilot/hooks/useCopilotFeedback.d.ts +1 -1
- package/dist/features/copilot/hooks/useCopilotFeedback.js +1 -1
- package/dist/features/events/components/AdminEventEditorView.js +16 -9
- package/dist/features/events/components/AdminEventEntriesView.js +7 -13
- package/dist/features/events/components/EventRafflesSection.js +14 -2
- package/dist/features/events/components/EventStatusBadge.js +1 -1
- package/dist/features/events/components/event-entry-detail-fields.d.ts +24 -0
- package/dist/features/events/components/event-entry-detail-fields.js +13 -0
- package/dist/features/events/repository/events.repository.js +1 -0
- package/dist/features/events/schemas/firestore.d.ts +3 -1
- package/dist/features/events/schemas/firestore.js +1 -0
- package/dist/features/faq/components/ContactCTA.js +1 -1
- package/dist/features/faq/components/FAQHelpfulButtons.js +2 -2
- package/dist/features/faq/repository/faqs.repository.d.ts +16 -1
- package/dist/features/faq/repository/faqs.repository.js +64 -54
- package/dist/features/faq/schemas/firestore.d.ts +3 -2
- package/dist/features/faq/schemas/firestore.js +2 -2
- package/dist/features/faq/schemas/index.d.ts +3 -3
- package/dist/features/faq/schemas/index.js +1 -1
- package/dist/features/filters/FilterFacetSection.js +2 -2
- package/dist/features/forms/Toggle.js +1 -1
- package/dist/features/grouped/components/GroupedListingEditorView.d.ts +18 -0
- package/dist/features/grouped/components/GroupedListingEditorView.js +192 -0
- package/dist/features/homepage/components/AdvertisementBanner.js +2 -2
- package/dist/features/homepage/components/BrandsSection.js +1 -1
- package/dist/features/homepage/components/CTABannerSection.js +1 -1
- package/dist/features/homepage/components/HomepageCustomerReviewsSection.js +1 -1
- package/dist/features/homepage/components/HomepageSectionBoundary.d.ts +39 -0
- package/dist/features/homepage/components/HomepageSectionBoundary.js +49 -0
- package/dist/features/homepage/components/ShopByCategorySection.js +1 -1
- package/dist/features/homepage/components/WelcomeSection.js +1 -1
- package/dist/features/homepage/lib/section-renderer.js +2 -1
- package/dist/features/layout/AppLayoutShell.js +5 -5
- package/dist/features/layout/BottomActions.js +5 -5
- package/dist/features/layout/BottomSheet.js +1 -1
- package/dist/features/layout/MainNavbar.js +1 -1
- package/dist/features/layout/NavbarLayout.js +1 -1
- package/dist/features/layout/TitleBarLayout.js +3 -3
- package/dist/features/layout/hooks/useFormBottomActions.d.ts +11 -1
- package/dist/features/layout/hooks/useFormBottomActions.js +16 -2
- package/dist/features/lottery/schemas/config-write.d.ts +141 -0
- package/dist/features/lottery/schemas/config-write.js +139 -0
- package/dist/features/media/MediaPickerModal.js +3 -1
- package/dist/features/media/MediaVideo.js +4 -2
- package/dist/features/media/components/MediaSlider.js +1 -1
- package/dist/features/messages/repository/conversations.repository.js +15 -5
- package/dist/features/orders/components/OrdersList.js +2 -2
- package/dist/features/payments/repository/payout.repository.d.ts +11 -1
- package/dist/features/payments/repository/payout.repository.js +14 -6
- package/dist/features/pre-orders/components/MarketplacePreorderCard.js +3 -2
- package/dist/features/pre-orders/components/PreOrderBadge.js +1 -1
- package/dist/features/pre-orders/components/PreOrderDetailPageView.js +1 -1
- package/dist/features/products/columns/productTableColumns.js +3 -3
- package/dist/features/products/components/CompareOverlay.js +1 -1
- package/dist/features/products/components/PrizeDrawDetailPageView.js +1 -1
- package/dist/features/products/components/PrizeDrawEntryActions.js +2 -2
- package/dist/features/products/components/PrizeDrawsSection.js +5 -1
- package/dist/features/products/components/ProductDetailActions.js +11 -7
- package/dist/features/products/components/ProductDetailPageView.js +3 -3
- package/dist/features/products/components/ProductFeatureBadges.js +2 -2
- package/dist/features/products/components/ProductFeaturesSelector.js +1 -1
- package/dist/features/products/components/ProductFilters.js +15 -8
- package/dist/features/products/components/ProductGrid.js +8 -17
- package/dist/features/products/components/ProductTabs.js +7 -4
- package/dist/features/products/components/ProductTabsShell.js +2 -7
- package/dist/features/products/constants/action-defs.d.ts +21 -4
- package/dist/features/products/constants/action-defs.js +7 -3
- package/dist/features/products/hooks/useRelatedProducts.js +2 -1
- package/dist/features/products/repository/products.repository.d.ts +1 -1
- package/dist/features/products/repository/products.repository.js +38 -16
- package/dist/features/products/schemas/firestore.d.ts +3 -2
- package/dist/features/products/schemas/firestore.js +1 -1
- package/dist/features/reviews/actions/review-actions.d.ts +8 -1
- package/dist/features/reviews/actions/review-actions.js +14 -3
- package/dist/features/reviews/components/ReviewDetailShell.js +10 -4
- package/dist/features/reviews/components/ReviewModal.js +1 -1
- package/dist/features/reviews/repository/reviews.repository.d.ts +29 -1
- package/dist/features/reviews/repository/reviews.repository.js +54 -13
- package/dist/features/reviews/schemas/firestore.d.ts +9 -1
- package/dist/features/reviews/schemas/firestore.js +1 -0
- package/dist/features/scams/components/ScamRegistryView.js +2 -2
- package/dist/features/search/components/Search.js +3 -3
- package/dist/features/search/components/SearchResultsSection.js +1 -1
- package/dist/features/seller/components/SellerAddressesView.js +3 -1
- package/dist/features/seller/components/SellerBidsView.js +6 -1
- package/dist/features/seller/components/SellerSidebar.js +1 -1
- package/dist/features/shell/SectionForm.d.ts +21 -1
- package/dist/features/shell/SectionForm.js +15 -3
- package/dist/features/shell/build-sections.js +10 -1
- package/dist/features/shell/field-ui-meta.d.ts +21 -0
- package/dist/features/shipments/components/AdminShipmentLotItemsView.js +23 -6
- package/dist/features/site-settings/components/ActionPermissionsManager.js +1 -1
- package/dist/features/store-extensions/repository/store-extensions.repositories.d.ts +21 -4
- package/dist/features/store-extensions/repository/store-extensions.repositories.js +24 -4
- package/dist/features/store-extensions/schemas/shipping-config-form.js +13 -0
- package/dist/features/store-extensions/schemas/store-category-form.js +1 -1
- package/dist/features/stores/actions/store-query-actions.js +25 -1
- package/dist/features/stores/components/InteractiveStoreCard.js +1 -1
- package/dist/features/stores/components/StoreHeader.js +1 -1
- package/dist/features/stores/components/StoreNavTabs.js +20 -8
- package/dist/features/stores/repository/store.repository.js +1 -0
- package/dist/features/stores/schemas/firestore.d.ts +2 -0
- package/dist/features/support/repository/support.repository.d.ts +12 -0
- package/dist/features/support/repository/support.repository.js +15 -0
- package/dist/features/support/schemas/ticket-create-form.d.ts +33 -0
- package/dist/features/support/schemas/ticket-create-form.js +82 -0
- package/dist/features/tester/repository/tester-checklist-item.repository.d.ts +0 -1
- package/dist/features/tester/repository/tester-checklist-item.repository.js +6 -15
- package/dist/features/tester/schemas/firestore.d.ts +5 -4
- package/dist/features/tester/schemas/firestore.js +3 -3
- package/dist/features/tester/seed-data/products-tester-seed-data.js +2 -2
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +138 -1
- package/dist/features/wishlist/components/WishlistHeartButton.d.ts +55 -0
- package/dist/features/wishlist/components/WishlistHeartButton.js +52 -0
- package/dist/features/wishlist/components/index.d.ts +2 -1
- package/dist/features/wishlist/components/index.js +1 -1
- package/dist/index.d.ts +10 -3
- package/dist/index.js +19 -4
- package/dist/monitoring/error-tracking.js +39 -5
- package/dist/monitoring/server-logger.js +70 -32
- package/dist/next/components/ErrorView.js +1 -1
- package/dist/next/routing/route-map.d.ts +39 -7
- package/dist/next/routing/route-map.js +34 -4
- package/dist/providers/db-firebase/base.repository.d.ts +30 -0
- package/dist/providers/db-firebase/base.repository.js +56 -9
- package/dist/react/ErrorBoundary.js +9 -1
- package/dist/react/contexts/SessionContext.js +6 -2
- package/dist/react/hooks/useAuthGate.d.ts +22 -0
- package/dist/react/hooks/useAuthGate.js +86 -7
- package/dist/schemas/registry.d.ts +3 -3
- package/dist/security/index.d.ts +1 -1
- package/dist/security/index.js +1 -1
- package/dist/security/pii-encrypt.d.ts +13 -5
- package/dist/security/pii-encrypt.js +132 -6
- package/dist/security/pii-schemas.d.ts +32 -4
- package/dist/security/pii-schemas.js +38 -5
- package/dist/seed/_helpers/product-search-txt.d.ts +18 -0
- package/dist/seed/_helpers/product-search-txt.js +33 -0
- package/dist/seed/_helpers/search-txt-wrappers.d.ts +20 -0
- package/dist/seed/_helpers/search-txt-wrappers.js +73 -0
- package/dist/seed/blog-posts-seed-data.d.ts +4 -0
- package/dist/seed/blog-posts-seed-data.js +7 -1
- package/dist/seed/events-seed-data.d.ts +5 -1
- package/dist/seed/events-seed-data.js +7 -1
- package/dist/seed/faq-seed-data.d.ts +16 -0
- package/dist/seed/faq-seed-data.js +22 -1
- package/dist/seed/products-art-seed-data.js +49 -7
- package/dist/seed/products-auctions-seed-data.js +12 -4
- package/dist/seed/products-classifieds-seed-data.d.ts +5 -0
- package/dist/seed/products-classifieds-seed-data.js +65 -9
- package/dist/seed/products-digital-codes-seed-data.d.ts +5 -0
- package/dist/seed/products-digital-codes-seed-data.js +65 -9
- package/dist/seed/products-live-items-seed-data.d.ts +5 -0
- package/dist/seed/products-live-items-seed-data.js +33 -5
- package/dist/seed/products-preorders-seed-data.d.ts +1 -1
- package/dist/seed/products-preorders-seed-data.js +6 -2
- package/dist/seed/products-prize-draws-seed-data.d.ts +5 -0
- package/dist/seed/products-prize-draws-seed-data.js +57 -8
- package/dist/seed/products-standard-seed-data.js +6 -2
- package/dist/seed/products-stickers-seed-data.js +49 -7
- package/dist/seed/reviews-seed-data.js +2 -1
- package/dist/seed/site-settings-seed-data.js +5 -3
- package/dist/seed/stores-seed-data.d.ts +4 -0
- package/dist/seed/stores-seed-data.js +7 -1
- package/dist/seo/json-ld.d.ts +44 -2
- package/dist/seo/json-ld.js +57 -11
- package/dist/styles.css +368 -90
- package/dist/tailwind-utilities.css +1 -1
- package/dist/tokens/index.d.ts +1 -1
- package/dist/tokens/index.js +1 -1
- package/dist/tokens/themes/default-dark.js +6 -0
- package/dist/tokens/themes/default-light.js +5 -0
- package/dist/tokens/tokens.css +84 -0
- package/dist/ui/DataTable.js +1 -1
- package/dist/ui/DataTable.style.css +3 -3
- package/dist/ui/components/BulkActionBar.style.css +1 -3
- package/dist/ui/components/Button.js +15 -6
- package/dist/ui/components/Dropdown.style.css +1 -4
- package/dist/ui/components/FilterChipGroup.js +1 -1
- package/dist/ui/components/FilterDrawer.style.css +1 -1
- package/dist/ui/components/Icon.d.ts +53 -0
- package/dist/ui/components/Icon.js +41 -0
- package/dist/ui/components/IconButton.d.ts +13 -0
- package/dist/ui/components/IconButton.js +13 -0
- package/dist/ui/components/IconButton.style.css +18 -10
- package/dist/ui/components/ListingLayout.style.css +2 -10
- package/dist/ui/components/ListingToolbar.js +6 -6
- package/dist/ui/components/Modal.js +1 -1
- package/dist/ui/components/PaginatedSelect.style.css +3 -3
- package/dist/ui/components/Pagination.style.css +1 -6
- package/dist/ui/components/RichTextEditor.js +1 -1
- package/dist/ui/components/RowActionMenu.style.css +1 -1
- package/dist/ui/components/SideDrawer.style.css +1 -1
- package/dist/ui/components/Slider.js +1 -1
- package/dist/ui/components/TabBarShell.d.ts +78 -0
- package/dist/ui/components/TabBarShell.js +95 -0
- package/dist/ui/components/Tabs.js +12 -8
- package/dist/ui/components/Tabs.style.css +249 -41
- package/dist/ui/components/ViewToggle.style.css +1 -5
- package/dist/ui/components/tabs-overflow.d.ts +17 -0
- package/dist/ui/components/tabs-overflow.js +142 -0
- package/dist/ui/icons/icon-registry.d.ts +38 -0
- package/dist/ui/icons/icon-registry.js +37 -0
- package/dist/ui/index.d.ts +6 -0
- package/dist/ui/index.js +3 -0
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/utils/media-url.d.ts +27 -4
- package/dist/utils/media-url.js +37 -6
- package/dist/utils/search-txt.d.ts +71 -0
- package/dist/utils/search-txt.js +118 -0
- package/package.json +1 -1
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { LotteryConfig } from "../types/index";
|
|
3
|
+
/**
|
|
4
|
+
* What an admin may say about one slot. Booking state is deliberately absent
|
|
5
|
+
* — see the header. `.strict()`, so `isBooked` is a 400.
|
|
6
|
+
*/
|
|
7
|
+
export declare const lotterySlotWriteSchema: z.ZodObject<{
|
|
8
|
+
slotNumber: z.ZodNumber;
|
|
9
|
+
name: z.ZodString;
|
|
10
|
+
image: z.ZodOptional<z.ZodString>;
|
|
11
|
+
price: z.ZodNumber;
|
|
12
|
+
}, "strict", z.ZodTypeAny, {
|
|
13
|
+
name: string;
|
|
14
|
+
price: number;
|
|
15
|
+
slotNumber: number;
|
|
16
|
+
image?: string | undefined;
|
|
17
|
+
}, {
|
|
18
|
+
name: string;
|
|
19
|
+
price: number;
|
|
20
|
+
slotNumber: number;
|
|
21
|
+
image?: string | undefined;
|
|
22
|
+
}>;
|
|
23
|
+
export declare const lotteryConfigWriteSchema: z.ZodEffects<z.ZodEffects<z.ZodObject<{
|
|
24
|
+
slots: z.ZodArray<z.ZodObject<{
|
|
25
|
+
slotNumber: z.ZodNumber;
|
|
26
|
+
name: z.ZodString;
|
|
27
|
+
image: z.ZodOptional<z.ZodString>;
|
|
28
|
+
price: z.ZodNumber;
|
|
29
|
+
}, "strict", z.ZodTypeAny, {
|
|
30
|
+
name: string;
|
|
31
|
+
price: number;
|
|
32
|
+
slotNumber: number;
|
|
33
|
+
image?: string | undefined;
|
|
34
|
+
}, {
|
|
35
|
+
name: string;
|
|
36
|
+
price: number;
|
|
37
|
+
slotNumber: number;
|
|
38
|
+
image?: string | undefined;
|
|
39
|
+
}>, "many">;
|
|
40
|
+
pricingMode: z.ZodEnum<["uniform", "variable"]>;
|
|
41
|
+
uniformPrice: z.ZodOptional<z.ZodNumber>;
|
|
42
|
+
drawWindowDurationMinutes: z.ZodNumber;
|
|
43
|
+
maxPullsPerTransaction: z.ZodNumber;
|
|
44
|
+
maxPullsPerUser: z.ZodNumber;
|
|
45
|
+
}, "strict", z.ZodTypeAny, {
|
|
46
|
+
slots: {
|
|
47
|
+
name: string;
|
|
48
|
+
price: number;
|
|
49
|
+
slotNumber: number;
|
|
50
|
+
image?: string | undefined;
|
|
51
|
+
}[];
|
|
52
|
+
pricingMode: "uniform" | "variable";
|
|
53
|
+
drawWindowDurationMinutes: number;
|
|
54
|
+
maxPullsPerTransaction: number;
|
|
55
|
+
maxPullsPerUser: number;
|
|
56
|
+
uniformPrice?: number | undefined;
|
|
57
|
+
}, {
|
|
58
|
+
slots: {
|
|
59
|
+
name: string;
|
|
60
|
+
price: number;
|
|
61
|
+
slotNumber: number;
|
|
62
|
+
image?: string | undefined;
|
|
63
|
+
}[];
|
|
64
|
+
pricingMode: "uniform" | "variable";
|
|
65
|
+
drawWindowDurationMinutes: number;
|
|
66
|
+
maxPullsPerTransaction: number;
|
|
67
|
+
maxPullsPerUser: number;
|
|
68
|
+
uniformPrice?: number | undefined;
|
|
69
|
+
}>, {
|
|
70
|
+
slots: {
|
|
71
|
+
name: string;
|
|
72
|
+
price: number;
|
|
73
|
+
slotNumber: number;
|
|
74
|
+
image?: string | undefined;
|
|
75
|
+
}[];
|
|
76
|
+
pricingMode: "uniform" | "variable";
|
|
77
|
+
drawWindowDurationMinutes: number;
|
|
78
|
+
maxPullsPerTransaction: number;
|
|
79
|
+
maxPullsPerUser: number;
|
|
80
|
+
uniformPrice?: number | undefined;
|
|
81
|
+
}, {
|
|
82
|
+
slots: {
|
|
83
|
+
name: string;
|
|
84
|
+
price: number;
|
|
85
|
+
slotNumber: number;
|
|
86
|
+
image?: string | undefined;
|
|
87
|
+
}[];
|
|
88
|
+
pricingMode: "uniform" | "variable";
|
|
89
|
+
drawWindowDurationMinutes: number;
|
|
90
|
+
maxPullsPerTransaction: number;
|
|
91
|
+
maxPullsPerUser: number;
|
|
92
|
+
uniformPrice?: number | undefined;
|
|
93
|
+
}>, {
|
|
94
|
+
slots: {
|
|
95
|
+
name: string;
|
|
96
|
+
price: number;
|
|
97
|
+
slotNumber: number;
|
|
98
|
+
image?: string | undefined;
|
|
99
|
+
}[];
|
|
100
|
+
pricingMode: "uniform" | "variable";
|
|
101
|
+
drawWindowDurationMinutes: number;
|
|
102
|
+
maxPullsPerTransaction: number;
|
|
103
|
+
maxPullsPerUser: number;
|
|
104
|
+
uniformPrice?: number | undefined;
|
|
105
|
+
}, {
|
|
106
|
+
slots: {
|
|
107
|
+
name: string;
|
|
108
|
+
price: number;
|
|
109
|
+
slotNumber: number;
|
|
110
|
+
image?: string | undefined;
|
|
111
|
+
}[];
|
|
112
|
+
pricingMode: "uniform" | "variable";
|
|
113
|
+
drawWindowDurationMinutes: number;
|
|
114
|
+
maxPullsPerTransaction: number;
|
|
115
|
+
maxPullsPerUser: number;
|
|
116
|
+
uniformPrice?: number | undefined;
|
|
117
|
+
}>;
|
|
118
|
+
export type LotteryConfigWriteInput = z.infer<typeof lotteryConfigWriteSchema>;
|
|
119
|
+
/** Why a merge was refused. Distinct from a validation failure. */
|
|
120
|
+
export interface LotteryMergeRejection {
|
|
121
|
+
ok: false;
|
|
122
|
+
code: "BOOKED_SLOT_REMOVED";
|
|
123
|
+
message: string;
|
|
124
|
+
slotNumbers: number[];
|
|
125
|
+
}
|
|
126
|
+
export type LotteryMergeResult = {
|
|
127
|
+
ok: true;
|
|
128
|
+
config: LotteryConfig;
|
|
129
|
+
} | LotteryMergeRejection;
|
|
130
|
+
/**
|
|
131
|
+
* Apply an admin's edit to the stored config, carrying every booking across.
|
|
132
|
+
*
|
|
133
|
+
* `existing` is undefined when the lottery is being created, in which case
|
|
134
|
+
* there is nothing to preserve and every slot starts unbooked.
|
|
135
|
+
*
|
|
136
|
+
* `totalSlots` is DERIVED from `slots.length` and never accepted from input —
|
|
137
|
+
* a caller-supplied count that disagrees with the array is exactly the
|
|
138
|
+
* mirror-drift trap (Root Cause #42), and `totalSlots` is what the fullness
|
|
139
|
+
* check reads.
|
|
140
|
+
*/
|
|
141
|
+
export declare function mergeLotteryConfig(input: LotteryConfigWriteInput, existing?: LotteryConfig): LotteryMergeResult;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* WHY: `lotteryConfig` had NO write schema. The only authoring UI
|
|
3
|
+
* (`LotteryAdminEditView`) was orphaned, and the route it would have
|
|
4
|
+
* posted to — `PATCH /api/admin/events/[id]` — is `.passthrough()`, so
|
|
5
|
+
* the whole config wrote through completely unvalidated.
|
|
6
|
+
* WHAT: The write contract, plus `mergeLotteryConfig` — the one function that
|
|
7
|
+
* is allowed to turn an admin's edit into a stored config.
|
|
8
|
+
*
|
|
9
|
+
* ## 🛑 The author-side slot shape CANNOT carry booking state
|
|
10
|
+
*
|
|
11
|
+
* `LotteryAdminEditView.handleSubmit` sent `isBooked: false` and `weight: 0`
|
|
12
|
+
* for every slot, and dropped `bookedByUserId` / `bookedByDisplayName` /
|
|
13
|
+
* `bookedByUserLotteryNumber` entirely. Against a `.passthrough()` PATCH that
|
|
14
|
+
* is not a validation gap — it is a **destructive write**: the first save of
|
|
15
|
+
* a live lottery marks every purchased slot as available again, and the
|
|
16
|
+
* buyers who paid for them are simply gone from the record.
|
|
17
|
+
*
|
|
18
|
+
* So the input type deliberately has NO booking fields at all. An admin
|
|
19
|
+
* editing a lottery is describing PRIZES, not attendance, and the schema
|
|
20
|
+
* refuses to express the latter — a `.strict()` object, so sending
|
|
21
|
+
* `isBooked` is a 400 rather than something a merge has to defend against.
|
|
22
|
+
*
|
|
23
|
+
* `weight` is absent for the same reason: it is derived from price
|
|
24
|
+
* server-side and is never client input.
|
|
25
|
+
*
|
|
26
|
+
* ## Merging is BY `slotNumber`, not by index
|
|
27
|
+
*
|
|
28
|
+
* Array position is not identity. An admin who deletes slot 3 shifts every
|
|
29
|
+
* later slot's index by one, and an index-wise merge would hand slot 7's
|
|
30
|
+
* buyer the prize that used to be slot 8. `slotNumber` is the stable key
|
|
31
|
+
* users actually see and pull against.
|
|
32
|
+
*
|
|
33
|
+
* A slot that is booked cannot be REMOVED either. Dropping it would erase a
|
|
34
|
+
* paid entry with no trace; the merge rejects that rather than silently
|
|
35
|
+
* carrying the slot along, because an admin who thinks they deleted a prize
|
|
36
|
+
* and did not is a worse outcome than an error.
|
|
37
|
+
*
|
|
38
|
+
* EXPORTS: lotteryConfigWriteSchema, lotterySlotWriteSchema,
|
|
39
|
+
* mergeLotteryConfig, type LotteryConfigWriteInput
|
|
40
|
+
*
|
|
41
|
+
* @tag domain:lottery
|
|
42
|
+
* @tag layer:schema
|
|
43
|
+
* @tag pattern:none
|
|
44
|
+
* @tag access:isomorphic
|
|
45
|
+
* @tag consumers:/api/admin/events/[id]/lottery-config,LotteryAdminEditView
|
|
46
|
+
* @tag sideEffects:none
|
|
47
|
+
*/
|
|
48
|
+
import { z } from "zod";
|
|
49
|
+
/** Mirrors `LotteryPricingMode`. */
|
|
50
|
+
const pricingModeSchema = z.enum(["uniform", "variable"]);
|
|
51
|
+
/**
|
|
52
|
+
* What an admin may say about one slot. Booking state is deliberately absent
|
|
53
|
+
* — see the header. `.strict()`, so `isBooked` is a 400.
|
|
54
|
+
*/
|
|
55
|
+
export const lotterySlotWriteSchema = z
|
|
56
|
+
.object({
|
|
57
|
+
slotNumber: z.coerce.number().int().min(1).max(200),
|
|
58
|
+
name: z.string().min(1, "Every slot needs a prize name.").max(200),
|
|
59
|
+
image: z.string().max(2000).optional(),
|
|
60
|
+
price: z.coerce.number().min(0, "A price cannot be negative."),
|
|
61
|
+
})
|
|
62
|
+
.strict();
|
|
63
|
+
export const lotteryConfigWriteSchema = z
|
|
64
|
+
.object({
|
|
65
|
+
slots: z
|
|
66
|
+
.array(lotterySlotWriteSchema)
|
|
67
|
+
.min(1, "A lottery needs at least one slot.")
|
|
68
|
+
.max(200, "A lottery cannot exceed 200 slots."),
|
|
69
|
+
pricingMode: pricingModeSchema,
|
|
70
|
+
uniformPrice: z.coerce.number().min(0).optional(),
|
|
71
|
+
drawWindowDurationMinutes: z.coerce.number().int().min(1).max(1440),
|
|
72
|
+
maxPullsPerTransaction: z.coerce.number().int().min(1).max(200),
|
|
73
|
+
maxPullsPerUser: z.coerce.number().int().min(1).max(200),
|
|
74
|
+
})
|
|
75
|
+
.strict()
|
|
76
|
+
.refine((c) => new Set(c.slots.map((s) => s.slotNumber)).size === c.slots.length, { message: "Two slots share a slot number.", path: ["slots"] })
|
|
77
|
+
.refine((c) => c.pricingMode !== "uniform" || typeof c.uniformPrice === "number", {
|
|
78
|
+
message: "Uniform pricing needs a price.",
|
|
79
|
+
path: ["uniformPrice"],
|
|
80
|
+
});
|
|
81
|
+
/**
|
|
82
|
+
* Apply an admin's edit to the stored config, carrying every booking across.
|
|
83
|
+
*
|
|
84
|
+
* `existing` is undefined when the lottery is being created, in which case
|
|
85
|
+
* there is nothing to preserve and every slot starts unbooked.
|
|
86
|
+
*
|
|
87
|
+
* `totalSlots` is DERIVED from `slots.length` and never accepted from input —
|
|
88
|
+
* a caller-supplied count that disagrees with the array is exactly the
|
|
89
|
+
* mirror-drift trap (Root Cause #42), and `totalSlots` is what the fullness
|
|
90
|
+
* check reads.
|
|
91
|
+
*/
|
|
92
|
+
export function mergeLotteryConfig(input, existing) {
|
|
93
|
+
const priorBySlot = new Map();
|
|
94
|
+
for (const slot of existing?.slots ?? [])
|
|
95
|
+
priorBySlot.set(slot.slotNumber, slot);
|
|
96
|
+
const incoming = new Set(input.slots.map((s) => s.slotNumber));
|
|
97
|
+
const droppedBooked = [...priorBySlot.values()]
|
|
98
|
+
.filter((s) => s.isBooked && !incoming.has(s.slotNumber))
|
|
99
|
+
.map((s) => s.slotNumber);
|
|
100
|
+
if (droppedBooked.length > 0) {
|
|
101
|
+
return {
|
|
102
|
+
ok: false,
|
|
103
|
+
code: "BOOKED_SLOT_REMOVED",
|
|
104
|
+
message: `Slot ${droppedBooked.join(", ")} has already been pulled and cannot be ` +
|
|
105
|
+
`removed. Reopen it first if the pull needs to be undone.`,
|
|
106
|
+
slotNumbers: droppedBooked,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
const slots = input.slots.map((s) => {
|
|
110
|
+
const prior = priorBySlot.get(s.slotNumber);
|
|
111
|
+
return {
|
|
112
|
+
slotNumber: s.slotNumber,
|
|
113
|
+
name: s.name,
|
|
114
|
+
image: s.image?.trim() || undefined,
|
|
115
|
+
price: input.pricingMode === "uniform" ? (input.uniformPrice ?? 0) : s.price,
|
|
116
|
+
// Server-derived, never input. Recomputed by the pricing pass; carried
|
|
117
|
+
// from the prior slot so an edit does not zero a live weighting.
|
|
118
|
+
weight: prior?.weight ?? 0,
|
|
119
|
+
// 🛑 THE WHOLE POINT: booking state comes from the STORED slot, never
|
|
120
|
+
// from the request. A slot the admin renames keeps its buyer.
|
|
121
|
+
isBooked: prior?.isBooked ?? false,
|
|
122
|
+
bookedByUserId: prior?.bookedByUserId,
|
|
123
|
+
bookedByDisplayName: prior?.bookedByDisplayName,
|
|
124
|
+
bookedByUserLotteryNumber: prior?.bookedByUserLotteryNumber,
|
|
125
|
+
};
|
|
126
|
+
});
|
|
127
|
+
return {
|
|
128
|
+
ok: true,
|
|
129
|
+
config: {
|
|
130
|
+
slots,
|
|
131
|
+
totalSlots: slots.length,
|
|
132
|
+
pricingMode: input.pricingMode,
|
|
133
|
+
uniformPrice: input.pricingMode === "uniform" ? input.uniformPrice : undefined,
|
|
134
|
+
drawWindowDurationMinutes: input.drawWindowDurationMinutes,
|
|
135
|
+
maxPullsPerTransaction: input.maxPullsPerTransaction,
|
|
136
|
+
maxPullsPerUser: input.maxPullsPerUser,
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
}
|
|
@@ -57,8 +57,10 @@ export function MediaPickerModal({ isOpen, onClose, onSelect, accept = "image/*"
|
|
|
57
57
|
setExistingFiles(data.data.files);
|
|
58
58
|
})
|
|
59
59
|
.catch((err) => {
|
|
60
|
+
// normalizeError covers Zod/Api/Firebase shapes an `instanceof Error`
|
|
61
|
+
// check misses, so those stop collapsing to the generic fallback.
|
|
60
62
|
if (!cancelled)
|
|
61
|
-
setExistingError(err
|
|
63
|
+
setExistingError(normalizeError(err).message || "Failed to load");
|
|
62
64
|
})
|
|
63
65
|
.finally(() => {
|
|
64
66
|
if (!cancelled)
|
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useRef, useEffect } from "react";
|
|
4
4
|
import { Div, Row, Span } from "../../ui";
|
|
5
|
-
import { resolveMediaUrl, getYouTubeVideoId } from "../../utils/media-url";
|
|
5
|
+
import { resolveMediaUrl, resolveVideoUrl, getYouTubeVideoId } from "../../utils/media-url";
|
|
6
6
|
import { useSiteSettings } from "../../core/hooks/useSiteSettings";
|
|
7
7
|
export function MediaVideo({ src, thumbnailUrl, alt = "Video", controls = true, autoPlayMuted = false, loop = false, trimStart, trimEnd, objectFit = "cover", watermark, }) {
|
|
8
8
|
const videoRef = useRef(null);
|
|
9
9
|
const fitClass = objectFit === "contain" ? "object-contain" : "object-cover";
|
|
10
|
-
|
|
10
|
+
// Video src must NOT fall through to /api/media/ext (image-only, 400s on
|
|
11
|
+
// video/mp4). The poster is an image and stays on the normal image path.
|
|
12
|
+
const resolvedSrc = resolveVideoUrl(src);
|
|
11
13
|
const resolvedPoster = resolveMediaUrl(thumbnailUrl);
|
|
12
14
|
const youtubeId = getYouTubeVideoId(src);
|
|
13
15
|
// Apply trimStart on load
|
|
@@ -5,7 +5,7 @@ export function MediaSlider({ value, min = 0, max = 100, step = 1, onChange, onC
|
|
|
5
5
|
// drag/cancel state; nothing here persists across a cancelled touch.
|
|
6
6
|
onTouchEnd: (e) => onChangeEnd?.(parseFloat(e.target.value)), className: [
|
|
7
7
|
"w-full h-2 appearance-none cursor-pointer rounded-full",
|
|
8
|
-
"
|
|
8
|
+
" bg-[var(--appkit-color-border)]",
|
|
9
9
|
"accent-primary-600 dark:accent-secondary-400",
|
|
10
10
|
className,
|
|
11
11
|
]
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
* or unread counters.
|
|
10
10
|
*/
|
|
11
11
|
import { getAdminDb } from "../../../providers/db-firebase";
|
|
12
|
+
import { encryptPiiFields, decryptPiiFields } from "../../../security/pii-encrypt";
|
|
13
|
+
import { CONVERSATION_PII_FIELDS } from "../../../security/pii-schemas";
|
|
12
14
|
import { serverLogger } from "../../../monitoring";
|
|
13
15
|
import { CONVERSATION_FIELDS } from "../../../constants/field-names";
|
|
14
16
|
import { CONVERSATIONS_COLLECTION, } from "../schemas/firestore";
|
|
@@ -37,7 +39,11 @@ function normaliseMessage(raw) {
|
|
|
37
39
|
};
|
|
38
40
|
}
|
|
39
41
|
function normaliseDoc(id, raw) {
|
|
40
|
-
|
|
42
|
+
// Decrypt here rather than at the six call sites: this is the one function
|
|
43
|
+
// every read path already goes through, so a new reader cannot forget it.
|
|
44
|
+
// `markRead` deliberately does NOT decrypt — it reads raw messages, flips
|
|
45
|
+
// isRead and writes them straight back, so ciphertext round-trips untouched.
|
|
46
|
+
const r = decryptPiiFields(raw, [...CONVERSATION_PII_FIELDS]);
|
|
41
47
|
return {
|
|
42
48
|
id,
|
|
43
49
|
buyerId: String(r.buyerId ?? ""),
|
|
@@ -102,8 +108,9 @@ export class ConversationsRepository {
|
|
|
102
108
|
createdAt: now,
|
|
103
109
|
updatedAt: now,
|
|
104
110
|
};
|
|
105
|
-
|
|
106
|
-
|
|
111
|
+
const encryptedDoc = encryptPiiFields(newDoc, [...CONVERSATION_PII_FIELDS]);
|
|
112
|
+
tx.set(ref, encryptedDoc);
|
|
113
|
+
return normaliseDoc(stableId, encryptedDoc);
|
|
107
114
|
});
|
|
108
115
|
}
|
|
109
116
|
async findById(id) {
|
|
@@ -168,8 +175,11 @@ export class ConversationsRepository {
|
|
|
168
175
|
unreadSeller,
|
|
169
176
|
updatedAt: now,
|
|
170
177
|
};
|
|
171
|
-
|
|
172
|
-
|
|
178
|
+
// Encrypt before the write; normaliseDoc decrypts on the way back out,
|
|
179
|
+
// so the caller still receives plaintext.
|
|
180
|
+
const encryptedPatch = encryptPiiFields(patch, [...CONVERSATION_PII_FIELDS]);
|
|
181
|
+
tx.update(ref, encryptedPatch);
|
|
182
|
+
return normaliseDoc(conversationId, { ...data, ...encryptedPatch });
|
|
173
183
|
});
|
|
174
184
|
}
|
|
175
185
|
catch (error) {
|
|
@@ -23,14 +23,14 @@ export function OrderCard({ order, onClick, labels = {}, renderActions }) {
|
|
|
23
23
|
day: "numeric",
|
|
24
24
|
})
|
|
25
25
|
: "";
|
|
26
|
-
const statusColor = STATUS_COLORS[order.orderStatus] ?? "bg-[var(--appkit-color-surface)]
|
|
26
|
+
const statusColor = STATUS_COLORS[order.orderStatus] ?? "bg-[var(--appkit-color-surface)] bg-[var(--appkit-color-surface-elevated)] text-[var(--appkit-color-text-muted)]";
|
|
27
27
|
// SB8-F — count unrevealed prize-draw entries to surface a "reveals pending" badge.
|
|
28
28
|
const unrevealedPrizeDraws = order.items.filter((it) => it.listingType === "prize-draw" &&
|
|
29
29
|
it.prizeRevealStatus !== undefined &&
|
|
30
30
|
it.prizeRevealStatus !== "revealed");
|
|
31
31
|
const revealsRemaining = unrevealedPrizeDraws.length;
|
|
32
32
|
return (_jsxs(Div, { padding: "5", role: onClick ? "button" : undefined, tabIndex: onClick ? 0 : undefined, onKeyDown: onClick
|
|
33
|
-
? (e) => (e.key === "Enter" || e.key === "
|
|
33
|
+
? (e) => (e.key === "Enter" || e.key === "") && onClick(order)
|
|
34
34
|
: undefined, onClick: onClick ? () => onClick(order) : undefined, className: `border border-neutral-200 ${onClick ? "cursor-pointer transition hover:shadow-md" : ""}`, rounded: "xl", surface: "default", children: [_jsxs(Row, { wrap: true, align: "start", justify: "between", gap: "3", children: [_jsxs(Div, { children: [_jsxs(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "xs", children: ["Order #", order.id.slice(-8).toUpperCase()] }), date && (_jsx(Text, { className: "mt-0.5", color: "faint", size: "xs", children: date }))] }), _jsx(Span, { size: "xs", weight: "semibold", className: `capitalize ${statusColor}`, rounded: "full", padding: "pill-md", children: labels[order.orderStatus] ?? order.orderStatus.replace(/_/g, " ") })] }), revealsRemaining > 0 && (_jsx(Row, { gap: "sm", className: "mt-2", children: _jsxs(Span, { size: "xs", weight: "semibold", className: CLS_PRIZE_BADGE, children: [revealsRemaining, " ", revealsRemaining === 1 ? "reveal" : "reveals", " pending"] }) })), _jsxs(Row, { wrap: true, gap: "3", className: "mt-4", children: [order.items.slice(0, 3).map((item, i) => (_jsxs(Row, { gap: "sm", children: [item.image && (_jsx(Div, { className: "relative h-10 w-10 flex-shrink-0", rounded: "lg", overflow: "hidden", children: _jsx(MediaImage, { src: item.image, alt: item.title, size: "thumbnail" }) })), _jsxs(Div, { children: [_jsx(Text, { truncate: true, size: "sm", weight: "medium", color: "primary", children: item.title }), _jsxs(Text, { size: "xs", color: "faint", children: ["\u00D7", item.quantity] })] })] }, i))), order.items.length > 3 && (_jsxs(Span, { size: "xs", className: "self-center", color: "faint", children: ["+", order.items.length - 3, " more"] }))] }), _jsxs(Row, { justify: "between", className: "mt-4 border-t border-neutral-100", padding: "t-sm", children: [_jsxs(Span, { size: "sm", className: "text-[var(--appkit-color-text-muted)]", children: [order.currency ?? "", " Total"] }), _jsx(Span, { weight: "semibold", className: "text-[var(--appkit-color-text)]", children: formatCurrency(order.total, order.currency) })] }), renderActions && (_jsx(Row, { justify: "end", gap: "sm", className: "mt-3", onClick: (e) => e.stopPropagation(), children: renderActions(order) }))] }));
|
|
35
35
|
}
|
|
36
36
|
export function OrdersList({ orders, isLoading, onOrderClick, totalPages = 1, currentPage = 1, onPageChange, emptyLabel = "No orders found", renderActions, }) {
|
|
@@ -19,7 +19,17 @@ export declare class PayoutRepository extends BaseRepository<PayoutDocument> {
|
|
|
19
19
|
constructor();
|
|
20
20
|
/** Decrypt PII fields on a payout document after Firestore read */
|
|
21
21
|
private decryptPayout;
|
|
22
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* Encrypt PII, then attach blind indices derived from the plaintext.
|
|
24
|
+
*
|
|
25
|
+
* Previously this reassigned `encrypted = addPiiIndices(data, …)` — which
|
|
26
|
+
* re-reads the ORIGINAL plaintext and returns `{...data, ...indices}` — and
|
|
27
|
+
* then spread that over the ciphertext, so plaintext won and `sellerEmail`
|
|
28
|
+
* and `upiId` were written to Firestore in the clear beside a valid index.
|
|
29
|
+
* `piiIndicesFor` returns the index fields alone, so the ciphertext cannot be
|
|
30
|
+
* overwritten. The identical bug was fixed in UserRepository and never
|
|
31
|
+
* propagated here.
|
|
32
|
+
*/
|
|
23
33
|
private encryptPayoutData;
|
|
24
34
|
/** Override mapDoc to auto-decrypt PII on every Firestore read */
|
|
25
35
|
protected mapDoc<D = PayoutDocument>(snap: import("../../../providers/db-firebase").DocumentSnapshot): D;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import { BaseRepository, prepareForFirestore, parseSieveDateValue, } from "../../../providers/db-firebase";
|
|
8
8
|
import { PAYOUT_COLLECTION, PAYOUT_FIELDS, PAYOUT_TRACKED_FIELDS, createPayoutId } from "../schemas";
|
|
9
9
|
import { withHistory } from "../../../_internal/shared/history/index";
|
|
10
|
-
import { encryptPiiFields, decryptPiiFields,
|
|
10
|
+
import { encryptPiiFields, decryptPiiFields, piiIndicesFor, PAYOUT_PII_FIELDS, PAYOUT_PII_INDEX_MAP, encryptPayoutBankAccount, decryptPayoutBankAccount, } from "../../../security";
|
|
11
11
|
export class PayoutRepository extends BaseRepository {
|
|
12
12
|
constructor() {
|
|
13
13
|
super(PAYOUT_COLLECTION);
|
|
@@ -20,13 +20,21 @@ export class PayoutRepository extends BaseRepository {
|
|
|
20
20
|
}
|
|
21
21
|
return decrypted;
|
|
22
22
|
}
|
|
23
|
-
/**
|
|
23
|
+
/**
|
|
24
|
+
* Encrypt PII, then attach blind indices derived from the plaintext.
|
|
25
|
+
*
|
|
26
|
+
* Previously this reassigned `encrypted = addPiiIndices(data, …)` — which
|
|
27
|
+
* re-reads the ORIGINAL plaintext and returns `{...data, ...indices}` — and
|
|
28
|
+
* then spread that over the ciphertext, so plaintext won and `sellerEmail`
|
|
29
|
+
* and `upiId` were written to Firestore in the clear beside a valid index.
|
|
30
|
+
* `piiIndicesFor` returns the index fields alone, so the ciphertext cannot be
|
|
31
|
+
* overwritten. The identical bug was fixed in UserRepository and never
|
|
32
|
+
* propagated here.
|
|
33
|
+
*/
|
|
24
34
|
encryptPayoutData(data) {
|
|
25
|
-
|
|
26
|
-
encrypted = addPiiIndices(data, PAYOUT_PII_INDEX_MAP);
|
|
27
|
-
encrypted = {
|
|
35
|
+
const encrypted = {
|
|
28
36
|
...encryptPiiFields(data, [...PAYOUT_PII_FIELDS]),
|
|
29
|
-
...
|
|
37
|
+
...piiIndicesFor(data, PAYOUT_PII_INDEX_MAP),
|
|
30
38
|
};
|
|
31
39
|
const access = encrypted;
|
|
32
40
|
if (access.bankAccount) {
|
|
@@ -17,6 +17,7 @@ const CLS_LIMITED_BADGE = "rounded-full bg-info-solid px-[var(--appkit-space-2)]
|
|
|
17
17
|
import { BaseListingCard, Button, RichText, Row, Span, Stack, Text, TextLink } from "../../../ui";
|
|
18
18
|
import { normalizeRichTextHtml } from "../../../utils/string.formatter";
|
|
19
19
|
import { PreorderBadge } from "./PreorderCard";
|
|
20
|
+
import { WishlistHeartButton } from "../../wishlist/components/WishlistHeartButton";
|
|
20
21
|
const DEFAULT_LABELS = {
|
|
21
22
|
preOrderBadge: "Pre-order",
|
|
22
23
|
featuredBadge: "Featured",
|
|
@@ -62,7 +63,7 @@ export function MarketplacePreorderCard({ product, className = "", variant = "gr
|
|
|
62
63
|
onSelect(product.id, !isSelected);
|
|
63
64
|
}, className: selectable || isSelected ? "opacity-100" : "opacity-0 pointer-events-none group-hover:opacity-100 group-hover:pointer-events-auto transition-opacity" }))] }), _jsx(BaseListingCard.Info, { variant: variant, children: variant === "list" ? (
|
|
64
65
|
/* ── Compact list layout ── */
|
|
65
|
-
_jsxs(_Fragment, { children: [_jsxs(Row, { className: "min-w-0", align: "start", justify: "between", gap: "sm", children: [_jsx(TextLink, { href: detailHref, className: "min-w-0 flex-1", children: _jsx(Text, { size: "sm", weight: "medium", color: "primary", truncate: 2, children: product.title }) }), wishlistActions && (_jsx(
|
|
66
|
+
_jsxs(_Fragment, { children: [_jsxs(Row, { className: "min-w-0", align: "start", justify: "between", gap: "sm", children: [_jsx(TextLink, { href: detailHref, className: "min-w-0 flex-1", children: _jsx(Text, { size: "sm", weight: "medium", color: "primary", truncate: 2, children: product.title }) }), wishlistActions && (_jsx(WishlistHeartButton, { size: "inline", inWishlist: inWishlist, addLabel: mergedLabels.addToWishlist, removeLabel: mergedLabels.removeFromWishlist, onToggle: handleWishlist }))] }), _jsxs(Row, { align: "center", gap: "sm", wrap: true, children: [_jsx(Text, { className: "text-primary", size: "sm", weight: "bold", children: formatCurrency(product.price, getDefaultCurrency()) }), shipDate && _jsx(PreorderBadge, { shipDate: shipDate })] }), _jsx(Button, { type: "button", variant: "primary", size: "sm", textSize: "xs", className: "self-start mt-0.5", onClick: handleNavigate, children: mergedLabels.reserveNow })] })) : (
|
|
66
67
|
/* ── Full grid layout ── */
|
|
67
|
-
_jsxs(_Fragment, { children: [_jsx(TextLink, { href: detailHref, children: _jsx(Text, { size: "sm", weight: "medium", color: "primary", truncate: 2, children: product.title }) }), product.description ? (_jsx(RichText, { html: normalizeRichTextHtml(product.description), proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "line-clamp-2 text-[length:var(--appkit-text-xs)] text-[var(--appkit-color-text-muted)]" })) : null, _jsxs(Row, { justify: "between", className: "mt-1", gap: "sm", children: [_jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: formatCurrency(product.price, getDefaultCurrency()) }), shipDate && _jsx(PreorderBadge, { shipDate: shipDate })] }), _jsxs(Row, { justify: "between", className: "mt-auto pt-[var(--appkit-space-2)]", gap: "sm", children: [_jsx(Button, { type: "button", variant: "primary", size: "sm", textSize: "xs", className: "flex-1", onClick: handleNavigate, children: mergedLabels.reserveNow }), onAddToCart ? (_jsx(Button, { type: "button", variant: "outline", size: "sm", textSize: "xs", className: "flex-1", onClick: (e) => { e.stopPropagation(); onAddToCart(product); }, children: mergedLabels.addToCart })) : null, wishlistActions ? (_jsx(
|
|
68
|
+
_jsxs(_Fragment, { children: [_jsx(TextLink, { href: detailHref, children: _jsx(Text, { size: "sm", weight: "medium", color: "primary", truncate: 2, children: product.title }) }), product.description ? (_jsx(RichText, { html: normalizeRichTextHtml(product.description), proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "line-clamp-2 text-[length:var(--appkit-text-xs)] text-[var(--appkit-color-text-muted)]" })) : null, _jsxs(Row, { justify: "between", className: "mt-1", gap: "sm", children: [_jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: formatCurrency(product.price, getDefaultCurrency()) }), shipDate && _jsx(PreorderBadge, { shipDate: shipDate })] }), _jsxs(Row, { justify: "between", className: "mt-auto pt-[var(--appkit-space-2)]", gap: "sm", children: [_jsx(Button, { type: "button", variant: "primary", size: "sm", textSize: "xs", className: "flex-1", onClick: handleNavigate, children: mergedLabels.reserveNow }), onAddToCart ? (_jsx(Button, { type: "button", variant: "outline", size: "sm", textSize: "xs", className: "flex-1", onClick: (e) => { e.stopPropagation(); onAddToCart(product); }, children: mergedLabels.addToCart })) : null, wishlistActions ? (_jsx(WishlistHeartButton, { size: "inline", inWishlist: inWishlist, addLabel: mergedLabels.addToWishlist, removeLabel: mergedLabels.removeFromWishlist, onToggle: handleWishlist })) : null] })] })) })] }));
|
|
68
69
|
}
|
|
@@ -5,7 +5,7 @@ const STATUS_MAP = {
|
|
|
5
5
|
pending: { label: "Pending", color: "bg-warning-surface text-warning" },
|
|
6
6
|
confirmed: { label: "Confirmed", color: "bg-info-surface text-info" },
|
|
7
7
|
ready: { label: "Ready", color: "bg-success-surface text-success" },
|
|
8
|
-
fulfilled: { label: "Fulfilled", color: "bg-[var(--appkit-color-surface)]
|
|
8
|
+
fulfilled: { label: "Fulfilled", color: "bg-[var(--appkit-color-surface)] bg-[var(--appkit-color-surface-elevated)] text-[var(--appkit-color-text-muted)]" },
|
|
9
9
|
cancelled: { label: "Cancelled", color: "bg-error-surface text-error" },
|
|
10
10
|
};
|
|
11
11
|
export function PreOrderBadge({ status, className = "" }) {
|
|
@@ -61,7 +61,7 @@ function PreOrderInfoSection({ title, productionStatus, maxPerUser, deliveryDate
|
|
|
61
61
|
emiAvailable: "EMI Available",
|
|
62
62
|
wishlistCount: (n) => `${n} wishlisted`,
|
|
63
63
|
categoryProductCount: (n, cat) => `${n} in ${cat}`,
|
|
64
|
-
} }), (categoryName || category || brand) && (_jsxs(Row, { gap: "sm", wrap: true, children: [category && (_jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(category)), className: "inline-flex items-center rounded-full border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] px-[var(--appkit-space-2-5)] py-[var(--appkit-space-1)] text-[length:var(--appkit-text-xs)] font-medium text-[var(--appkit-color-text-muted)] transition-colors hover:border-primary-300 hover:bg-primary-
|
|
64
|
+
} }), (categoryName || category || brand) && (_jsxs(Row, { gap: "sm", wrap: true, children: [category && (_jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(category)), className: "inline-flex items-center rounded-full border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] px-[var(--appkit-space-2-5)] py-[var(--appkit-space-1)] text-[length:var(--appkit-text-xs)] font-medium text-[var(--appkit-color-text-muted)] transition-colors hover:border-primary-300 hover:bg-primary-surface hover:text-primary-700 dark:hover:border-primary-700/60 dark:hover:text-primary-400", children: categoryName || category })), !category && categoryName && (_jsx(Span, { layout: "inline-flex", size: "xs", weight: "medium", border: "subtle", rounded: "full", padding: "pill-sm-tall", surface: "muted", color: "muted", children: categoryName })), brand && brandSlug && (_jsx(Link, { href: String(ROUTES.PUBLIC.BRAND_DETAIL(brandSlug)), className: "inline-flex items-center rounded-full border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] px-[var(--appkit-space-2-5)] py-[var(--appkit-space-1)] text-[length:var(--appkit-text-xs)] font-medium text-[var(--appkit-color-text-muted)] transition-colors hover:border-primary-300 hover:bg-primary-surface hover:text-primary-700 dark:hover:border-primary-700/60 dark:hover:text-primary-400", children: brand })), brand && !brandSlug && (_jsx(Span, { layout: "inline-flex", size: "xs", weight: "medium", border: "subtle", rounded: "full", padding: "pill-sm-tall", surface: "muted", color: "muted", children: brand }))] })), productFeatures && features.length > 0 && (_jsx(FeatureBadgeList, { productFeatureIds: features, features: productFeatures })), !productFeatures && features.length > 0 && (_jsxs(Div, { border: "subtle", surface: "muted", padding: "inline", rounded: "xl", children: [_jsx(Text, { className: "mb-2 tracking-wide", color: "muted", size: "xs", weight: "semibold", transform: "uppercase", children: "About this product" }), _jsx(Ul, { spacing: "comfortable", size: "sm", color: "primary", children: features.map((f, i) => (_jsxs(Li, { layout: "flex-start", gap: "2", children: [_jsx(Span, { className: "mt-0.5 flex-shrink-0 text-primary-500", children: "\u2022" }), f] }, i))) })] })), descriptionHtml && (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "text-[length:var(--appkit-text-sm)] leading-relaxed text-[var(--appkit-color-text-muted)] line-clamp-4" })), safeSeller && (_jsx(Div, { className: `${__P.p3}`, border: "subtle", rounded: "xl", surface: "muted", children: _jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Div, { children: [_jsx(Text, { className: "text-[10px] tracking-wide mb-0.5", color: "faint", transform: "uppercase", children: "Sold by" }), _jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: safeSeller })] }), storeHref && (_jsx(Link, { href: storeHref, className: "shrink-0 rounded-lg bg-primary/10 dark:bg-primary/20 px-[var(--appkit-space-3)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-xs)] font-semibold text-primary-700 dark:text-primary-300 hover:bg-primary/20 dark:hover:bg-primary/30 transition-colors", children: "Visit Store \u2192" }))] }) }))] }));
|
|
65
65
|
}
|
|
66
66
|
function PreOrderBuyBarPanel({ reserveTarget, reservedCount, progressPct, productId, price, currency, depositAmount, depositPercent, isCancellable, tags, onReserveNow, }) {
|
|
67
67
|
return (_jsxs(Stack, { id: "pre-order-buy-bar", className: `${__P.p5}`, border: "subtle", gap: "md", rounded: "xl", surface: "muted", children: [reserveTarget > 0 && (_jsxs(Stack, { gap: "sm", children: [_jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Text, { size: "xs", color: "muted", children: [reservedCount, " of ", reserveTarget, " reserved"] }), _jsxs(Span, { size: "xs", weight: "semibold", className: "text-primary-600 dark:text-primary-400", children: [progressPct, "%"] })] }), _jsx(Div, { className: `h-2 w-full ${__O.hidden}`, rounded: "full", surface: "subtle", children: _jsx(ProgressBarFill, { pct: progressPct, className: "h-full bg-primary transition-all rounded-full" }) })] })), onReserveNow ? (_jsx(PreOrderActionsClient, { productId: productId, price: price, currency: currency, depositAmount: depositAmount, depositPercent: depositPercent, isCancellable: isCancellable, tags: tags, onReserveNow: onReserveNow })) : (_jsxs(_Fragment, { children: [price !== null && (_jsxs(Div, { children: [_jsx(Text, { size: "2xl", weight: "bold", color: "primary", children: formatCurrency(price, currency) }), depositAmount !== null && (_jsxs(Text, { className: "mt-0.5", color: "muted", size: "xs", children: ["Reserve with ", formatCurrency(depositAmount, currency), depositPercent !== null ? ` (${depositPercent}% deposit)` : ""] }))] })), _jsxs(Stack, { gap: "sm", children: [_jsx(Button, { variant: "primary", size: "md", className: "w-full", children: "Reserve Now" }), isCancellable && (_jsx(Text, { size: "xs", align: "start", color: "muted", children: "\u2713 Free cancellation before production" }))] }), tags.length > 0 && (_jsx(Div, { border: "default", className: "border-t", padding: "t-md", children: _jsx(Row, { wrap: true, gap: "xs", children: tags.map((tag) => (_jsx(Span, { size: "xs", rounded: "full", padding: "pill-sm-tall", surface: "subtle", color: "muted", children: tag }, tag))) }) })), _jsx(Div, { border: "default", className: "border-t", padding: "t-md", children: _jsx(Row, { wrap: true, gap: "sm", justify: "center", className: "text-center", children: [
|
|
@@ -7,7 +7,7 @@ const __O = {
|
|
|
7
7
|
};
|
|
8
8
|
const STATUS_STYLES = {
|
|
9
9
|
published: "bg-success-surface text-success dark:bg-success-surface dark:text-success",
|
|
10
|
-
draft: "bg-[var(--appkit-color-surface)]
|
|
10
|
+
draft: "bg-[var(--appkit-color-surface)] bg-[var(--appkit-color-border)] text-[var(--appkit-color-text-muted)]",
|
|
11
11
|
out_of_stock: "bg-warning-surface text-warning dark:bg-warning-surface dark:text-warning",
|
|
12
12
|
discontinued: "bg-error-surface text-error dark:bg-error-surface dark:text-error",
|
|
13
13
|
sold: "bg-primary/10 dark:bg-primary/20 text-primary",
|
|
@@ -54,7 +54,7 @@ export function getProductTableColumns({ labels, onEdit, onDelete, currencyCode,
|
|
|
54
54
|
return (_jsx(Span, { className: [
|
|
55
55
|
"rounded px-[var(--appkit-space-2)] py-[var(--appkit-space-1)] text-[length:var(--appkit-text-xs)] font-medium",
|
|
56
56
|
STATUS_STYLES[status] ??
|
|
57
|
-
"bg-[var(--appkit-color-surface)]
|
|
57
|
+
"bg-[var(--appkit-color-surface)] bg-[var(--appkit-color-border)] text-[var(--appkit-color-text-muted)]",
|
|
58
58
|
].join(" "), children: status.replace("_", " ") }));
|
|
59
59
|
},
|
|
60
60
|
},
|
|
@@ -76,7 +76,7 @@ export function getProductTableColumns({ labels, onEdit, onDelete, currencyCode,
|
|
|
76
76
|
"rounded px-[var(--appkit-space-2)] py-[var(--appkit-space-1)] text-[length:var(--appkit-text-xs)] font-medium",
|
|
77
77
|
featured
|
|
78
78
|
? CLS_LOW_STOCK
|
|
79
|
-
: "bg-[var(--appkit-color-surface)]
|
|
79
|
+
: "bg-[var(--appkit-color-surface)] bg-[var(--appkit-color-border)] text-[var(--appkit-color-text-muted)]",
|
|
80
80
|
].join(" "), children: featured ? labels.yes : labels.no }));
|
|
81
81
|
},
|
|
82
82
|
},
|
|
@@ -9,7 +9,7 @@ const __O = {
|
|
|
9
9
|
hidden: "overflow-hidden",
|
|
10
10
|
};
|
|
11
11
|
const REL_NOOPENER = "noopener noreferrer";
|
|
12
|
-
const CLS_REMOVE_BTN = "shrink-0 text-zinc-400 hover:
|
|
12
|
+
const CLS_REMOVE_BTN = "shrink-0 text-zinc-400 hover:text-error hover:bg-surface-hover";
|
|
13
13
|
import Link from "next/link";
|
|
14
14
|
import { X } from "lucide-react";
|
|
15
15
|
import { Button, Div, Heading, IconButton, Row, Span, Stack, Text } from "../../../ui";
|
|
@@ -44,7 +44,7 @@ function statusClass(s) {
|
|
|
44
44
|
case "open":
|
|
45
45
|
return "bg-success-surface text-success";
|
|
46
46
|
case "closed":
|
|
47
|
-
return "
|
|
47
|
+
return " bg-[var(--appkit-color-border)] text-[var(--appkit-color-text-muted)]";
|
|
48
48
|
case "pending":
|
|
49
49
|
default:
|
|
50
50
|
return "bg-warning-surface text-warning";
|
|
@@ -18,7 +18,7 @@ import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
|
|
|
18
18
|
*/
|
|
19
19
|
export function PrizeDrawEntryActions({ productId, productSlug, title, pricePerEntry, currency, remainingEntries, revealStatus, prizeGithubFileUrl, }) {
|
|
20
20
|
const router = useRouter();
|
|
21
|
-
const { requireAuth, modalOpen, modalMessage, closeModal } = useAuthGate();
|
|
21
|
+
const { requireAuth, isAuthResolving, modalOpen, modalMessage, closeModal } = useAuthGate();
|
|
22
22
|
const [consentOpen, setConsentOpen] = useState(false);
|
|
23
23
|
const closed = revealStatus === "closed" || remainingEntries === 0;
|
|
24
24
|
const handleEnter = useCallback(() => {
|
|
@@ -31,5 +31,5 @@ export function PrizeDrawEntryActions({ productId, productSlug, title, pricePerE
|
|
|
31
31
|
const slug = productSlug ?? productId;
|
|
32
32
|
router.push(`${String(ROUTES.USER.CHECKOUT)}?directItem=${encodeURIComponent(slug)}&type=prize-draw`);
|
|
33
33
|
}, [productSlug, productId, router]);
|
|
34
|
-
return (_jsxs(Stack, { gap: "md", children: [_jsxs(Text, { size: "2xl", weight: "bold", color: "primary", children: [formatCurrency(pricePerEntry, currency), _jsx(Text, { as: "span", className: "ml-1 text-[var(--appkit-color-text-muted)]", size: "xs", weight: "normal", children: "per entry" })] }), _jsx(Button, { action: closed ? undefined : ACTIONS.PRIZE_DRAW["buy-now"], variant: "primary", size: "md", className: "w-full", disabled: closed, onClick: handleEnter, children: closed ? "Draw closed" : undefined }), prizeGithubFileUrl ? (_jsx(Anchor, { href: prizeGithubFileUrl, tone: "none", underline: "hover", size: "xs", weight: "medium", className: "text-left text-primary-600 underline-offset-4 dark:text-primary-400", children: "View RNG source code on GitHub \u2192" })) : null, _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "xs", align: "start", children: "Winners chosen by Node.js crypto.randomInt \u2014 fully auditable. Entries are locked once paid; refunds only if the prize pool is exhausted." }), _jsx(NonRefundableConsentModal, { open: consentOpen, listingType: "prize-draw", itemTitle: title, priceLabel: formatCurrency(pricePerEntry, currency), onCancel: () => setConsentOpen(false), onConfirm: handleConfirm }), _jsx(LoginRequiredModal, { isOpen: modalOpen, onClose: closeModal, message: modalMessage })] }));
|
|
34
|
+
return (_jsxs(Stack, { gap: "md", children: [_jsxs(Text, { size: "2xl", weight: "bold", color: "primary", children: [formatCurrency(pricePerEntry, currency), _jsx(Text, { as: "span", className: "ml-1 text-[var(--appkit-color-text-muted)]", size: "xs", weight: "normal", children: "per entry" })] }), _jsx(Button, { action: closed ? undefined : ACTIONS.PRIZE_DRAW["buy-now"], variant: "primary", size: "md", className: "w-full", disabled: closed || isAuthResolving, onClick: handleEnter, children: closed ? "Draw closed" : isAuthResolving ? "Checking…" : undefined }), prizeGithubFileUrl ? (_jsx(Anchor, { href: prizeGithubFileUrl, tone: "none", underline: "hover", size: "xs", weight: "medium", className: "text-left text-primary-600 underline-offset-4 dark:text-primary-400", children: "View RNG source code on GitHub \u2192" })) : null, _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "xs", align: "start", children: "Winners chosen by Node.js crypto.randomInt \u2014 fully auditable. Entries are locked once paid; refunds only if the prize pool is exhausted." }), _jsx(NonRefundableConsentModal, { open: consentOpen, listingType: "prize-draw", itemTitle: title, priceLabel: formatCurrency(pricePerEntry, currency), onCancel: () => setConsentOpen(false), onConfirm: handleConfirm }), _jsx(LoginRequiredModal, { isOpen: modalOpen, onClose: closeModal, message: modalMessage })] }));
|
|
35
35
|
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
|
|
2
|
+
// Import from the DEFINING module, never `@mohasinac/appkit/client` — that entry
|
|
3
|
+
// is `"use client"`, so in this async Server Component `sortBy` would resolve to
|
|
4
|
+
// a client-reference proxy and calling it throws during the server render
|
|
5
|
+
// (Root Cause #18). That is exactly what took `section-prize-draws` down.
|
|
6
|
+
import { sortBy } from "../../../constants/sort";
|
|
3
7
|
import { Container, Div, Heading, Section, Stack, Text, } from "../../../ui";
|
|
4
8
|
import { ROUTES } from "../../../next";
|
|
5
9
|
import { listPublicProducts } from "../../../_internal/server/features/products/list-public";
|