@mohasinac/appkit 4.37.0 → 4.38.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_internal/server/features/bundles/og.d.ts +0 -1
- package/dist/_internal/server/features/bundles/og.js +2 -18
- package/dist/_internal/server/features/catalogue/og.d.ts +0 -1
- package/dist/_internal/server/features/catalogue/og.js +1 -9
- package/dist/_internal/server/features/classified/og.d.ts +0 -1
- package/dist/_internal/server/features/classified/og.js +1 -9
- package/dist/_internal/server/features/digital-code/og.d.ts +0 -1
- package/dist/_internal/server/features/digital-code/og.js +1 -9
- package/dist/_internal/server/features/grouped/og.d.ts +7 -4
- package/dist/_internal/server/features/grouped/og.js +7 -6
- package/dist/_internal/server/features/item-requests/og.d.ts +0 -1
- package/dist/_internal/server/features/item-requests/og.js +2 -18
- package/dist/_internal/server/features/live/og.d.ts +0 -1
- package/dist/_internal/server/features/live/og.js +1 -9
- package/dist/_internal/server/features/prize-draws/og.js +5 -10
- package/dist/_internal/server/features/products/og.js +5 -2
- package/dist/_internal/server/features/seo/sitemap.js +10 -2
- package/dist/_internal/server/features/shared/listing-adapters.d.ts +1 -2
- package/dist/_internal/server/features/shared/listing-adapters.js +5 -3
- package/dist/_internal/shared/listing-types/_registry.d.ts +31 -4
- package/dist/_internal/shared/listing-types/art/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/art/config.js +2 -0
- package/dist/_internal/shared/listing-types/auction/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/auction/config.js +3 -0
- package/dist/_internal/shared/listing-types/classified/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/classified/config.js +3 -0
- package/dist/_internal/shared/listing-types/digital-code/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/digital-code/config.js +3 -0
- package/dist/_internal/shared/listing-types/live/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/live/config.js +3 -0
- package/dist/_internal/shared/listing-types/pre-order/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/pre-order/config.js +2 -0
- package/dist/_internal/shared/listing-types/prize-draw/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/prize-draw/config.js +3 -0
- package/dist/_internal/shared/listing-types/standard/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/standard/config.js +2 -0
- package/dist/_internal/shared/listing-types/stickers/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/stickers/config.js +2 -0
- package/dist/client/observability/reportClientError.js +11 -1
- package/dist/client.d.ts +4 -0
- package/dist/client.js +4 -0
- package/dist/core/hooks/useSyncManager.js +11 -1
- package/dist/features/account/hooks/useNotifications.js +25 -5
- package/dist/features/auctions/components/AuctionBottomActions.js +10 -2
- package/dist/features/auctions/components/AuctionDetailPageView.js +12 -3
- package/dist/features/auctions/components/LiveBidPrice.js +2 -2
- package/dist/features/auctions/components/LiveMinIncrement.d.ts +1 -1
- package/dist/features/auctions/components/LiveMinIncrement.js +8 -0
- package/dist/features/auctions/components/MarketplaceAuctionCard.js +3 -3
- package/dist/features/auctions/components/PlaceBidFormClient.js +16 -1
- package/dist/features/auctions/hooks/useAuctions.js +12 -1
- package/dist/features/auctions/hooks/useRealtimeBids.d.ts +3 -0
- package/dist/features/auctions/hooks/useRealtimeBids.js +138 -36
- package/dist/features/catalogue/components/PublicCatalogueItemDetailView.js +2 -2
- package/dist/features/catalogue/components/PublicCatalogueView.js +2 -2
- package/dist/features/categories/components/BundleCollage.js +10 -5
- package/dist/features/categories/components/BundleDetailView.js +7 -3
- package/dist/features/categories/components/CategoryProductsListing.js +4 -1
- package/dist/features/classified/components/ClassifiedDetailPageView.js +2 -2
- package/dist/features/digital-codes/components/DigitalCodeDetailPageView.js +2 -2
- package/dist/features/homepage/components/FeaturedBundlesSection.js +4 -4
- package/dist/features/live/components/LiveItemDetailPageView.js +2 -2
- package/dist/features/pre-orders/components/MarketplacePreorderCard.js +3 -3
- package/dist/features/pre-orders/components/PreOrderActionsClient.js +2 -2
- package/dist/features/pre-orders/components/PreOrderDetailPageView.js +2 -2
- package/dist/features/pre-orders/components/PreOrdersIndexListing.js +4 -1
- package/dist/features/products/actions/product-actions.d.ts +9 -0
- package/dist/features/products/actions/product-actions.js +11 -0
- package/dist/features/products/columns/productTableColumns.js +2 -2
- package/dist/features/products/components/AuctionsIndexListing.js +4 -1
- package/dist/features/products/components/BidHistory.js +2 -2
- package/dist/features/products/components/CompareOverlay.js +2 -2
- package/dist/features/products/components/GroupMemberPicker.js +5 -5
- package/dist/features/products/components/ListingBottomActions.js +12 -3
- package/dist/features/products/components/MakeOfferButton.js +7 -1
- package/dist/features/products/components/MarketplaceBundleCard.js +4 -4
- package/dist/features/products/components/MarketplacePrizeDrawCard.js +2 -2
- package/dist/features/products/components/PrizeDrawCollage.js +16 -5
- package/dist/features/products/components/PrizeDrawEntryActions.js +2 -2
- package/dist/features/products/components/PrizeDrawsIndexListing.js +4 -1
- package/dist/features/products/components/PrizeRevealModal.js +2 -2
- package/dist/features/products/components/ProductDetailActions.js +10 -3
- package/dist/features/products/components/ProductFilters.js +2 -2
- package/dist/features/products/components/ProductGrid.js +5 -2
- package/dist/features/products/components/ProductsIndexListing.js +12 -1
- package/dist/features/products/components/ProductsIndexPageView.js +26 -0
- package/dist/features/products/components/ShowGroupSection.js +2 -2
- package/dist/features/products/components/SublistingCarouselSection.js +2 -2
- package/dist/features/products/constants/listing-tabs.d.ts +36 -12
- package/dist/features/products/constants/listing-tabs.js +38 -14
- package/dist/features/products/constants/sieve.d.ts +14 -0
- package/dist/features/products/constants/sieve.js +39 -0
- package/dist/features/tester/seed-data/authored/buying__bidding.js +5 -4
- package/dist/features/tester/seed-data/authored/buying__browsing-search.js +85 -85
- package/dist/features/tester/seed-data/authored/buying__image-tile-layout.js +2 -2
- package/dist/features/tester/seed-data/authored/buying__product-detail.js +53 -15
- package/dist/features/tester/seed-data/authored/content-discovery__search.js +6 -4
- package/dist/features/tester/seed-data/authored/design-ux__carousel-arrow-bounds.js +4 -4
- package/dist/features/tester/seed-data/authored/design-ux__homepage-carousels.js +2 -2
- package/dist/features/tester/seed-data/authored/design-ux__status-badge-legibility.js +4 -4
- package/dist/features/tester/seed-data/authored/design-ux__sticky-cta-bar.js +8 -8
- package/dist/features/tester/seed-data/authored/public-pages__core-listing-pages.js +8 -8
- package/dist/features/tester/seed-data/authored/seo__canonical-and-host.js +16 -0
- package/dist/features/tester/seed-data/authored/seo__og-images.js +17 -0
- package/dist/features/tester/seed-data/stores-tester-seed-data.js +41 -2
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +36 -6
- package/dist/features/wishlist/components/WishlistPage.js +2 -2
- package/dist/index.d.ts +3 -1
- package/dist/index.js +6 -1
- package/dist/react/contexts/SessionContext.js +40 -1
- package/dist/react/hooks/useCanSeePrices.d.ts +37 -0
- package/dist/react/hooks/useCanSeePrices.js +40 -0
- package/dist/seed/addresses-seed-data.js +36 -2
- package/dist/seed/index.d.ts +1 -1
- package/dist/seed/index.js +6 -1
- package/dist/seed/users-seed-data.js +10 -2
- package/dist/seo/json-ld.d.ts +21 -0
- package/dist/seo/json-ld.js +45 -0
- package/dist/styles.css +41 -0
- package/dist/ui/components/GatedPrice.d.ts +66 -0
- package/dist/ui/components/GatedPrice.js +80 -0
- package/dist/ui/components/GatedPrice.style.css +40 -0
- package/dist/ui/components/PriceDisplay.d.ts +12 -0
- package/dist/ui/components/PriceDisplay.js +17 -17
- package/dist/ui/components/index.style.css +1 -0
- package/dist/ui/index.d.ts +2 -0
- package/dist/ui/index.js +1 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -598,6 +598,9 @@ export { createSeoConfig } from "./seo/index";
|
|
|
598
598
|
// faqJsonLd - Shared export for faq json ld.
|
|
599
599
|
export { faqJsonLd } from "./seo/index";
|
|
600
600
|
// [CLIENT-SSR]-Runs in both SSR and browser â€" React component or hook that does not depend on browser-only APIs.
|
|
601
|
+
// gatedPriceWebPageJsonLd - Shared export for gated price web page json ld.
|
|
602
|
+
export { gatedPriceWebPageJsonLd } from "./seo/index";
|
|
603
|
+
// [CLIENT-SSR]-Runs in both SSR and browser â€" React component or hook that does not depend on browser-only APIs.
|
|
601
604
|
// generateAuctionMetadata - Helper for generate auction metadata.
|
|
602
605
|
export { generateAuctionMetadata } from "./seo/index";
|
|
603
606
|
// [CLIENT-SSR]-Runs in both SSR and browser â€" React component or hook that does not depend on browser-only APIs.
|
|
@@ -1189,6 +1192,8 @@ export { faqSeedData } from "./seed/index";
|
|
|
1189
1192
|
// [SERVER-ONLY]-Server-only — uses Node.js, Next.js server internals, or third-party server SDKs (auth, email, payment, shipping).
|
|
1190
1193
|
// testerChecklistSeedData - Model for tester checklist seed data.
|
|
1191
1194
|
export { testerChecklistSeedData } from "./seed/index";
|
|
1195
|
+
// testerResponsesSeedData - Seeded tester submissions behind the bug-hunter demo fixtures.
|
|
1196
|
+
export { testerResponsesSeedData } from "./seed/index";
|
|
1192
1197
|
// [SERVER-ONLY]-Server-only — uses Node.js, Next.js server internals, or third-party server SDKs (auth, email, payment, shipping).
|
|
1193
1198
|
// storesTesterSeedData - Model for the shared tester sandbox store seed data.
|
|
1194
1199
|
export { storesTesterSeedData } from "./seed/index";
|
|
@@ -5362,7 +5367,7 @@ export { Button } from "./ui/index";
|
|
|
5362
5367
|
export { Heading, Spinner, IconButton, Progress, IndeterminateProgress, Pagination, PageLoader } from "./ui/index";
|
|
5363
5368
|
export { ModalFooter, UnsavedChangesModal, ResponsiveView, Textarea, Checkbox } from "./ui/index";
|
|
5364
5369
|
export { ActionRow } from "./ui/index";
|
|
5365
|
-
export { ToastProvider, StarRating, StepperNav, ViewToggle, PriceDisplay, SummaryCard } from "./ui/index";
|
|
5370
|
+
export { ToastProvider, StarRating, StepperNav, ViewToggle, PriceDisplay, GatedPrice, PricesOnly, SummaryCard } from "./ui/index";
|
|
5366
5371
|
export { CountdownDisplay, ItemRow, HorizontalScroller } from "./ui/index";
|
|
5367
5372
|
export { CardBody, MenuTrigger, MenuContent, MenuSeparator, RoleBadge, SkipToMain } from "./ui/index";
|
|
5368
5373
|
export { Tabs, TabsList, TabsTrigger, TabsContent, PasswordStrengthIndicator, TablePagination } from "./ui/index";
|
|
@@ -19,7 +19,22 @@ const DEFAULT_ENDPOINTS = {
|
|
|
19
19
|
// ---------------------------------------------------------------------------
|
|
20
20
|
const SESSION_COOKIE = "__session";
|
|
21
21
|
const SESSION_ID_COOKIE = "__session_id";
|
|
22
|
-
|
|
22
|
+
/**
|
|
23
|
+
* How often a VISIBLE tab pings session activity and re-reads RBAC flags.
|
|
24
|
+
*
|
|
25
|
+
* Each tick costs two function invocations (`sessionActivity`, then the
|
|
26
|
+
* piggybacked profile re-fetch below), per open tab, wall-clock rather than
|
|
27
|
+
* activity-gated. At the original 5 minutes that was 24 invocations/hour for
|
|
28
|
+
* every tab any signed-in user had left open, and it contributed to the Vercel
|
|
29
|
+
* overage that paused the account (2026-09-14).
|
|
30
|
+
*
|
|
31
|
+
* 15 minutes is safe because it is a BACKSTOP, not the primary freshness
|
|
32
|
+
* mechanism: `RoleGuard` calls `refreshUser()` on every mount of a role-gated
|
|
33
|
+
* layout, so navigating into /admin, /store or /user re-reads the flags
|
|
34
|
+
* immediately, and the visibility handler below re-reads them the moment a
|
|
35
|
+
* backgrounded tab is looked at again.
|
|
36
|
+
*/
|
|
37
|
+
const ACTIVITY_INTERVAL_MS = 15 * 60 * 1000; // 15 minutes
|
|
23
38
|
// ---------------------------------------------------------------------------
|
|
24
39
|
// Context
|
|
25
40
|
// ---------------------------------------------------------------------------
|
|
@@ -166,6 +181,14 @@ export function SessionProvider({ children, initialUser, endpoints: endpointOver
|
|
|
166
181
|
const currentSessionId = getSessionIdFromCookie();
|
|
167
182
|
if (!currentSessionId || !user)
|
|
168
183
|
return;
|
|
184
|
+
// A hidden tab is not "activity", so reporting it as such is both wasteful
|
|
185
|
+
// and wrong — an abandoned background tab used to keep both requests below
|
|
186
|
+
// firing indefinitely. The visibility handler re-runs this immediately when
|
|
187
|
+
// the tab is looked at again, so nothing is merely deferred into staleness.
|
|
188
|
+
if (typeof document !== "undefined" &&
|
|
189
|
+
document.visibilityState === "hidden") {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
169
192
|
try {
|
|
170
193
|
await apiClient.post(ep.sessionActivity, {
|
|
171
194
|
sessionId: currentSessionId,
|
|
@@ -203,6 +226,22 @@ export function SessionProvider({ children, initialUser, endpoints: endpointOver
|
|
|
203
226
|
useEffect(() => {
|
|
204
227
|
updateSessionActivityRef.current = updateSessionActivity;
|
|
205
228
|
}, [updateSessionActivity]);
|
|
229
|
+
// Re-read session activity and RBAC flags the moment a backgrounded tab is
|
|
230
|
+
// looked at again. This is what lets the interval above be 15 minutes and the
|
|
231
|
+
// hidden-tab skip be safe: the user-visible freshness guarantee is now "fresh
|
|
232
|
+
// whenever you are actually looking at it", which is strictly better than the
|
|
233
|
+
// old fixed 5-minute tick that fired whether anyone was there or not.
|
|
234
|
+
useEffect(() => {
|
|
235
|
+
if (typeof document === "undefined")
|
|
236
|
+
return;
|
|
237
|
+
const onVisible = () => {
|
|
238
|
+
if (document.visibilityState === "visible") {
|
|
239
|
+
void updateSessionActivityRef.current?.();
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
document.addEventListener("visibilitychange", onVisible);
|
|
243
|
+
return () => document.removeEventListener("visibilitychange", onVisible);
|
|
244
|
+
}, []);
|
|
206
245
|
// -----------------------------------------------------------------------
|
|
207
246
|
// Refresh helpers
|
|
208
247
|
// -----------------------------------------------------------------------
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export interface CanSeePricesState {
|
|
2
|
+
/** The viewer is signed in and may be shown money amounts. */
|
|
3
|
+
canSeePrices: boolean;
|
|
4
|
+
/**
|
|
5
|
+
* The session has not resolved yet. Render a neutral placeholder while this
|
|
6
|
+
* is true — NEVER the sign-in prompt. See the note below.
|
|
7
|
+
*/
|
|
8
|
+
isAuthResolving: boolean;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* May the current viewer be shown money amounts on a public listing surface?
|
|
12
|
+
*
|
|
13
|
+
* Prices, current bids, deposits and per-entry amounts are hidden from
|
|
14
|
+
* signed-out visitors so the catalogue cannot be read at scale without an
|
|
15
|
+
* account. Signed-in users see everything exactly as before.
|
|
16
|
+
*
|
|
17
|
+
* ## Why there are THREE states, not two
|
|
18
|
+
*
|
|
19
|
+
* `SessionProvider` is mounted `initialUser={null}` because the root locale
|
|
20
|
+
* layout must stay static (Root Cause #82), so `loading` starts `true` on every
|
|
21
|
+
* hard load — for signed-in users too, until Firebase persistence resolves and
|
|
22
|
+
* `/api/user/profile` returns. A naive `!user` test therefore renders
|
|
23
|
+
* "Sign in to see price" at an already-signed-in user for several hundred
|
|
24
|
+
* milliseconds on every page load.
|
|
25
|
+
*
|
|
26
|
+
* So: `isAuthResolving` means "don't know yet" and must render a neutral
|
|
27
|
+
* placeholder. Only `canSeePrices === false && isAuthResolving === false` is a
|
|
28
|
+
* confirmed guest.
|
|
29
|
+
*
|
|
30
|
+
* ## Why `useOptionalSession`
|
|
31
|
+
*
|
|
32
|
+
* This is library code that ADJUSTS for the viewer rather than requiring one.
|
|
33
|
+
* `useSession()` throws without a provider, which would turn an optional
|
|
34
|
+
* refinement into a breaking contract for every consumer (Root Cause #20). With
|
|
35
|
+
* no provider we fail CLOSED — no session means no prices.
|
|
36
|
+
*/
|
|
37
|
+
export declare function useCanSeePrices(): CanSeePricesState;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useOptionalSession } from "../contexts/SessionContext";
|
|
3
|
+
/**
|
|
4
|
+
* May the current viewer be shown money amounts on a public listing surface?
|
|
5
|
+
*
|
|
6
|
+
* Prices, current bids, deposits and per-entry amounts are hidden from
|
|
7
|
+
* signed-out visitors so the catalogue cannot be read at scale without an
|
|
8
|
+
* account. Signed-in users see everything exactly as before.
|
|
9
|
+
*
|
|
10
|
+
* ## Why there are THREE states, not two
|
|
11
|
+
*
|
|
12
|
+
* `SessionProvider` is mounted `initialUser={null}` because the root locale
|
|
13
|
+
* layout must stay static (Root Cause #82), so `loading` starts `true` on every
|
|
14
|
+
* hard load — for signed-in users too, until Firebase persistence resolves and
|
|
15
|
+
* `/api/user/profile` returns. A naive `!user` test therefore renders
|
|
16
|
+
* "Sign in to see price" at an already-signed-in user for several hundred
|
|
17
|
+
* milliseconds on every page load.
|
|
18
|
+
*
|
|
19
|
+
* So: `isAuthResolving` means "don't know yet" and must render a neutral
|
|
20
|
+
* placeholder. Only `canSeePrices === false && isAuthResolving === false` is a
|
|
21
|
+
* confirmed guest.
|
|
22
|
+
*
|
|
23
|
+
* ## Why `useOptionalSession`
|
|
24
|
+
*
|
|
25
|
+
* This is library code that ADJUSTS for the viewer rather than requiring one.
|
|
26
|
+
* `useSession()` throws without a provider, which would turn an optional
|
|
27
|
+
* refinement into a breaking contract for every consumer (Root Cause #20). With
|
|
28
|
+
* no provider we fail CLOSED — no session means no prices.
|
|
29
|
+
*/
|
|
30
|
+
export function useCanSeePrices() {
|
|
31
|
+
const session = useOptionalSession();
|
|
32
|
+
// No provider: fail closed, and don't claim to be mid-resolution — there is
|
|
33
|
+
// nothing in flight that will ever change the answer.
|
|
34
|
+
if (!session)
|
|
35
|
+
return { canSeePrices: false, isAuthResolving: false };
|
|
36
|
+
return {
|
|
37
|
+
canSeePrices: Boolean(session.user?.uid),
|
|
38
|
+
isAuthResolving: session.loading,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
* addresses live entirely in store-addresses-seed-data.ts (merged in at the API route
|
|
4
4
|
* level) — this file only carries ownerType:"user" entries now, to avoid seeding two
|
|
5
5
|
* overlapping sources of store address data.
|
|
6
|
-
* WHAT: Exports
|
|
6
|
+
* WHAT: Exports 4 user addresses. Top-level collection (SB-UNI-A) with ownerType discriminator.
|
|
7
7
|
* PII encrypted via HMAC blind indices (emailIndex, phoneIndex). Composite indexes
|
|
8
8
|
* (ownerType, ownerId, createdAt desc) + (ownerType, ownerId, isDefault).
|
|
9
9
|
*
|
|
10
10
|
* EXPORTS:
|
|
11
|
-
* addressesSeedData — Array of
|
|
11
|
+
* addressesSeedData — Array of 4 user address documents
|
|
12
12
|
*
|
|
13
13
|
* @tag domain:addresses,shipping
|
|
14
14
|
* @tag layer:seed
|
|
@@ -74,5 +74,39 @@ const _rawAddressesSeedData = [
|
|
|
74
74
|
createdAt: daysAgo(365),
|
|
75
75
|
updatedAt: daysAgo(5),
|
|
76
76
|
},
|
|
77
|
+
/*
|
|
78
|
+
* Automated checklist runner — delivery address.
|
|
79
|
+
*
|
|
80
|
+
* 🛑 THIS ROW IS WHY CHECKOUT CAN BE TESTED AT ALL.
|
|
81
|
+
*
|
|
82
|
+
* `user-claude-tester` had no address, so /checkout stopped dead at "Step 1 of 3:
|
|
83
|
+
* Shipping Address — No saved addresses yet." Every order, payment, coupon-at-placement,
|
|
84
|
+
* offer-checkout and forfeited-bid case died there: 117 blocked cases, 27% of everything
|
|
85
|
+
* the run could not test, and the single largest blocker by a wide margin (measured,
|
|
86
|
+
* run 1789300124915).
|
|
87
|
+
*
|
|
88
|
+
* It has to be SEEDED rather than created by the runner mid-run. `addresses` is PRESERVE
|
|
89
|
+
* tier in tester/scripts/lib/collections.mjs — teardown deliberately never touches it,
|
|
90
|
+
* because real people's saved addresses live there — so a row the runner created at
|
|
91
|
+
* runtime would survive every future run with nothing owning its cleanup. Seeded, it is
|
|
92
|
+
* idempotent: `appkit-seed load` upserts this exact id, and re-running changes nothing.
|
|
93
|
+
*/
|
|
94
|
+
{
|
|
95
|
+
id: "addr-claude-tester-home",
|
|
96
|
+
ownerType: "user",
|
|
97
|
+
ownerId: "user-claude-tester",
|
|
98
|
+
label: "Home",
|
|
99
|
+
fullName: "Claude (automated)",
|
|
100
|
+
phone: "+91-99999-00019",
|
|
101
|
+
addressLine1: "1 Automation Street",
|
|
102
|
+
addressLine2: "Koramangala",
|
|
103
|
+
city: "Bengaluru",
|
|
104
|
+
state: "Karnataka",
|
|
105
|
+
postalCode: "560034",
|
|
106
|
+
country: "India",
|
|
107
|
+
isDefault: true,
|
|
108
|
+
createdAt: daysAgo(1),
|
|
109
|
+
updatedAt: daysAgo(1),
|
|
110
|
+
},
|
|
77
111
|
];
|
|
78
112
|
export const addressesSeedData = _rawAddressesSeedData;
|
package/dist/seed/index.d.ts
CHANGED
|
@@ -78,7 +78,7 @@ export { historySeedData } from "./history-seed-data";
|
|
|
78
78
|
export { groupedListingsSeedData } from "./grouped-listings-seed-data";
|
|
79
79
|
export { scammersSeedData } from "./scammers-seed-data";
|
|
80
80
|
export { supportTicketsSeedData } from "./support-tickets-seed-data";
|
|
81
|
-
export { testerChecklistSeedData, storesTesterSeedData, categoriesTesterSeedData, productsTesterSeedData, blogTesterSeedData, eventsTesterSeedData, couponsTesterSeedData, bidsTesterSeedData, cartsTesterSeedData, ordersTesterSeedData, } from "../features/tester/seed-data";
|
|
81
|
+
export { testerChecklistSeedData, testerResponsesSeedData, storesTesterSeedData, categoriesTesterSeedData, productsTesterSeedData, blogTesterSeedData, eventsTesterSeedData, couponsTesterSeedData, bidsTesterSeedData, cartsTesterSeedData, ordersTesterSeedData, } from "../features/tester/seed-data";
|
|
82
82
|
export { productFeaturesSeedData } from "./product-features-seed-data";
|
|
83
83
|
export { offersSeedData } from "./offers-seed-data";
|
|
84
84
|
export { payoutMethodsSeedData, shippingConfigsSeedData, analyticsCardsSeedData, analyticsAlertsSeedData, storeCategoriesSeedData, listingTemplatesSeedData, moderationQueueSeedData, reportsSeedData, itemRequestsSeedData, storeWhatsAppConfigSeedData, storeGoogleConfigSeedData, } from "./store-extensions-seed-data";
|
package/dist/seed/index.js
CHANGED
|
@@ -69,7 +69,12 @@ export { scammersSeedData } from "./scammers-seed-data";
|
|
|
69
69
|
export { supportTicketsSeedData } from "./support-tickets-seed-data";
|
|
70
70
|
// Tester sandbox seed fixtures live in ../features/tester/seed-data (isolated from this
|
|
71
71
|
// folder on purpose) — re-exported here so every other seed consumer keeps one import surface.
|
|
72
|
-
export { testerChecklistSeedData,
|
|
72
|
+
export { testerChecklistSeedData,
|
|
73
|
+
// 🛑 Adding a tester seed export means FOUR hops, not one: the tester barrel,
|
|
74
|
+
// THIS re-export, appkit/src/index.ts, and seed-cli's own import. Missing the
|
|
75
|
+
// middle two is silent — seed-cli logs "no seed data, skipping" and the run
|
|
76
|
+
// continues, which is exactly what happened on the first attempt here.
|
|
77
|
+
testerResponsesSeedData, storesTesterSeedData, categoriesTesterSeedData, productsTesterSeedData, blogTesterSeedData, eventsTesterSeedData, couponsTesterSeedData, bidsTesterSeedData, cartsTesterSeedData, ordersTesterSeedData, } from "../features/tester/seed-data";
|
|
73
78
|
export { productFeaturesSeedData } from "./product-features-seed-data";
|
|
74
79
|
export { offersSeedData } from "./offers-seed-data";
|
|
75
80
|
// S-STORE Extensions (Tier S-STORE) — 11 new collections
|
|
@@ -546,8 +546,16 @@ export const usersSeedData = [
|
|
|
546
546
|
isBot: true,
|
|
547
547
|
emailVerified: true,
|
|
548
548
|
disabled: false,
|
|
549
|
-
|
|
550
|
-
|
|
549
|
+
// 🛑 This MUST name a store whose own `ownerId` is this uid. It used to say
|
|
550
|
+
// "store-tester-qa-seller", which is owned by `user-tester-qa` — so the app, which
|
|
551
|
+
// resolves sellers by `ownerId`, answered every /api/store/* call with 403
|
|
552
|
+
// "No store found for this account" while the client, reading `role` and `storeId`
|
|
553
|
+
// off this document, rendered the entire Store Panel over it. 36 checklist cases were
|
|
554
|
+
// blocked by that one-way pointer (measured, run 1789300124915).
|
|
555
|
+
// See stores-tester-seed-data.ts for why the shared sandbox could not simply be
|
|
556
|
+
// repointed at this account instead.
|
|
557
|
+
storeId: "store-claude-tester",
|
|
558
|
+
storeSlug: "store-claude-tester",
|
|
551
559
|
storeStatus: "approved",
|
|
552
560
|
publicProfile: {
|
|
553
561
|
isPublic: false,
|
package/dist/seo/json-ld.d.ts
CHANGED
|
@@ -84,6 +84,27 @@ export interface BlogPostJsonLdInput {
|
|
|
84
84
|
metaTitle?: string;
|
|
85
85
|
metaDescription?: string;
|
|
86
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* Declares that this page's price is behind a sign-in gate.
|
|
89
|
+
*
|
|
90
|
+
* Prices are hidden from signed-out visitors, and Googlebot crawls signed out —
|
|
91
|
+
* so the crawler sees "Sign in to see price" while the Product JSON-LD beside it
|
|
92
|
+
* declares a real `offers.price`. Left undeclared, that is a structured-data
|
|
93
|
+
* mismatch: Google requires marked-up content to be visible to the user, and an
|
|
94
|
+
* unexplained gap can cost the rich result.
|
|
95
|
+
*
|
|
96
|
+
* `isAccessibleForFree: false` + `hasPart`/`cssSelector` is the supported way to
|
|
97
|
+
* say "the markup is richer than what an anonymous visitor sees". It lives on a
|
|
98
|
+
* `WebPage` node rather than on the Product, because `isAccessibleForFree` is a
|
|
99
|
+
* `CreativeWork` property and `Product` is not a `CreativeWork`.
|
|
100
|
+
*
|
|
101
|
+
* Serving the price to crawler user-agents but not to people would be cloaking,
|
|
102
|
+
* which is a harder violation; this declares the gate instead of hiding it.
|
|
103
|
+
*
|
|
104
|
+
* 🛑 Emit this on EVERY page that emits `offers.price`. The audit enforces the
|
|
105
|
+
* pairing precisely so nobody removes one half later.
|
|
106
|
+
*/
|
|
107
|
+
export declare function gatedPriceWebPageJsonLd(path: string): Record<string, JsonLdValue>;
|
|
87
108
|
export declare function productJsonLd(product: ProductJsonLdInput): Record<string, JsonLdValue>;
|
|
88
109
|
export declare function reviewJsonLd(review: ReviewJsonLdInput): Record<string, JsonLdValue>;
|
|
89
110
|
export declare function aggregateRatingJsonLd(product: Pick<ProductJsonLdInput, "title" | "slug">, stats: {
|
package/dist/seo/json-ld.js
CHANGED
|
@@ -39,6 +39,51 @@ function absoluteUrl(value, base = SITE_URL) {
|
|
|
39
39
|
import { ProductStatusValues } from "../features/products/schemas";
|
|
40
40
|
import { getDefaultCurrency } from "../core/baseline-resolver";
|
|
41
41
|
// --- Helpers -----------------------------------------------------------------
|
|
42
|
+
/**
|
|
43
|
+
* The CSS selector the price gate renders in every state.
|
|
44
|
+
*
|
|
45
|
+
* 🛑 Must equal `GATED_PRICE_CLASS` in `ui/components/GatedPrice.tsx`. It is
|
|
46
|
+
* duplicated as a literal here rather than imported because `seo/` is reached
|
|
47
|
+
* from server-only metadata paths and must not pull in a `"use client"` module
|
|
48
|
+
* (Root Cause #76). `audit-guest-price-leak` cross-checks the two.
|
|
49
|
+
*/
|
|
50
|
+
const GATED_PRICE_SELECTOR = ".appkit-gated-price";
|
|
51
|
+
/**
|
|
52
|
+
* Declares that this page's price is behind a sign-in gate.
|
|
53
|
+
*
|
|
54
|
+
* Prices are hidden from signed-out visitors, and Googlebot crawls signed out —
|
|
55
|
+
* so the crawler sees "Sign in to see price" while the Product JSON-LD beside it
|
|
56
|
+
* declares a real `offers.price`. Left undeclared, that is a structured-data
|
|
57
|
+
* mismatch: Google requires marked-up content to be visible to the user, and an
|
|
58
|
+
* unexplained gap can cost the rich result.
|
|
59
|
+
*
|
|
60
|
+
* `isAccessibleForFree: false` + `hasPart`/`cssSelector` is the supported way to
|
|
61
|
+
* say "the markup is richer than what an anonymous visitor sees". It lives on a
|
|
62
|
+
* `WebPage` node rather than on the Product, because `isAccessibleForFree` is a
|
|
63
|
+
* `CreativeWork` property and `Product` is not a `CreativeWork`.
|
|
64
|
+
*
|
|
65
|
+
* Serving the price to crawler user-agents but not to people would be cloaking,
|
|
66
|
+
* which is a harder violation; this declares the gate instead of hiding it.
|
|
67
|
+
*
|
|
68
|
+
* 🛑 Emit this on EVERY page that emits `offers.price`. The audit enforces the
|
|
69
|
+
* pairing precisely so nobody removes one half later.
|
|
70
|
+
*/
|
|
71
|
+
export function gatedPriceWebPageJsonLd(path) {
|
|
72
|
+
return {
|
|
73
|
+
"@context": "https://schema.org",
|
|
74
|
+
"@type": "WebPage",
|
|
75
|
+
// Absolutised against the ONE canonical host, never a second definition of
|
|
76
|
+
// it (Root Cause #81) — a WebPage node naming a host the sitemap does not
|
|
77
|
+
// advertise is the same split-identity bug in miniature.
|
|
78
|
+
url: absoluteUrl(path),
|
|
79
|
+
isAccessibleForFree: false,
|
|
80
|
+
hasPart: {
|
|
81
|
+
"@type": "WebPageElement",
|
|
82
|
+
isAccessibleForFree: false,
|
|
83
|
+
cssSelector: GATED_PRICE_SELECTOR,
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
42
87
|
export function productJsonLd(product) {
|
|
43
88
|
const url = `${SITE_URL}/products/${product.slug}`;
|
|
44
89
|
// Absolutise, then de-duplicate: `mainImage` is very often also `images[0]`,
|
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,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
|
-
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
return (
|
|
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
|
}
|