@mohasinac/appkit 4.37.1 → 4.39.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.
Files changed (172) hide show
  1. package/dist/_internal/client/features/digital-code/CodeRevealPanel.d.ts +20 -1
  2. package/dist/_internal/client/features/digital-code/CodeRevealPanel.js +21 -1
  3. package/dist/_internal/client/features/digital-code/DigitalContentPoolManager.d.ts +40 -0
  4. package/dist/_internal/client/features/digital-code/DigitalContentPoolManager.js +67 -0
  5. package/dist/_internal/server/features/bundles/og.d.ts +0 -1
  6. package/dist/_internal/server/features/bundles/og.js +2 -18
  7. package/dist/_internal/server/features/catalogue/og.d.ts +0 -1
  8. package/dist/_internal/server/features/catalogue/og.js +1 -9
  9. package/dist/_internal/server/features/checkout/actions.js +18 -0
  10. package/dist/_internal/server/features/classified/og.d.ts +0 -1
  11. package/dist/_internal/server/features/classified/og.js +1 -9
  12. package/dist/_internal/server/features/digital-code/download.d.ts +60 -0
  13. package/dist/_internal/server/features/digital-code/download.js +128 -0
  14. package/dist/_internal/server/features/digital-code/og.d.ts +0 -1
  15. package/dist/_internal/server/features/digital-code/og.js +1 -9
  16. package/dist/_internal/server/features/digital-code/pool.d.ts +101 -0
  17. package/dist/_internal/server/features/digital-code/pool.js +208 -0
  18. package/dist/_internal/server/features/digital-code/upload.d.ts +51 -0
  19. package/dist/_internal/server/features/digital-code/upload.js +144 -0
  20. package/dist/_internal/server/features/grouped/og.d.ts +7 -4
  21. package/dist/_internal/server/features/grouped/og.js +7 -6
  22. package/dist/_internal/server/features/item-requests/og.d.ts +0 -1
  23. package/dist/_internal/server/features/item-requests/og.js +2 -18
  24. package/dist/_internal/server/features/live/og.d.ts +0 -1
  25. package/dist/_internal/server/features/live/og.js +1 -9
  26. package/dist/_internal/server/features/prize-draws/og.js +5 -10
  27. package/dist/_internal/server/features/products/og.js +5 -2
  28. package/dist/_internal/server/features/seo/sitemap.js +10 -2
  29. package/dist/_internal/server/features/shared/listing-adapters.d.ts +1 -2
  30. package/dist/_internal/server/features/shared/listing-adapters.js +5 -3
  31. package/dist/_internal/server/jobs/core/cleanupRtdbEvents.js +40 -0
  32. package/dist/_internal/server/jobs/core/countersReconcile.js +153 -40
  33. package/dist/_internal/server/jobs/core/onCategoryWrite.js +33 -0
  34. package/dist/_internal/server/jobs/core/onProductWrite.js +93 -25
  35. package/dist/_internal/server/notifications/send-recorder.d.ts +79 -0
  36. package/dist/_internal/server/notifications/send-recorder.js +126 -0
  37. package/dist/_internal/shared/listing-types/_registry.d.ts +31 -4
  38. package/dist/_internal/shared/listing-types/art/config.d.ts +1 -0
  39. package/dist/_internal/shared/listing-types/art/config.js +2 -0
  40. package/dist/_internal/shared/listing-types/auction/config.d.ts +1 -0
  41. package/dist/_internal/shared/listing-types/auction/config.js +3 -0
  42. package/dist/_internal/shared/listing-types/classified/config.d.ts +1 -0
  43. package/dist/_internal/shared/listing-types/classified/config.js +3 -0
  44. package/dist/_internal/shared/listing-types/digital-code/config.d.ts +1 -0
  45. package/dist/_internal/shared/listing-types/digital-code/config.js +3 -0
  46. package/dist/_internal/shared/listing-types/live/config.d.ts +1 -0
  47. package/dist/_internal/shared/listing-types/live/config.js +3 -0
  48. package/dist/_internal/shared/listing-types/pre-order/config.d.ts +1 -0
  49. package/dist/_internal/shared/listing-types/pre-order/config.js +2 -0
  50. package/dist/_internal/shared/listing-types/prize-draw/config.d.ts +1 -0
  51. package/dist/_internal/shared/listing-types/prize-draw/config.js +3 -0
  52. package/dist/_internal/shared/listing-types/standard/config.d.ts +1 -0
  53. package/dist/_internal/shared/listing-types/standard/config.js +2 -0
  54. package/dist/_internal/shared/listing-types/stickers/config.d.ts +1 -0
  55. package/dist/_internal/shared/listing-types/stickers/config.js +2 -0
  56. package/dist/client/observability/reportClientError.js +11 -1
  57. package/dist/client.d.ts +6 -0
  58. package/dist/client.js +10 -0
  59. package/dist/core/hooks/useSyncManager.js +11 -1
  60. package/dist/features/account/hooks/useNotifications.js +25 -5
  61. package/dist/features/auctions/components/AuctionBottomActions.js +10 -2
  62. package/dist/features/auctions/components/AuctionDetailPageView.js +12 -3
  63. package/dist/features/auctions/components/LiveBidPrice.js +2 -2
  64. package/dist/features/auctions/components/LiveMinIncrement.d.ts +1 -1
  65. package/dist/features/auctions/components/LiveMinIncrement.js +8 -0
  66. package/dist/features/auctions/components/MarketplaceAuctionCard.js +3 -3
  67. package/dist/features/auctions/components/PlaceBidFormClient.js +16 -1
  68. package/dist/features/auctions/hooks/useAuctions.js +12 -1
  69. package/dist/features/auctions/hooks/useRealtimeBids.d.ts +3 -0
  70. package/dist/features/auctions/hooks/useRealtimeBids.js +138 -36
  71. package/dist/features/blog/components/BlogIndexPageView.js +10 -1
  72. package/dist/features/blog/repository/blog.repository.d.ts +14 -0
  73. package/dist/features/blog/repository/blog.repository.js +23 -1
  74. package/dist/features/catalogue/components/PublicCatalogueItemDetailView.js +2 -2
  75. package/dist/features/catalogue/components/PublicCatalogueView.js +2 -2
  76. package/dist/features/categories/components/BundleCollage.js +10 -5
  77. package/dist/features/categories/components/BundleDetailView.js +7 -3
  78. package/dist/features/categories/components/CategoryDetailPageView.js +1 -1
  79. package/dist/features/categories/components/CategoryProductsListing.js +4 -1
  80. package/dist/features/categories/components/CategoryTree.js +1 -1
  81. package/dist/features/categories/repository/categories.repository.d.ts +34 -2
  82. package/dist/features/categories/repository/categories.repository.js +31 -9
  83. package/dist/features/classified/components/ClassifiedDetailPageView.js +2 -2
  84. package/dist/features/contact/email.js +36 -1
  85. package/dist/features/digital-codes/components/DigitalCodeDetailPageView.js +2 -2
  86. package/dist/features/homepage/components/FeaturedBundlesSection.js +4 -4
  87. package/dist/features/live/components/LiveItemDetailPageView.js +2 -2
  88. package/dist/features/orders/repository/orders.repository.d.ts +21 -1
  89. package/dist/features/orders/repository/orders.repository.js +26 -9
  90. package/dist/features/pre-orders/components/MarketplacePreorderCard.js +3 -3
  91. package/dist/features/pre-orders/components/PreOrderActionsClient.js +2 -2
  92. package/dist/features/pre-orders/components/PreOrderDetailPageView.js +2 -2
  93. package/dist/features/pre-orders/components/PreOrdersIndexListing.js +4 -1
  94. package/dist/features/products/actions/product-actions.d.ts +9 -0
  95. package/dist/features/products/actions/product-actions.js +11 -0
  96. package/dist/features/products/columns/productTableColumns.js +2 -2
  97. package/dist/features/products/components/AuctionsIndexListing.js +4 -1
  98. package/dist/features/products/components/BidHistory.js +2 -2
  99. package/dist/features/products/components/CompareOverlay.js +2 -2
  100. package/dist/features/products/components/GroupMemberPicker.js +5 -5
  101. package/dist/features/products/components/ListingBottomActions.js +12 -3
  102. package/dist/features/products/components/MakeOfferButton.js +7 -1
  103. package/dist/features/products/components/MarketplaceBundleCard.js +4 -4
  104. package/dist/features/products/components/MarketplacePrizeDrawCard.js +2 -2
  105. package/dist/features/products/components/PrizeDrawCollage.js +16 -5
  106. package/dist/features/products/components/PrizeDrawEntryActions.js +2 -2
  107. package/dist/features/products/components/PrizeDrawsIndexListing.js +4 -1
  108. package/dist/features/products/components/PrizeRevealModal.js +2 -2
  109. package/dist/features/products/components/ProductDetailActions.js +10 -3
  110. package/dist/features/products/components/ProductFilters.js +2 -2
  111. package/dist/features/products/components/ProductGrid.js +5 -2
  112. package/dist/features/products/components/ProductsIndexListing.js +12 -1
  113. package/dist/features/products/components/ProductsIndexPageView.js +26 -0
  114. package/dist/features/products/components/ShowGroupSection.js +2 -2
  115. package/dist/features/products/components/SublistingCarouselSection.js +2 -2
  116. package/dist/features/products/constants/listing-tabs.d.ts +36 -12
  117. package/dist/features/products/constants/listing-tabs.js +38 -14
  118. package/dist/features/products/constants/sieve.d.ts +14 -0
  119. package/dist/features/products/constants/sieve.js +39 -0
  120. package/dist/features/products/repository/products.repository.d.ts +35 -0
  121. package/dist/features/products/repository/products.repository.js +116 -3
  122. package/dist/features/products/schemas/firestore.d.ts +37 -0
  123. package/dist/features/seller/actions/seller-actions.js +4 -13
  124. package/dist/features/seller/components/SellerProductShell.d.ts +7 -1
  125. package/dist/features/seller/components/SellerProductShell.js +11 -2
  126. package/dist/features/stores/api/[storeSlug]/reviews/route.d.ts +6 -1
  127. package/dist/features/stores/api/[storeSlug]/reviews/route.js +89 -30
  128. package/dist/features/stores/components/StoresIndexPageView.js +17 -1
  129. package/dist/features/tester/seed-data/authored/admin__users-trust.js +1 -1
  130. package/dist/features/tester/seed-data/authored/buying__bidding.js +5 -4
  131. package/dist/features/tester/seed-data/authored/buying__browsing-search.js +85 -85
  132. package/dist/features/tester/seed-data/authored/buying__image-tile-layout.js +2 -2
  133. package/dist/features/tester/seed-data/authored/buying__offers.js +1 -1
  134. package/dist/features/tester/seed-data/authored/buying__product-detail.js +53 -15
  135. package/dist/features/tester/seed-data/authored/buying__user-dashboard-extras.js +6 -6
  136. package/dist/features/tester/seed-data/authored/content-discovery__search.js +6 -4
  137. package/dist/features/tester/seed-data/authored/design-ux__carousel-arrow-bounds.js +4 -4
  138. package/dist/features/tester/seed-data/authored/design-ux__homepage-carousels.js +2 -2
  139. package/dist/features/tester/seed-data/authored/design-ux__status-badge-legibility.js +4 -4
  140. package/dist/features/tester/seed-data/authored/design-ux__sticky-cta-bar.js +8 -8
  141. package/dist/features/tester/seed-data/authored/public-pages__core-listing-pages.js +8 -8
  142. package/dist/features/tester/seed-data/authored/seo__canonical-and-host.js +16 -0
  143. package/dist/features/tester/seed-data/authored/seo__og-images.js +17 -0
  144. package/dist/features/tester/seed-data/stores-tester-seed-data.js +41 -2
  145. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +36 -6
  146. package/dist/features/tester/utils/phases.js +78 -12
  147. package/dist/features/wishlist/components/WishlistPage.js +2 -2
  148. package/dist/index.d.ts +2 -1
  149. package/dist/index.js +4 -1
  150. package/dist/providers/db-firebase/rtdb-paths.d.ts +7 -0
  151. package/dist/providers/db-firebase/rtdb-paths.js +7 -0
  152. package/dist/react/contexts/SessionContext.js +40 -1
  153. package/dist/react/hooks/useCanSeePrices.d.ts +37 -0
  154. package/dist/react/hooks/useCanSeePrices.js +40 -0
  155. package/dist/seed/addresses-seed-data.js +36 -2
  156. package/dist/seed/users-seed-data.js +10 -2
  157. package/dist/seo/json-ld.d.ts +21 -0
  158. package/dist/seo/json-ld.js +45 -0
  159. package/dist/server.d.ts +3 -0
  160. package/dist/server.js +12 -0
  161. package/dist/styles.css +41 -0
  162. package/dist/ui/components/FilePickerButton.d.ts +32 -0
  163. package/dist/ui/components/FilePickerButton.js +23 -0
  164. package/dist/ui/components/GatedPrice.d.ts +66 -0
  165. package/dist/ui/components/GatedPrice.js +80 -0
  166. package/dist/ui/components/GatedPrice.style.css +40 -0
  167. package/dist/ui/components/PriceDisplay.d.ts +12 -0
  168. package/dist/ui/components/PriceDisplay.js +17 -17
  169. package/dist/ui/components/index.style.css +1 -0
  170. package/dist/ui/index.d.ts +4 -0
  171. package/dist/ui/index.js +8 -0
  172. package/package.json +1 -1
package/dist/styles.css CHANGED
@@ -3295,6 +3295,47 @@
3295
3295
  color: var(--appkit-color-emerald-300);
3296
3296
  }
3297
3297
 
3298
+ /**
3299
+ * Sign-in gate for money amounts on public listing surfaces.
3300
+ *
3301
+ * The pending state must occupy the SAME vertical space as a rendered price, or
3302
+ * every card in a grid reflows once the session resolves. It is deliberately a
3303
+ * silent placeholder rather than a shimmer: the wait is short, and an animated
3304
+ * skeleton on 24 cards reads as a page that failed to load.
3305
+ */
3306
+ .appkit-gated-price__pending {
3307
+ display: inline-block;
3308
+ min-width: 5rem;
3309
+ height: 1.25rem;
3310
+ border-radius: 0.25rem;
3311
+ background: var(--appkit-color-surface-muted);
3312
+ }
3313
+
3314
+ .appkit-gated-price__prompt {
3315
+ display: inline-flex;
3316
+ align-items: center;
3317
+ gap: 0.25rem;
3318
+ font-size: 0.8125rem;
3319
+ font-weight: 600;
3320
+ line-height: 1.25rem;
3321
+ color: var(--appkit-color-primary);
3322
+ text-decoration: none;
3323
+ cursor: pointer;
3324
+ }
3325
+
3326
+ .appkit-gated-price__prompt:hover {
3327
+ text-decoration: underline;
3328
+ }
3329
+
3330
+ /* A lock glyph via ::before rather than an icon component: this renders inside
3331
+ card grids, where 24 extra SVG trees per page is real weight for decoration
3332
+ that carries no meaning the label doesn't already state. */
3333
+ .appkit-gated-price__prompt::before {
3334
+ content: "\1F512";
3335
+ font-size: 0.75rem;
3336
+ line-height: 1;
3337
+ }
3338
+
3298
3339
  .appkit-item-row {
3299
3340
  display: flex;
3300
3341
  align-items: flex-start;
@@ -0,0 +1,32 @@
1
+ import React from "react";
2
+ import type { ButtonProps } from "./Button";
3
+ /**
4
+ * A button that opens the native file picker.
5
+ *
6
+ * 🛑 THE RAW `<input type="file">` BELONGS HERE, and only here. Rule #9 bans raw
7
+ * form elements in product code and points every case at a primitive; there was
8
+ * no primitive for a file picker, so the one feature that needed one
9
+ * (`DigitalContentPoolManager`) had to carry a raw input plus a suppression
10
+ * marker — and `audit-no-suppression-comments` is strict-zero and says, in its
11
+ * own header, "do NOT add a new marker or exception". The only close available
12
+ * was to extend the primitive layer, which is exactly what Root Cause #22 says
13
+ * to do before reaching for a marker.
14
+ *
15
+ * Deliberately NOT built on `MediaUploadField`. That component drives the public
16
+ * `/api/media/*` pipeline, which ends by minting a `/media/{shortId}` URL served
17
+ * with no authentication — correct for product photography, catastrophic for a
18
+ * paid download. This primitive hands the caller a `File` and has no opinion
19
+ * about where the bytes go.
20
+ *
21
+ * The input is visually hidden rather than styled, because a styled file input
22
+ * cannot be made to match `<Button>` across browsers — Safari and Firefox each
23
+ * render their own immovable "Choose file" text inside it.
24
+ */
25
+ export interface FilePickerButtonProps extends Omit<ButtonProps, "onClick" | "type" | "children"> {
26
+ /** Called with the chosen file. Never called with null. */
27
+ onFile: (file: File) => void;
28
+ /** Native `accept` string, e.g. `"image/*"`. Omit to accept anything. */
29
+ accept?: string;
30
+ children: React.ReactNode;
31
+ }
32
+ export declare function FilePickerButton({ onFile, accept, children, ...buttonProps }: FilePickerButtonProps): React.JSX.Element;
@@ -0,0 +1,23 @@
1
+ "use client";
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useId, useRef } from "react";
4
+ import { Button } from "./Button";
5
+ // No forwardRef: appkit's <Button> does not accept a ref, so there is nothing
6
+ // to forward one to. Adding the plumbing would be a prop that silently does
7
+ // nothing, which is worse than its absence.
8
+ export function FilePickerButton({ onFile, accept, children, ...buttonProps }) {
9
+ const inputRef = useRef(null);
10
+ const inputId = useId();
11
+ return (_jsxs(_Fragment, { children: [_jsx(Button, { ...buttonProps, type: "button", onClick: () => inputRef.current?.click(), children: children }), _jsx("input", { ref: inputRef, id: inputId, type: "file", accept: accept, hidden: true, onChange: (ev) => {
12
+ const file = ev.target.files?.[0];
13
+ /*
14
+ * Reset BEFORE handing the file on. Without it, picking the same file
15
+ * twice in a row fires no change event the second time — the value is
16
+ * unchanged — and the control silently does nothing, which reads as a
17
+ * broken button rather than as a no-op.
18
+ */
19
+ ev.target.value = "";
20
+ if (file)
21
+ onFile(file);
22
+ } })] }));
23
+ }
@@ -0,0 +1,66 @@
1
+ import React from "react";
2
+ /**
3
+ * 🛑 CONTRACT WITH THE STRUCTURED DATA — do not rename.
4
+ *
5
+ * The Product/Auction JSON-LD declares this exact selector in its paywalled
6
+ * content markup (`hasPart.cssSelector`, see `appkit/src/seo/json-ld.ts`), which
7
+ * is what tells Google the page legitimately carries an `offers.price` that an
8
+ * anonymous visitor cannot see. Rename this and the selector silently matches
9
+ * nothing, which reads to a crawler as an undeclared content mismatch.
10
+ *
11
+ * It is rendered in EVERY state — signed in, signed out and resolving — because
12
+ * the markup describes the region, not the refusal.
13
+ */
14
+ export declare const GATED_PRICE_CLASS = "appkit-gated-price";
15
+ export interface GatedPriceProps {
16
+ /** The amount to show once the viewer is allowed to see it. */
17
+ amount?: number;
18
+ currency?: string;
19
+ /** Struck-through "was" price; a discount badge is derived from the pair. */
20
+ originalAmount?: number;
21
+ variant?: "compact" | "detail";
22
+ className?: string;
23
+ /**
24
+ * Copy for the signed-out state. Default: "Sign in to see price". Override
25
+ * when the amount isn't a price — "Sign in to see the current bid".
26
+ */
27
+ label?: string;
28
+ /**
29
+ * Arbitrary money content to gate, for the sites that don't render a bare
30
+ * amount ("Buy Now — ₹1,200", "min increment ₹100", a composite row). When
31
+ * given, `amount` is ignored and this is what renders once allowed.
32
+ */
33
+ children?: React.ReactNode;
34
+ }
35
+ /**
36
+ * Renders its children only when the viewer may see prices; renders NOTHING
37
+ * otherwise — no prompt, no placeholder.
38
+ *
39
+ * For SECONDARY money details that sit beside an already-gated primary amount:
40
+ * a handling fee, a deposit note, a "you save ₹X" line, a min-bid increment.
41
+ * Those read as broken copy when gated in place ("min increment Sign in to see
42
+ * price"), and the prompt is already on screen one line up, so repeating it is
43
+ * noise rather than an affordance.
44
+ *
45
+ * 🛑 Do NOT use this for a listing's primary price. A card whose price row
46
+ * simply vanishes reads as a rendering bug, and its height stops matching its
47
+ * neighbours — use `<GatedPrice>` there, which keeps the row and explains it.
48
+ *
49
+ * Renders nothing while the session resolves, so a signed-in viewer sees these
50
+ * appear rather than flash the wrong state.
51
+ */
52
+ export declare function PricesOnly({ children }: {
53
+ children: React.ReactNode;
54
+ }): React.JSX.Element | null;
55
+ /**
56
+ * A money amount that only signed-in visitors may read.
57
+ *
58
+ * Three states, and the middle one is the whole reason this is a component
59
+ * rather than an inline `user ? price : prompt` at each call site — see
60
+ * `useCanSeePrices` for why "resolving" must not render the prompt.
61
+ *
62
+ * The amount is still present in the page payload and in the structured data;
63
+ * this gates the RENDER. Withholding it from the response entirely would mean
64
+ * fetching prices separately on every listing page — deliberately out of scope.
65
+ */
66
+ export declare function GatedPrice({ amount, currency, originalAmount, variant, className, label, children, }: GatedPriceProps): React.JSX.Element | null;
@@ -0,0 +1,80 @@
1
+ "use client";
2
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Span } from "./Typography";
4
+ import { Row } from "./Layout";
5
+ import { ROUTES } from "../../next/routing/route-map";
6
+ import { getDefaultCurrency } from "../../core/baseline-resolver";
7
+ import { formatCurrency, formatPercentage } from "../../utils/number.formatter";
8
+ import { useCanSeePrices } from "../../react/hooks/useCanSeePrices";
9
+ /**
10
+ * 🛑 CONTRACT WITH THE STRUCTURED DATA — do not rename.
11
+ *
12
+ * The Product/Auction JSON-LD declares this exact selector in its paywalled
13
+ * content markup (`hasPart.cssSelector`, see `appkit/src/seo/json-ld.ts`), which
14
+ * is what tells Google the page legitimately carries an `offers.price` that an
15
+ * anonymous visitor cannot see. Rename this and the selector silently matches
16
+ * nothing, which reads to a crawler as an undeclared content mismatch.
17
+ *
18
+ * It is rendered in EVERY state — signed in, signed out and resolving — because
19
+ * the markup describes the region, not the refusal.
20
+ */
21
+ export const GATED_PRICE_CLASS = "appkit-gated-price";
22
+ const DEFAULT_LABEL = "Sign in to see price";
23
+ /**
24
+ * Renders its children only when the viewer may see prices; renders NOTHING
25
+ * otherwise — no prompt, no placeholder.
26
+ *
27
+ * For SECONDARY money details that sit beside an already-gated primary amount:
28
+ * a handling fee, a deposit note, a "you save ₹X" line, a min-bid increment.
29
+ * Those read as broken copy when gated in place ("min increment Sign in to see
30
+ * price"), and the prompt is already on screen one line up, so repeating it is
31
+ * noise rather than an affordance.
32
+ *
33
+ * 🛑 Do NOT use this for a listing's primary price. A card whose price row
34
+ * simply vanishes reads as a rendering bug, and its height stops matching its
35
+ * neighbours — use `<GatedPrice>` there, which keeps the row and explains it.
36
+ *
37
+ * Renders nothing while the session resolves, so a signed-in viewer sees these
38
+ * appear rather than flash the wrong state.
39
+ */
40
+ export function PricesOnly({ children }) {
41
+ const { canSeePrices } = useCanSeePrices();
42
+ if (!canSeePrices)
43
+ return null;
44
+ return _jsx(_Fragment, { children: children });
45
+ }
46
+ /**
47
+ * A money amount that only signed-in visitors may read.
48
+ *
49
+ * Three states, and the middle one is the whole reason this is a component
50
+ * rather than an inline `user ? price : prompt` at each call site — see
51
+ * `useCanSeePrices` for why "resolving" must not render the prompt.
52
+ *
53
+ * The amount is still present in the page payload and in the structured data;
54
+ * this gates the RENDER. Withholding it from the response entirely would mean
55
+ * fetching prices separately on every listing page — deliberately out of scope.
56
+ */
57
+ export function GatedPrice({ amount, currency = getDefaultCurrency(), originalAmount, variant = "compact", className, label = DEFAULT_LABEL, children, }) {
58
+ const { canSeePrices, isAuthResolving } = useCanSeePrices();
59
+ const rootClass = `${GATED_PRICE_CLASS} ${className ?? ""}`.trim();
60
+ if (isAuthResolving) {
61
+ return (_jsx(Span, { className: `${rootClass} appkit-gated-price__pending`, "aria-hidden": "true" }));
62
+ }
63
+ if (!canSeePrices) {
64
+ return (_jsx("a", { href: String(ROUTES.AUTH.LOGIN), className: `${rootClass} appkit-gated-price__prompt`, children: label }));
65
+ }
66
+ if (children !== undefined) {
67
+ return _jsx(Span, { className: rootClass, children: children });
68
+ }
69
+ if (amount === undefined)
70
+ return null;
71
+ const hasDiscount = originalAmount !== undefined && originalAmount > amount;
72
+ const discountPct = hasDiscount ? (originalAmount - amount) / originalAmount : 0;
73
+ const priceClass = variant === "detail"
74
+ ? "appkit-price-display__price appkit-price-display__price--detail"
75
+ : "appkit-price-display__price appkit-price-display__price--compact";
76
+ const originalClass = variant === "detail"
77
+ ? "appkit-price-display__original appkit-price-display__original--detail"
78
+ : "appkit-price-display__original appkit-price-display__original--compact";
79
+ return (_jsxs(Row, { wrap: true, align: "baseline", gap: "xs", className: `appkit-price-display ${rootClass}`, children: [_jsx(Span, { className: priceClass, children: formatCurrency(amount, currency) }), hasDiscount && (_jsxs(_Fragment, { children: [_jsx(Span, { className: originalClass, children: formatCurrency(originalAmount, currency) }), _jsxs(Span, { className: "appkit-price-display__discount", children: ["-", formatPercentage(discountPct, 0)] })] }))] }));
80
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Sign-in gate for money amounts on public listing surfaces.
3
+ *
4
+ * The pending state must occupy the SAME vertical space as a rendered price, or
5
+ * every card in a grid reflows once the session resolves. It is deliberately a
6
+ * silent placeholder rather than a shimmer: the wait is short, and an animated
7
+ * skeleton on 24 cards reads as a page that failed to load.
8
+ */
9
+ .appkit-gated-price__pending {
10
+ display: inline-block;
11
+ min-width: 5rem;
12
+ height: 1.25rem;
13
+ border-radius: 0.25rem;
14
+ background: var(--appkit-color-surface-muted);
15
+ }
16
+
17
+ .appkit-gated-price__prompt {
18
+ display: inline-flex;
19
+ align-items: center;
20
+ gap: 0.25rem;
21
+ font-size: 0.8125rem;
22
+ font-weight: 600;
23
+ line-height: 1.25rem;
24
+ color: var(--appkit-color-primary);
25
+ text-decoration: none;
26
+ cursor: pointer;
27
+ }
28
+
29
+ .appkit-gated-price__prompt:hover {
30
+ text-decoration: underline;
31
+ }
32
+
33
+ /* A lock glyph via ::before rather than an icon component: this renders inside
34
+ card grids, where 24 extra SVG trees per page is real weight for decoration
35
+ that carries no meaning the label doesn't already state. */
36
+ .appkit-gated-price__prompt::before {
37
+ content: "\1F512";
38
+ font-size: 0.75rem;
39
+ line-height: 1;
40
+ }
@@ -5,4 +5,16 @@ export interface PriceDisplayProps {
5
5
  variant?: "compact" | "detail";
6
6
  className?: string;
7
7
  }
8
+ /**
9
+ * A product price on a public surface — gated behind sign-in.
10
+ *
11
+ * This is now a thin wrapper over `<GatedPrice>`, with no opt-out, because both
12
+ * of its consumers (`ProductGrid`'s card + list rows, and the generic
13
+ * `ProductDetailPageView`) are public listing surfaces. There is deliberately no
14
+ * `ungated` prop: nothing in admin, seller, cart, checkout or orders uses this
15
+ * component, so an escape hatch here would exist only to be misused. Those
16
+ * surfaces call `formatCurrency` directly and are signed-in by definition.
17
+ *
18
+ * Rendering, discount badge and markup are unchanged for a signed-in viewer.
19
+ */
8
20
  export declare function PriceDisplay({ amount, currency, originalAmount, variant, className, }: PriceDisplayProps): import("react").JSX.Element;
@@ -1,18 +1,18 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { Span } from "./Typography";
3
- import { Row } from "./Layout";
4
- import { getDefaultCurrency } from "../../core/baseline-resolver";
5
- import { formatCurrency, formatPercentage } from "../../utils/number.formatter";
6
- export function PriceDisplay({ amount, currency = getDefaultCurrency(), originalAmount, variant = "compact", className, }) {
7
- const hasDiscount = originalAmount !== undefined && originalAmount > amount;
8
- const discountPct = hasDiscount
9
- ? (originalAmount - amount) / originalAmount
10
- : 0;
11
- const priceClass = variant === "detail"
12
- ? "appkit-price-display__price appkit-price-display__price--detail"
13
- : "appkit-price-display__price appkit-price-display__price--compact";
14
- const originalClass = variant === "detail"
15
- ? "appkit-price-display__original appkit-price-display__original--detail"
16
- : "appkit-price-display__original appkit-price-display__original--compact";
17
- return (_jsxs(Row, { wrap: true, align: "baseline", gap: "xs", className: `appkit-price-display ${className ?? ""}`.trim(), children: [_jsx(Span, { className: priceClass, children: formatCurrency(amount, currency) }), hasDiscount && (_jsxs(_Fragment, { children: [_jsx(Span, { className: originalClass, children: formatCurrency(originalAmount, currency) }), _jsxs(Span, { className: "appkit-price-display__discount", children: ["-", formatPercentage(discountPct, 0)] })] }))] }));
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { GatedPrice } from "./GatedPrice";
4
+ /**
5
+ * A product price on a public surface — gated behind sign-in.
6
+ *
7
+ * This is now a thin wrapper over `<GatedPrice>`, with no opt-out, because both
8
+ * of its consumers (`ProductGrid`'s card + list rows, and the generic
9
+ * `ProductDetailPageView`) are public listing surfaces. There is deliberately no
10
+ * `ungated` prop: nothing in admin, seller, cart, checkout or orders uses this
11
+ * component, so an escape hatch here would exist only to be misused. Those
12
+ * surfaces call `formatCurrency` directly and are signed-in by definition.
13
+ *
14
+ * Rendering, discount badge and markup are unchanged for a signed-in viewer.
15
+ */
16
+ export function PriceDisplay({ amount, currency, originalAmount, variant = "compact", className, }) {
17
+ return (_jsx(GatedPrice, { amount: amount, currency: currency, originalAmount: originalAmount, variant: variant, className: className }));
18
18
  }
@@ -21,6 +21,7 @@
21
21
  @import "./Skeleton.style.css";
22
22
  @import "./RatingDisplay.style.css";
23
23
  @import "./PriceDisplay.style.css";
24
+ @import "./GatedPrice.style.css";
24
25
  @import "./ItemRow.style.css";
25
26
  @import "./Tooltip.style.css";
26
27
  @import "./Layout.style.css";
@@ -33,6 +33,8 @@ export type { SkeletonProps } from "./components/Skeleton";
33
33
  export { Skeleton } from "./components/Skeleton";
34
34
  export type { ButtonProps } from "./components/Button";
35
35
  export { Button } from "./components/Button";
36
+ export { FilePickerButton } from "./components/FilePickerButton";
37
+ export type { FilePickerButtonProps } from "./components/FilePickerButton";
36
38
  export type { IconButtonProps } from "./components/IconButton";
37
39
  export { IconButton } from "./components/IconButton";
38
40
  export type { IconProps, IconTone } from "./components/Icon";
@@ -115,6 +117,8 @@ export type { RatingDisplayProps } from "./components/RatingDisplay";
115
117
  export { RatingDisplay } from "./components/RatingDisplay";
116
118
  export type { PriceDisplayProps } from "./components/PriceDisplay";
117
119
  export { PriceDisplay } from "./components/PriceDisplay";
120
+ export type { GatedPriceProps } from "./components/GatedPrice";
121
+ export { GatedPrice, PricesOnly, GATED_PRICE_CLASS } from "./components/GatedPrice";
118
122
  export type { StatsGridProps, StatItem } from "./components/StatsGrid";
119
123
  export { StatsGrid } from "./components/StatsGrid";
120
124
  export type { SummaryCardProps, SummaryLine } from "./components/SummaryCard";
package/dist/ui/index.js CHANGED
@@ -19,6 +19,13 @@ export { SiteLogo } from "./components/SiteLogo";
19
19
  export { SiteMark } from "./components/SiteMark";
20
20
  export { Skeleton } from "./components/Skeleton";
21
21
  export { Button } from "./components/Button";
22
+ /*
23
+ * The one primitive allowed to own a raw <input type="file">. Rule #9 sends
24
+ * every raw form element to a primitive and there was none for a file picker,
25
+ * so the only feature that needed one had to carry a suppression marker that
26
+ * audit-no-suppression-comments (strict-zero) correctly refuses.
27
+ */
28
+ export { FilePickerButton } from "./components/FilePickerButton";
22
29
  export { IconButton } from "./components/IconButton";
23
30
  export { Icon } from "./components/Icon";
24
31
  export { ICONS, ICON_SIZE, isIconKey, resolveIcon, } from "./icons/icon-registry";
@@ -60,6 +67,7 @@ export { StepperNav } from "./components/StepperNav";
60
67
  export { ViewToggle } from "./components/ViewToggle";
61
68
  export { RatingDisplay } from "./components/RatingDisplay";
62
69
  export { PriceDisplay } from "./components/PriceDisplay";
70
+ export { GatedPrice, PricesOnly, GATED_PRICE_CLASS } from "./components/GatedPrice";
63
71
  export { StatsGrid } from "./components/StatsGrid";
64
72
  export { SummaryCard } from "./components/SummaryCard";
65
73
  export { CountdownDisplay } from "./components/CountdownDisplay";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mohasinac/appkit",
3
- "version": "4.37.1",
3
+ "version": "4.39.0",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public"