@mohasinac/appkit 4.69.1 → 4.71.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/PROVIDERS.md +190 -0
- package/dist/.build-stamp.json +3 -3
- package/dist/_internal/client/features/lottery/LotteryAdminSlotView.js +2 -1
- package/dist/_internal/server/features/checkout/actions.js +43 -8
- package/dist/_internal/server/features/orders/actions.js +2 -1
- package/dist/_internal/server/features/orders/adapters.js +18 -0
- package/dist/_internal/server/features/orders/balance.js +2 -1
- package/dist/_internal/server/features/orders/cancellation-refund.d.ts +0 -89
- package/dist/_internal/server/features/orders/cancellation-refund.js +3 -2
- package/dist/_internal/server/features/payouts/actions.js +2 -1
- package/dist/_internal/server/features/points/award.js +3 -2
- package/dist/_internal/server/features/protection-claims/actions.js +2 -1
- package/dist/_internal/server/features/refunds/actions.js +3 -2
- package/dist/_internal/server/features/shipping/courier-block.js +5 -2
- package/dist/_internal/server/features/shipping/reconcile.js +3 -2
- package/dist/_internal/server/features/shipping/reimbursement.js +3 -2
- package/dist/_internal/server/features/stores/adapters.d.ts +1 -1
- package/dist/_internal/server/features/stores/adapters.js +12 -2
- package/dist/_internal/server/jobs/core/dailyStatusDigest.js +7 -3
- package/dist/_internal/server/jobs/core/emiInstallmentReminder.js +2 -1
- package/dist/_internal/server/jobs/core/obligationSweep.js +5 -4
- package/dist/_internal/server/jobs/core/onScamReportCreate.js +2 -1
- package/dist/_internal/server/jobs/core/paymentReconcile.js +5 -4
- package/dist/_internal/shared/checkout/rules/auction.rule.js +25 -7
- package/dist/_internal/shared/checkout/rules/prize-draw.rule.js +26 -8
- package/dist/_internal/shared/config/schema.d.ts +15 -0
- package/dist/_internal/shared/content/published-values.d.ts +2 -2
- package/dist/_internal/shared/content/published-values.js +31 -12
- package/dist/_internal/shared/features/auctions/errors.js +2 -1
- package/dist/_internal/shared/features/orders/refund-copy.js +3 -2
- package/dist/_internal/shared/features/promotions/errors.js +2 -1
- package/dist/constants/field-names.d.ts +23 -0
- package/dist/constants/field-names.js +23 -0
- package/dist/constants/storage-keys.d.ts +90 -0
- package/dist/constants/storage-keys.js +119 -0
- package/dist/contracts/index.d.ts +1 -1
- package/dist/contracts/payment.d.ts +42 -0
- package/dist/contracts/registry.d.ts +8 -0
- package/dist/contracts/tax.d.ts +118 -0
- package/dist/contracts/tax.js +3 -0
- package/dist/core/brand-resolver.d.ts +92 -0
- package/dist/core/brand-resolver.js +120 -0
- package/dist/core/brand-text.d.ts +15 -0
- package/dist/core/brand-text.js +73 -0
- package/dist/core/hooks/useNewsletterSubscribe.d.ts +30 -0
- package/dist/core/hooks/useNewsletterSubscribe.js +121 -0
- package/dist/features/about/components/DeveloperView.js +2 -1
- package/dist/features/about/index.d.ts +0 -2
- package/dist/features/about/index.js +0 -1
- package/dist/features/account/components/BuyerAccountGuideView.js +2 -1
- package/dist/features/account/components/BuyerAuctionsGuideView.js +4 -3
- package/dist/features/account/components/BuyerOrdersGuideView.js +6 -5
- package/dist/features/account/components/BuyerShoppingGuideView.js +3 -2
- package/dist/features/account/components/UserOffersPanel.js +2 -1
- package/dist/features/account/hooks/useCollapsedSections.js +5 -3
- package/dist/features/account/schemas/index.js +1 -1
- package/dist/features/admin/actions/notification-actions.js +9 -2
- package/dist/features/admin/components/AdminAnalyticsGuideView.js +2 -1
- package/dist/features/admin/components/AdminBundlesView.js +2 -1
- package/dist/features/admin/components/AdminCatalogGuideView.js +2 -1
- package/dist/features/admin/components/AdminContentGuideView.js +2 -1
- package/dist/features/admin/components/AdminFaqEditorView.js +2 -1
- package/dist/features/admin/components/AdminGatewayEventsView.js +2 -1
- package/dist/features/admin/components/AdminGuideHubView.js +2 -1
- package/dist/features/admin/components/AdminMoneyCasesView.js +2 -1
- package/dist/features/admin/components/AdminOrdersGuideView.js +2 -1
- package/dist/features/admin/components/AdminProductsView.js +3 -2
- package/dist/features/admin/components/AdminSectionsView.js +3 -2
- package/dist/features/admin/components/AdminSiteSettingsView.js +8 -7
- package/dist/features/admin/components/AdminStoresGuideView.js +2 -1
- package/dist/features/admin/components/AdminSupportTicketDetailView.js +2 -1
- package/dist/features/admin/components/AdminTeamGuideView.js +2 -1
- package/dist/features/admin/components/AdminTrustGuideView.js +3 -2
- package/dist/features/admin/components/AdminUsersGuideView.js +2 -1
- package/dist/features/admin/components/sections/adminSectionsTypes.js +1 -1
- package/dist/features/admin/schemas/firestore.js +0 -1
- package/dist/features/auth/actions/user-slug.js +21 -2
- package/dist/features/auth/hooks/useAuth.js +11 -4
- package/dist/features/cart/utils/pending-ops.js +16 -3
- package/dist/features/catalogue/utils/product-from-item.d.ts +18 -2
- package/dist/features/catalogue/utils/product-from-item.js +23 -4
- package/dist/features/categories/components/BundleItemsPicker.js +2 -1
- package/dist/features/categories/components/CategoryDetailPageView.js +2 -1
- package/dist/features/categories/components/ContextualTrail.d.ts +9 -2
- package/dist/features/categories/components/ContextualTrail.js +11 -3
- package/dist/features/categories/components/LastCategoryBeacon.js +2 -2
- package/dist/features/checkout/actions/checkout-value-otp-actions.js +9 -1
- package/dist/features/contact/email.js +11 -12
- package/dist/features/email/notification-templates.js +4 -5
- package/dist/features/email/render-static-markup.d.ts +34 -0
- package/dist/features/email/render-static-markup.js +4 -0
- package/dist/features/events/utils/event-charts.js +2 -1
- package/dist/features/homepage/components/AnnouncementBar.js +5 -3
- package/dist/features/homepage/components/FAQSection.js +1 -1
- package/dist/features/homepage/components/MarketplaceHomepageView.js +34 -0
- package/dist/features/homepage/components/WelcomeSection.d.ts +3 -1
- package/dist/features/homepage/components/WelcomeSection.js +2 -2
- package/dist/features/homepage/lib/section-renderer.d.ts +8 -0
- package/dist/features/homepage/lib/section-renderer.js +1 -1
- package/dist/features/layout/AppLayoutShell.d.ts +3 -1
- package/dist/features/layout/AppLayoutShell.js +2 -2
- package/dist/features/layout/TitleBarLayout.d.ts +8 -1
- package/dist/features/layout/TitleBarLayout.js +2 -2
- package/dist/features/media/MediaVideo.js +2 -1
- package/dist/features/orders/actions/refund-actions.js +3 -2
- package/dist/features/orders/components/RefundRequestView.js +15 -2
- package/dist/features/orders/repository/orders.repository.js +2 -1
- package/dist/features/orders/types/index.d.ts +14 -0
- package/dist/features/orders/utils/payment-proof-message.js +5 -2
- package/dist/features/price-index/components/PriceIndexToolView.js +6 -5
- package/dist/features/pricing/components/AdminPricingView.js +2 -1
- package/dist/features/print/components/labels.js +2 -1
- package/dist/features/products/components/CityCollectiblesView.js +2 -1
- package/dist/features/products/components/MakeOfferButton.js +2 -1
- package/dist/features/products/components/PrizeDrawCollage.js +3 -2
- package/dist/features/products/components/PrizeRevealModal.js +2 -1
- package/dist/features/products/repository/products.repository.js +2 -1
- package/dist/features/promotions/components/CouponCard.js +3 -2
- package/dist/features/promotions/components/CouponHelpDetails.js +2 -1
- package/dist/features/promotions/constants/coupon-help.d.ts +1 -1
- package/dist/features/promotions/constants/coupon-help.js +8 -1
- package/dist/features/scams/components/ScamAwarenessModal.js +2 -1
- package/dist/features/scams/components/ScamProfileView.js +5 -3
- package/dist/features/scams/components/ScamRegistryView.js +2 -1
- package/dist/features/scams/constants/scam-types.d.ts +13 -1
- package/dist/features/scams/constants/scam-types.js +67 -23
- package/dist/features/scams/schemas/firestore.js +1 -1
- package/dist/features/search/components/Search.js +5 -1
- package/dist/features/seller/actions/offer-actions.js +47 -9
- package/dist/features/seller/components/DispatchForm.js +3 -2
- package/dist/features/seller/components/PrintCenterView.js +4 -2
- package/dist/features/seller/components/SellerBundlesView.js +2 -1
- package/dist/features/seller/components/SellerPayoutSettingsView.js +3 -2
- package/dist/features/seller/components/SellerProductShell.js +3 -2
- package/dist/features/seller/components/SellerProductsView.js +4 -3
- package/dist/features/store-extensions/repository/store-extensions.repositories.d.ts +30 -4
- package/dist/features/store-extensions/repository/store-extensions.repositories.js +46 -7
- package/dist/features/store-extensions/schemas/firestore.d.ts +22 -3
- package/dist/features/store-extensions/schemas/firestore.js +20 -0
- package/dist/features/stores/components/StoreCapabilitiesGuideView.js +7 -6
- package/dist/features/stores/components/StoreFinanceGuideView.js +6 -5
- package/dist/features/stores/components/StoreGuideHubView.js +2 -1
- package/dist/features/stores/components/StoreListingsGuideView.js +2 -1
- package/dist/features/stores/components/StoreOrdersGuideView.js +5 -4
- package/dist/features/stores/components/StoreSettingsGuideView.js +2 -1
- package/dist/features/support/schemas/index.d.ts +2 -2
- package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.d.ts +1 -1
- package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +3 -2
- package/dist/features/whatsapp-bot/helpers/whatsapp.js +5 -2
- package/dist/index.d.ts +0 -3
- package/dist/index.js +0 -6
- package/dist/providers/payment-manual/index.d.ts +11 -0
- package/dist/providers/payment-manual/index.js +13 -0
- package/dist/providers/payment-phonepe/index.d.ts +23 -7
- package/dist/providers/payment-phonepe/index.js +15 -0
- package/dist/providers/payment-razorpay/index.d.ts +13 -0
- package/dist/providers/payment-razorpay/index.js +21 -1
- package/dist/providers/tax-india-gst/index.d.ts +45 -0
- package/dist/providers/tax-india-gst/index.js +78 -0
- package/dist/react/hooks/useScrollToTop.d.ts +5 -0
- package/dist/react/hooks/useScrollToTop.js +70 -0
- package/dist/schemas/registry.d.ts +2 -2
- package/dist/seo/json-ld.js +25 -4
- package/dist/styles.css +20 -0
- package/dist/ui/components/SiteLogo.d.ts +20 -2
- package/dist/ui/components/SiteLogo.js +28 -6
- package/dist/ui/components/SiteMark.d.ts +1 -1
- package/dist/ui/components/SiteMark.js +21 -6
- package/dist/ui/components/Tabs.d.ts +9 -1
- package/dist/ui/components/Tabs.js +20 -3
- package/dist/ui/components/Tabs.style.css +20 -0
- package/dist/ui/rich-text/RichText.d.ts +0 -12
- package/dist/ui/rich-text/RichText.js +52 -1
- package/dist/utils/date.formatter.d.ts +20 -0
- package/dist/utils/date.formatter.js +25 -0
- package/dist/utils/number.formatter.d.ts +47 -0
- package/dist/utils/number.formatter.js +79 -1
- package/dist/validation/phone.validator.d.ts +0 -1
- package/dist/validation/phone.validator.js +17 -3
- package/package.json +17 -3
- package/dist/features/about/components/AboutView.d.ts +0 -30
- package/dist/features/about/components/AboutView.js +0 -14
|
@@ -4,6 +4,13 @@ import { useCallback, useEffect, useRef, useState } from "react";
|
|
|
4
4
|
import { useMutation, useQuery } from "@tanstack/react-query";
|
|
5
5
|
import { getClientAuthProvider } from "../../../contracts/client-auth.js";
|
|
6
6
|
import { getClientSessionAdapter } from "../../../contracts/client-session.js";
|
|
7
|
+
/*
|
|
8
|
+
* 🛑 The auth-popup message type is a CROSS-PACKAGE contract: appkit listens
|
|
9
|
+
* here, the consumer's /auth/close page posts. Both must read this constant —
|
|
10
|
+
* namespacing one side only makes the popup post a message nobody hears, and
|
|
11
|
+
* OAuth login fails silently with no type error anywhere.
|
|
12
|
+
*/
|
|
13
|
+
import { MESSAGE_TYPES, STORAGE_KEYS } from "../../../constants/storage-keys.js";
|
|
7
14
|
import { apiClient } from "../../../http/index.js";
|
|
8
15
|
import { useAuthEvent } from "./useAuthEvent.js";
|
|
9
16
|
import { RealtimeEventStatus } from "../../../react/hooks/useRealtimeEvent.js";
|
|
@@ -113,7 +120,7 @@ export function useGoogleLogin(options) {
|
|
|
113
120
|
const handleMessage = (event) => {
|
|
114
121
|
if (event.origin !== window.location.origin)
|
|
115
122
|
return;
|
|
116
|
-
if (!event.data || event.data.type !==
|
|
123
|
+
if (!event.data || event.data.type !== MESSAGE_TYPES.authClose)
|
|
117
124
|
return;
|
|
118
125
|
if (calledRef.current)
|
|
119
126
|
return;
|
|
@@ -159,7 +166,7 @@ export function useGoogleLogin(options) {
|
|
|
159
166
|
authEvent.reset();
|
|
160
167
|
const { eventId, customToken, rtdbEnabled } = await apiClient.post(AUTH_ENDPOINTS.EVENT_INIT, {});
|
|
161
168
|
const url = `${window.location.origin}${AUTH_ENDPOINTS.GOOGLE_START}?eventId=${encodeURIComponent(eventId)}`;
|
|
162
|
-
localStorage.setItem(
|
|
169
|
+
localStorage.setItem(STORAGE_KEYS.oauthRedirect, url);
|
|
163
170
|
// Only subscribe to RTDB if it's available — skipping avoids an immediate
|
|
164
171
|
// FAILED status (token sign-in error) that would block the postMessage fallback.
|
|
165
172
|
if (rtdbEnabled !== false) {
|
|
@@ -229,7 +236,7 @@ export function useLinkGoogleAccount(options) {
|
|
|
229
236
|
const handleMessage = (event) => {
|
|
230
237
|
if (event.origin !== window.location.origin)
|
|
231
238
|
return;
|
|
232
|
-
if (!event.data || event.data.type !==
|
|
239
|
+
if (!event.data || event.data.type !== MESSAGE_TYPES.authClose)
|
|
233
240
|
return;
|
|
234
241
|
if (calledRef.current)
|
|
235
242
|
return;
|
|
@@ -270,7 +277,7 @@ export function useLinkGoogleAccount(options) {
|
|
|
270
277
|
authEvent.reset();
|
|
271
278
|
const { eventId, customToken, rtdbEnabled } = await apiClient.post(AUTH_ENDPOINTS.EVENT_INIT, { mode: "link" });
|
|
272
279
|
const url = `${window.location.origin}${AUTH_ENDPOINTS.GOOGLE_START}?eventId=${encodeURIComponent(eventId)}`;
|
|
273
|
-
localStorage.setItem(
|
|
280
|
+
localStorage.setItem(STORAGE_KEYS.oauthRedirect, url);
|
|
274
281
|
if (rtdbEnabled !== false) {
|
|
275
282
|
authEvent.subscribe(eventId, customToken);
|
|
276
283
|
}
|
|
@@ -6,9 +6,22 @@
|
|
|
6
6
|
* The sync manager replays these and clears them after a successful push.
|
|
7
7
|
*/
|
|
8
8
|
import { normalizeError } from "../../../errors/normalize.js";
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
import { getStorageNamespace } from "../../../constants/storage-keys.js";
|
|
10
|
+
/*
|
|
11
|
+
* 🛑 A SECOND storage-key namespace owner, now removed.
|
|
12
|
+
*
|
|
13
|
+
* A1 made `constants/storage-keys.ts` the one namespace, injected by the
|
|
14
|
+
* consumer via `setStorageNamespace()`. This file had its own parallel
|
|
15
|
+
* mechanism reading a DIFFERENT env var (`NEXT_PUBLIC_APP_ID`) with a
|
|
16
|
+
* hardcoded `"letitrip"` fallback — so two modules decided what prefixes a
|
|
17
|
+
* browser key, and only one of them was configurable.
|
|
18
|
+
*
|
|
19
|
+
* The resolved value is unchanged for this install (the namespace is also
|
|
20
|
+
* "letitrip"), which matters: changing the prefix would ORPHAN every queued
|
|
21
|
+
* cart/wishlist op already sitting in a returning visitor's localStorage
|
|
22
|
+
* rather than migrating it.
|
|
23
|
+
*/
|
|
24
|
+
const APP_ID = () => getStorageNamespace();
|
|
12
25
|
const CART_OPS_KEY = () => `${APP_ID()}_cart_ops`;
|
|
13
26
|
const WL_OPS_KEY = () => `${APP_ID()}_wl_ops`;
|
|
14
27
|
/** Fired whenever the pending cart/wishlist op queue changes, so a badge
|
|
@@ -1,8 +1,24 @@
|
|
|
1
1
|
import type { CatalogueItemDocument } from "../schemas/firestore";
|
|
2
2
|
import type { ProductDocument } from "../../products/schemas/firestore";
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* The platform's own consignment store — used whenever a catalogue item is
|
|
4
5
|
* listed by someone with no personal seller store of their own (buyer
|
|
5
|
-
* approval path, admin direct-list path).
|
|
6
|
+
* approval path, admin direct-list path).
|
|
7
|
+
*
|
|
8
|
+
* 🛑 DERIVED, no longer its own literal. `store-letitrip-official` was written
|
|
9
|
+
* out three times: here, as `PLATFORM_STORE_ID` in
|
|
10
|
+
* `features/stores/schemas/firestore.ts`, and again in the consumer's
|
|
11
|
+
* shipment-link route. And the comment on `PLATFORM_STORE_ID` justified its
|
|
12
|
+
* copy by pointing AT this one — "the same literal already lives legitimately
|
|
13
|
+
* (`CONSIGNMENT_STORE_ID` …)". That argument is about WHERE a brand-bearing
|
|
14
|
+
* literal may live (`audit-ssr-in-appkit` Rule 3 bars `_internal/`); it says
|
|
15
|
+
* nothing about how many copies there may be, and reading it as permission
|
|
16
|
+
* gave one id three owners (Root Cause #42).
|
|
17
|
+
*
|
|
18
|
+
* They are the same store, in two roles. If a consumer ever needs a separate
|
|
19
|
+
* consignment storefront, that is a deliberate second configured value — not
|
|
20
|
+
* a literal that drifted from this one.
|
|
21
|
+
*/
|
|
6
22
|
export declare const CONSIGNMENT_STORE_ID = "store-letitrip-official";
|
|
7
23
|
/**
|
|
8
24
|
* Shared field mapping from a catalogue item to a new product — used by
|
|
@@ -1,8 +1,26 @@
|
|
|
1
1
|
import { productRepository } from "../../products/repository/products.repository.js";
|
|
2
|
-
|
|
2
|
+
import { getBrandName } from "../../../core/brand-resolver.js";
|
|
3
|
+
import { PLATFORM_STORE_ID } from "../../stores/schemas/firestore.js";
|
|
4
|
+
/**
|
|
5
|
+
* The platform's own consignment store — used whenever a catalogue item is
|
|
3
6
|
* listed by someone with no personal seller store of their own (buyer
|
|
4
|
-
* approval path, admin direct-list path).
|
|
5
|
-
|
|
7
|
+
* approval path, admin direct-list path).
|
|
8
|
+
*
|
|
9
|
+
* 🛑 DERIVED, no longer its own literal. `store-letitrip-official` was written
|
|
10
|
+
* out three times: here, as `PLATFORM_STORE_ID` in
|
|
11
|
+
* `features/stores/schemas/firestore.ts`, and again in the consumer's
|
|
12
|
+
* shipment-link route. And the comment on `PLATFORM_STORE_ID` justified its
|
|
13
|
+
* copy by pointing AT this one — "the same literal already lives legitimately
|
|
14
|
+
* (`CONSIGNMENT_STORE_ID` …)". That argument is about WHERE a brand-bearing
|
|
15
|
+
* literal may live (`audit-ssr-in-appkit` Rule 3 bars `_internal/`); it says
|
|
16
|
+
* nothing about how many copies there may be, and reading it as permission
|
|
17
|
+
* gave one id three owners (Root Cause #42).
|
|
18
|
+
*
|
|
19
|
+
* They are the same store, in two roles. If a consumer ever needs a separate
|
|
20
|
+
* consignment storefront, that is a deliberate second configured value — not
|
|
21
|
+
* a literal that drifted from this one.
|
|
22
|
+
*/
|
|
23
|
+
export const CONSIGNMENT_STORE_ID = PLATFORM_STORE_ID;
|
|
6
24
|
/**
|
|
7
25
|
* Shared field mapping from a catalogue item to a new product — used by
|
|
8
26
|
* both the direct-listing path (seller/admin) and the admin-approval path
|
|
@@ -14,7 +32,8 @@ export const CONSIGNMENT_STORE_ID = "store-letitrip-official";
|
|
|
14
32
|
export async function createProductFromCatalogueItem(item, storeId) {
|
|
15
33
|
return productRepository.create({
|
|
16
34
|
title: item.title,
|
|
17
|
-
description: item.description ||
|
|
35
|
+
description: item.description ||
|
|
36
|
+
`Listed via ${getBrandName()}'s personal catalogue.`,
|
|
18
37
|
categorySlugs: item.categorySlugs ?? [],
|
|
19
38
|
brandSlug: item.brandSlug,
|
|
20
39
|
price: item.price ?? 0,
|
|
@@ -25,6 +25,7 @@ import { BUNDLE_MAX_ITEMS, BUNDLE_MIN_ITEMS, } from "../../../_internal/shared/f
|
|
|
25
25
|
import { BUNDLE_COPY } from "../../../_internal/shared/features/categories/bundle-copy.js";
|
|
26
26
|
import { PRODUCT_ENDPOINTS } from "../../../constants/api-endpoints.js";
|
|
27
27
|
import { productImageUrls } from "../../products/schemas/firestore.js";
|
|
28
|
+
import { formatMoney } from "../../../utils/number.formatter.js";
|
|
28
29
|
const __P = {
|
|
29
30
|
p3: "p-[var(--appkit-space-3)]",
|
|
30
31
|
};
|
|
@@ -48,7 +49,7 @@ function renderBundleSearchResults(props) {
|
|
|
48
49
|
return (_jsxs(_Fragment, { children: [searchError && _jsx(Text, { size: "sm", color: "danger", children: searchError }), isSearching && _jsx(Text, { size: "sm", color: "muted", children: BUNDLE_COPY.picker.searchingHint }), !isSearching && debouncedQuery.length >= 2 && results.length === 0 && !searchError && (_jsx(Text, { size: "sm", color: "muted", children: BUNDLE_COPY.picker.noMatches(debouncedQuery) })), results.length > 0 && (_jsx(Div, { className: `max-h-72 ${__O.yAuto}`, rounded: "lg", border: "default", children: results.map((r) => {
|
|
49
50
|
const isSelected = selectedSet.has(r.id);
|
|
50
51
|
const atCap = !isSelected && value.length >= maxItems;
|
|
51
|
-
return (_jsxs(Row, { border: "subtle", gap: "sm", align: "center", className: `border-b last:border-b-0 ${isSelected ? CLS_ROW_SELECTED : ""} ${atCap ? "opacity-50" : ""}`, padding: "inlineSm", children: [_jsx(Checkbox, { checked: isSelected, disabled: atCap, onChange: () => toggle(r.id), "aria-label": BUNDLE_COPY.picker.toggleAria(r.title) }), _jsxs(Stack, { gap: "xs", className: "flex-1", children: [_jsx(Text, { size: "sm", weight: "medium", children: r.title }), _jsxs(Row, { gap: "sm", children: [_jsx(Text, { size: "xs", color: "muted", children: r.id }), typeof r.price === "number" && _jsxs(Text, { size: "xs", color: "muted", children: ["\u00B7
|
|
52
|
+
return (_jsxs(Row, { border: "subtle", gap: "sm", align: "center", className: `border-b last:border-b-0 ${isSelected ? CLS_ROW_SELECTED : ""} ${atCap ? "opacity-50" : ""}`, padding: "inlineSm", children: [_jsx(Checkbox, { checked: isSelected, disabled: atCap, onChange: () => toggle(r.id), "aria-label": BUNDLE_COPY.picker.toggleAria(r.title) }), _jsxs(Stack, { gap: "xs", className: "flex-1", children: [_jsx(Text, { size: "sm", weight: "medium", children: r.title }), _jsxs(Row, { gap: "sm", children: [_jsx(Text, { size: "xs", color: "muted", children: r.id }), typeof r.price === "number" && _jsxs(Text, { size: "xs", color: "muted", children: ["\u00B7 ", formatMoney(r.price)] })] })] })] }, r.id));
|
|
52
53
|
}) }))] }));
|
|
53
54
|
}
|
|
54
55
|
export function BundleItemsPicker({ value, onChange, fetchProducts, minItems = BUNDLE_MIN_ITEMS, maxItems = BUNDLE_MAX_ITEMS, initialMetadata = {}, }) {
|
|
@@ -36,6 +36,7 @@ import { breadcrumbJsonLd, collectionPageJsonLd, faqJsonLd, } from "../../../seo
|
|
|
36
36
|
import { toStoreListItem } from "../../../_internal/server/features/stores/adapters.js";
|
|
37
37
|
import { hidePublicTestData } from "../../../_internal/server/features/tester/visibility.js";
|
|
38
38
|
import { productImageUrls } from "../../products/schemas/firestore.js";
|
|
39
|
+
import { getBrandName } from "../../../core/brand-resolver.js";
|
|
39
40
|
const __O = {
|
|
40
41
|
hidden: "overflow-hidden",
|
|
41
42
|
};
|
|
@@ -603,7 +604,7 @@ export async function CategoryDetailPageView({ slug }) {
|
|
|
603
604
|
const ldFaq = category?.faqs && category.faqs.length > 0
|
|
604
605
|
? faqJsonLd(category.faqs.map((f) => ({ question: f.question, answer: f.answer })))
|
|
605
606
|
: null;
|
|
606
|
-
return (_jsxs(Main, { children: [_jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: JSON.stringify(ldBreadcrumb) } }), _jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: JSON.stringify(ldCollection) } }), ldFaq && (_jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: JSON.stringify(ldFaq) } })), _jsxs(Section, { className: `relative ${__O.hidden} ${hasCover ? "min-h-[220px] md:min-h-[280px]" : "bg-[var(--appkit-color-bg)]"}`, children: [hasCover && (_jsxs(_Fragment, { children: [_jsx(Div, { className: "absolute inset-0", children: _jsx(MediaImage, { src: coverImage, alt: "", size: "banner" }) }), _jsx(Div, { surface: "overlay-md", className: "absolute inset-0" })] })), !hasCover && hasMosaic && (_jsx(Div, { className: "relative z-10 max-w-7xl mx-auto", padding: "x-md", children: _jsx(Div, { padding: "t-lg", children: _jsx(ImageMosaic, { images: mosaicImages, alt: `${category?.name ?? slug} on
|
|
607
|
+
return (_jsxs(Main, { children: [_jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: JSON.stringify(ldBreadcrumb) } }), _jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: JSON.stringify(ldCollection) } }), ldFaq && (_jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: JSON.stringify(ldFaq) } })), _jsxs(Section, { className: `relative ${__O.hidden} ${hasCover ? "min-h-[220px] md:min-h-[280px]" : "bg-[var(--appkit-color-bg)]"}`, children: [hasCover && (_jsxs(_Fragment, { children: [_jsx(Div, { className: "absolute inset-0", children: _jsx(MediaImage, { src: coverImage, alt: "", size: "banner" }) }), _jsx(Div, { surface: "overlay-md", className: "absolute inset-0" })] })), !hasCover && hasMosaic && (_jsx(Div, { className: "relative z-10 max-w-7xl mx-auto", padding: "x-md", children: _jsx(Div, { padding: "t-lg", children: _jsx(ImageMosaic, { images: mosaicImages, alt: `${category?.name ?? slug} on ${getBrandName()}` }) }) })), _jsxs(Div, { className: "relative z-10 max-w-7xl mx-auto", paddingY: hasCover ? "y-3xl" : "y-xl", padding: "x-md", children: [_jsx(BreadcrumbTrail, { crumbs: trail, className: "mb-4", tone: hasCover ? "on-media" : "default" }), category?.id && _jsx(LastCategoryBeacon, { categoryId: category.id }), _jsx(Heading, { color: "inverse", level: 1, className: `mb-2 ${hasCover ? "" : "text-[var(--appkit-color-text)]"}`, size: "3xl", mdSize: "4xl", weight: "bold", children: category?.name ?? slug }), category?.id && (_jsx(RecordOwnerQuickActions, { resource: "CATEGORY", recordId: category.id, placement: "detail",
|
|
607
608
|
/*
|
|
608
609
|
* Same flag the rest of this hero branches on. Without it the
|
|
609
610
|
* pencil renders `ghost` (zinc-600) on the dark cover and is
|
|
@@ -41,8 +41,15 @@ import type { BreadcrumbCrumb } from "../utils/breadcrumb-chain";
|
|
|
41
41
|
* @tag layer:client-component
|
|
42
42
|
* @tag access:client-only
|
|
43
43
|
*/
|
|
44
|
-
/**
|
|
45
|
-
|
|
44
|
+
/**
|
|
45
|
+
* The `sessionStorage` key. Read here, written by `<LastCategoryBeacon>`.
|
|
46
|
+
*
|
|
47
|
+
* 🛑 A getter, not a `const` — the namespace is configured by the consumer's
|
|
48
|
+
* bootstrap, which runs after this module is evaluated, so a value captured at
|
|
49
|
+
* import time would freeze the default. Kept as a named export because
|
|
50
|
+
* `<LastCategoryBeacon>` writes through the same key.
|
|
51
|
+
*/
|
|
52
|
+
export declare const getLastCategoryKey: () => string;
|
|
46
53
|
export interface ContextualTrailProps {
|
|
47
54
|
/**
|
|
48
55
|
* One entry per assignment, DEFAULT FIRST. Each carries the assignment's
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import React from "react";
|
|
4
4
|
import { storageManager } from "../../../core/StorageManager.js";
|
|
5
|
+
import { STORAGE_KEYS } from "../../../constants/storage-keys.js";
|
|
5
6
|
import { BreadcrumbTrail } from "./BreadcrumbTrail.js";
|
|
6
7
|
/**
|
|
7
8
|
* I5b — the visible trail follows the path the visitor actually took, when
|
|
@@ -44,8 +45,15 @@ import { BreadcrumbTrail } from "./BreadcrumbTrail.js";
|
|
|
44
45
|
* @tag layer:client-component
|
|
45
46
|
* @tag access:client-only
|
|
46
47
|
*/
|
|
47
|
-
/**
|
|
48
|
-
|
|
48
|
+
/**
|
|
49
|
+
* The `sessionStorage` key. Read here, written by `<LastCategoryBeacon>`.
|
|
50
|
+
*
|
|
51
|
+
* 🛑 A getter, not a `const` — the namespace is configured by the consumer's
|
|
52
|
+
* bootstrap, which runs after this module is evaluated, so a value captured at
|
|
53
|
+
* import time would freeze the default. Kept as a named export because
|
|
54
|
+
* `<LastCategoryBeacon>` writes through the same key.
|
|
55
|
+
*/
|
|
56
|
+
export const getLastCategoryKey = () => STORAGE_KEYS.lastCategory;
|
|
49
57
|
export function ContextualTrail({ trails, alternates, className }) {
|
|
50
58
|
// Index 0 is the default chain and is what the server rendered.
|
|
51
59
|
const [index, setIndex] = React.useState(0);
|
|
@@ -63,7 +71,7 @@ export function ContextualTrail({ trails, alternates, className }) {
|
|
|
63
71
|
* component needs no catch at all and there is one implementation of
|
|
64
72
|
* "storage might not exist" rather than a second, quieter copy.
|
|
65
73
|
*/
|
|
66
|
-
const last = storageManager.get(
|
|
74
|
+
const last = storageManager.get(getLastCategoryKey(), { type: "session" });
|
|
67
75
|
if (!last)
|
|
68
76
|
return;
|
|
69
77
|
const match = trails.findIndex((t) => t.categoryId === last);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import React from "react";
|
|
3
3
|
import { storageManager } from "../../../core/StorageManager.js";
|
|
4
|
-
import {
|
|
4
|
+
import { getLastCategoryKey } from "./ContextualTrail.js";
|
|
5
5
|
/**
|
|
6
6
|
* Record which category page the visitor is on, for `<ContextualTrail>`.
|
|
7
7
|
*
|
|
@@ -27,7 +27,7 @@ export function LastCategoryBeacon({ categoryId }) {
|
|
|
27
27
|
* and no second copy of that logic. A `false` return means the visitor
|
|
28
28
|
* keeps the default trail, which is a correct answer.
|
|
29
29
|
*/
|
|
30
|
-
storageManager.set(
|
|
30
|
+
storageManager.set(getLastCategoryKey(), categoryId, { type: "session" });
|
|
31
31
|
}, [categoryId]);
|
|
32
32
|
return null;
|
|
33
33
|
}
|
|
@@ -20,6 +20,7 @@ import { sendWhatsAppBusinessMessage } from "../../whatsapp-bot/helpers/whatsapp
|
|
|
20
20
|
import { isChannelHealthy, withChannelRetry } from "../../../_internal/server/notifications/channel-health.js";
|
|
21
21
|
import { guardSend, logSuppressedSend } from "../../../_internal/server/notifications/send-guard.js";
|
|
22
22
|
import { CHECKOUT_VALUE_OTP_EXPIRY_MS, CHECKOUT_VALUE_OTP_MAX_ATTEMPTS, hashOtp, generateOtpCode, checkoutValueOtpRef, enforceCheckoutValueOtpRateLimit, buildCheckoutValueOtpMessage, } from "../../auth/checkout-value-otp.js";
|
|
23
|
+
import { getBrandName } from "../../../core/brand-resolver.js";
|
|
23
24
|
function maskEmail(userEmail) {
|
|
24
25
|
const [local, domain] = userEmail.split("@");
|
|
25
26
|
return domain
|
|
@@ -44,7 +45,14 @@ export async function sendCheckoutValueOtp(userId, userEmail, channel = "email",
|
|
|
44
45
|
const code = generateOtpCode();
|
|
45
46
|
const codeHash = hashOtp(code);
|
|
46
47
|
const expiresAt = new Date(Date.now() + CHECKOUT_VALUE_OTP_EXPIRY_MS);
|
|
47
|
-
|
|
48
|
+
/*
|
|
49
|
+
* 🛑 Was `process.env.NEXT_PUBLIC_SITE_NAME || "LetItRip"`, and the env var
|
|
50
|
+
* holds `"Let It Rip India"` — which is `appkit.config.js → brand.heroCaption`,
|
|
51
|
+
* not `brand.name`. So every high-value checkout OTP named the site something
|
|
52
|
+
* the brand owner does not call it, and an unconfigured install of appkit
|
|
53
|
+
* named it this marketplace.
|
|
54
|
+
*/
|
|
55
|
+
const siteName = getBrandName();
|
|
48
56
|
if (channel === "whatsapp") {
|
|
49
57
|
if (!addressId) {
|
|
50
58
|
throw new ValidationError("Select a delivery address before sending the code via WhatsApp.");
|
|
@@ -1,16 +1,9 @@
|
|
|
1
1
|
import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import "server-only";
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
|
|
7
|
-
// re-export chain is reachable from a Server Component (e.g. layout.tsx
|
|
8
|
-
// → providers.config.ts → @mohasinac/appkit/server → ... → email.tsx).
|
|
9
|
-
// `require()` is opaque to Next's static analyser so the chain stops here.
|
|
10
|
-
function renderToStaticMarkup(el) {
|
|
11
|
-
const m = require("react-dom/server");
|
|
12
|
-
return m.renderToStaticMarkup(el);
|
|
13
|
-
}
|
|
3
|
+
// The lazy `require("react-dom/server")` this file used to carry inline now
|
|
4
|
+
// lives in ONE place — see render-static-markup.ts for why it cannot be a
|
|
5
|
+
// top-level import, and why there is exactly one copy of it.
|
|
6
|
+
import { renderStaticMarkup as renderToStaticMarkup } from "../email/render-static-markup.js";
|
|
14
7
|
import { normalizeError } from "../../errors/normalize.js";
|
|
15
8
|
import { guardSend, logSuppressedSend } from "../../_internal/server/notifications/send-guard.js";
|
|
16
9
|
import { recordSendAttempt } from "../../_internal/server/notifications/send-recorder.js";
|
|
@@ -20,8 +13,14 @@ import { siteSettingsRepository } from "../admin/repository/site-settings.reposi
|
|
|
20
13
|
import { serverLogger } from "../../monitoring/index.js";
|
|
21
14
|
import { currentYear, formatCurrency } from "../../utils/index.js";
|
|
22
15
|
import { EmailBold, EmailButton, EmailContainer, EmailDivider, EmailDoc, EmailFooter, EmailHeader, EmailRow } from "../email/primitives.js";
|
|
16
|
+
import { getBrandName } from "../../core/brand-resolver.js";
|
|
17
|
+
/*
|
|
18
|
+
* One of the five `NEXT_PUBLIC_SITE_NAME` readers with five different
|
|
19
|
+
* fallbacks that `core/brand-resolver.ts` was written to replace. Kept as a
|
|
20
|
+
* local function so the two call sites below are untouched.
|
|
21
|
+
*/
|
|
23
22
|
function getSiteName() {
|
|
24
|
-
return
|
|
23
|
+
return getBrandName();
|
|
25
24
|
}
|
|
26
25
|
function getSiteUrl() {
|
|
27
26
|
return process.env.NEXTAUTH_URL?.trim() || "http://localhost:3000";
|
|
@@ -31,11 +31,10 @@ import { EmailBold, EmailButton, EmailContainer, EmailDivider, EmailDoc, EmailFo
|
|
|
31
31
|
* inline-styled markup email clients actually render; hand-rolled markup
|
|
32
32
|
* is what `audit-email-raw-html` blocks.
|
|
33
33
|
*/
|
|
34
|
-
// See constraint (1) above
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
34
|
+
// See constraint (1) above. The lazy require now lives in ONE place —
|
|
35
|
+
// `render-static-markup.ts` — because two more call sites needed it and a third
|
|
36
|
+
// identical copy would trip `audit-duplicate-helpers`.
|
|
37
|
+
import { renderStaticMarkup as renderToStaticMarkup } from "./render-static-markup.js";
|
|
39
38
|
/**
|
|
40
39
|
* 🛑 Every notification type, no exceptions.
|
|
41
40
|
*
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `renderToStaticMarkup`, behind a lazy `require` — the ONE definition.
|
|
3
|
+
*
|
|
4
|
+
* ## Why this is not a plain import
|
|
5
|
+
*
|
|
6
|
+
* A top-level `import { renderToStaticMarkup } from "react-dom/server"` is
|
|
7
|
+
* flagged by Next's static tracer the moment any module in the re-export chain
|
|
8
|
+
* is reachable from a Server Component — and these are, via
|
|
9
|
+
* `@mohasinac/appkit/server` → `providers.config.ts` → `src/app/layout.tsx`:
|
|
10
|
+
*
|
|
11
|
+
* Error: You're importing a component that imports react-dom/server.
|
|
12
|
+
*
|
|
13
|
+
* `require()` is opaque to that analyser, so the chain stops here. The
|
|
14
|
+
* specifier MUST stay BARE: a relative one resolves against the emitted chunk
|
|
15
|
+
* and fails only in production (Root Cause #86).
|
|
16
|
+
*
|
|
17
|
+
* ## 🛑 Why it is a module rather than a local helper per file
|
|
18
|
+
*
|
|
19
|
+
* It was a local copy in `features/contact/email.tsx` and
|
|
20
|
+
* `features/email/notification-templates.tsx`, which worked — but
|
|
21
|
+
* `courier-block` and `dailyStatusDigest` imported `react-dom/server` DIRECTLY
|
|
22
|
+
* and broke `next build` outright. Giving each of those a fourth and fifth copy
|
|
23
|
+
* of the shim would have traded a build error for an `audit-duplicate-helpers`
|
|
24
|
+
* failure, which fires at THREE byte-identical top-level bodies. One definition
|
|
25
|
+
* is the only answer that satisfies both.
|
|
26
|
+
*
|
|
27
|
+
* 🛑 The build error was INVISIBLE on the registry pin, because a package in
|
|
28
|
+
* `node_modules` is not traversed as first-party source. It surfaced only when
|
|
29
|
+
* the consumer was switched to `file:./appkit`. So "it builds" was a property
|
|
30
|
+
* of where appkit was installed, not of appkit being correct — which matters
|
|
31
|
+
* for Tier AK, where the whole point is that appkit must stand on its own.
|
|
32
|
+
*/
|
|
33
|
+
import type { ReactElement } from "react";
|
|
34
|
+
export declare function renderStaticMarkup(el: ReactElement): string;
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* @tag layer:utils
|
|
17
17
|
*/
|
|
18
18
|
import { calculateDiscount } from "../../promotions/schemas/firestore.js";
|
|
19
|
+
import { formatMoney } from "../../../utils/number.formatter.js";
|
|
19
20
|
/** A poll needs at least one real vote before a bar chart means anything. */
|
|
20
21
|
const MIN_VOTES = 1;
|
|
21
22
|
/**
|
|
@@ -121,7 +122,7 @@ export function buildCouponSavingsChart(coupons) {
|
|
|
121
122
|
return {
|
|
122
123
|
kind: "groupedBar",
|
|
123
124
|
title: "What each code saves",
|
|
124
|
-
categories: BASKET_SIZES.map((b) =>
|
|
125
|
+
categories: BASKET_SIZES.map((b) => formatMoney(b)),
|
|
125
126
|
series,
|
|
126
127
|
unit: "₹",
|
|
127
128
|
caption: captionParts.join(" "),
|
|
@@ -6,7 +6,9 @@ import { X } from "lucide-react";
|
|
|
6
6
|
import { useAuth } from "../../../react/contexts/SessionContext.js";
|
|
7
7
|
import { Div } from "../../../ui/components/Div.js";
|
|
8
8
|
import { IconButton, Row, Span, TextLink } from "@mohasinac/appkit/client";
|
|
9
|
-
|
|
9
|
+
import { STORAGE_KEYS } from "../../../constants/storage-keys.js";
|
|
10
|
+
// 🛑 Read at USE time — the namespace is set by the consumer's bootstrap, which
|
|
11
|
+
// runs after this module is evaluated.
|
|
10
12
|
const ONE_WEEK_MS = 7 * 24 * 60 * 60 * 1000;
|
|
11
13
|
export function hashBannerMessage(msg) {
|
|
12
14
|
let h = 0;
|
|
@@ -17,7 +19,7 @@ export function hashBannerMessage(msg) {
|
|
|
17
19
|
}
|
|
18
20
|
function isLocallyDismissed(message) {
|
|
19
21
|
try {
|
|
20
|
-
const raw = localStorage.getItem(
|
|
22
|
+
const raw = localStorage.getItem(STORAGE_KEYS.announcementDismissed);
|
|
21
23
|
if (!raw)
|
|
22
24
|
return false;
|
|
23
25
|
const { hash, at } = JSON.parse(raw);
|
|
@@ -32,7 +34,7 @@ function isLocallyDismissed(message) {
|
|
|
32
34
|
}
|
|
33
35
|
function saveLocalDismissal(message) {
|
|
34
36
|
try {
|
|
35
|
-
localStorage.setItem(
|
|
37
|
+
localStorage.setItem(STORAGE_KEYS.announcementDismissed, JSON.stringify({ hash: hashBannerMessage(message), at: Date.now() }));
|
|
36
38
|
}
|
|
37
39
|
catch (_err) {
|
|
38
40
|
void normalizeError(_err); /* quota exceeded — ignore */
|
|
@@ -65,7 +65,7 @@ export function FAQSection({ title, subtitle, items, tabs: tabsProp, showCategor
|
|
|
65
65
|
return items;
|
|
66
66
|
return items.filter((i) => (i.category ?? "general") === activeTab);
|
|
67
67
|
}, [items, showTabs, activeTab]);
|
|
68
|
-
return (_jsx(Section, { paddingX: "x-md-2xl", className: className, surface: "subtle", paddingY: "y-4xl", children: _jsxs(Div, { className: "w-full max-w-4xl mx-auto", children: [_jsxs(Div, { className: "text-left mb-10", children: [_jsx(Heading, { level: 2, variant: "none", gradient: "brand", className: "mb-3", mdSize: "4xl", size: "3xl", weight: "bold", children: title }), subtitle && (_jsx(Text, { className: `max-w-xl ${themed.textSecondary}`, size: "base", children: subtitle }))] }), showTabs && (_jsx(Tabs, { value: activeTab, onChange: setActiveTab, className: "mb-8", children: _jsxs(TabsList, { children: [_jsx(TabsTrigger, { value: "all", children: "All" }), derivedTabs.map((tab) => (_jsx(TabsTrigger, { value: tab.value, children: tab.label }, tab.value)))] }) })), _jsxs(Stack, { gap: "none", rounded: "xl", className: `${themed.bgPrimary} ${__O.hidden} border ${themed.border}`, children: [visibleItems.length === 0 && (_jsx(Text, { paddingY: "3xl", align: "start", className: themed.textSecondary, children: "No FAQs in this category yet." })), visibleItems.map((faq, index) => {
|
|
68
|
+
return (_jsx(Section, { paddingX: "x-md-2xl", className: className, surface: "subtle", paddingY: "y-4xl", children: _jsxs(Div, { className: "w-full max-w-4xl mx-auto", children: [_jsxs(Div, { className: "text-left mb-10", children: [_jsx(Heading, { level: 2, variant: "none", gradient: "brand", className: "mb-3", mdSize: "4xl", size: "3xl", weight: "bold", children: title }), subtitle && (_jsx(Text, { className: `max-w-xl ${themed.textSecondary}`, size: "base", children: subtitle }))] }), showTabs && (_jsx(Tabs, { value: activeTab, onChange: setActiveTab, className: "mb-8", children: _jsxs(TabsList, { wrap: true, children: [_jsx(TabsTrigger, { value: "all", children: "All" }), derivedTabs.map((tab) => (_jsx(TabsTrigger, { value: tab.value, children: tab.label }, tab.value)))] }) })), _jsxs(Stack, { gap: "none", rounded: "xl", className: `${themed.bgPrimary} ${__O.hidden} border ${themed.border}`, children: [visibleItems.length === 0 && (_jsx(Text, { paddingY: "3xl", align: "start", className: themed.textSecondary, children: "No FAQs in this category yet." })), visibleItems.map((faq, index) => {
|
|
69
69
|
const isOpen = openIds.has(faq.id);
|
|
70
70
|
return (_jsxs(Div, { className: `transition-colors duration-200 ${index < visibleItems.length - 1 ? "border-b border-[var(--appkit-color-border)]" : ""} ${isOpen ? "bg-[var(--appkit-color-surface-elevated)]" : ""}`, children: [_jsxs(Button, { variant: "ghost", rounded: "none", className: `w-full text-left px-[1.25rem] py-[1rem] ${flex.between} gap-[1rem] hover:${themed.bgTertiary} transition-colors`, onClick: () => toggle(faq.id), "aria-expanded": isOpen, children: [_jsx(Span, { size: "sm", weight: "medium", className: `flex-1 leading-snug ${isOpen ? "text-primary" : themed.textPrimary}`, children: faq.question }), _jsx(ChevronDown, { className: `w-4 h-4 flex-shrink-0 transition-transform duration-250 ${isOpen ? "rotate-180 text-primary" : themed.textSecondary}` })] }), _jsx(Div, { layout: "grid", className: `transition-all duration-300 ease-out ${isOpen ? "grid-rows-[1fr] opacity-100" : "grid-rows-[0fr] opacity-0"}`, children: _jsx(Div, { className: `${__O.hidden}`, children: _jsx(Div, { paddingY: "b-md-lg", padding: "x-md", children: isHtml(faq.answer) ? (_jsx(RichText, { html: faq.answer, proseClass: "prose prose-sm max-w-none dark:prose-invert" })) : (_jsx(Text, { className: `leading-relaxed ${themed.textSecondary}`, size: "sm", children: faq.answer })) }) }) })] }, faq.id));
|
|
71
71
|
})] }), viewMoreHref && (_jsx(Div, { className: "text-left mt-10", children: _jsxs(TextLink, { rounded: "full", paddingX: "xl", paddingY: "sm", href: viewMoreHref, layout: "inline-flex", align: "center", className: "gap-[0.5rem] bg-primary/10 text-primary hover:bg-primary/20 dark:bg-primary/15 transition-colors", size: "sm", weight: "medium", children: [viewMoreLabel, hasMore && moreCount > 0 && (_jsxs(Span, { color: "inverse", size: "xs", className: "bg-primary", rounded: "full", padding: "pill-xs", children: ["+", moreCount] }))] }) }))] }) }));
|
|
@@ -22,6 +22,7 @@ import { gateCarouselSlides } from "../lib/slide-gating.js";
|
|
|
22
22
|
import { gateHomepageSections } from "../lib/section-gating.js";
|
|
23
23
|
import { closedSurfaceContext, isClosedHref } from "../../../_internal/shared/listing-types/route-gating.js";
|
|
24
24
|
import { getSurfaceCounts } from "../../../_internal/server/features/products/surface-counts.js";
|
|
25
|
+
import { ROUTES } from "../../../next/routing/route-map.js";
|
|
25
26
|
import { SECTION_COPY } from "../constants/section-copy.js";
|
|
26
27
|
import { homepageSectionsRepository } from "../repository/homepage-sections.repository.js";
|
|
27
28
|
import { isListingTypeEnabled, isCategoryTypeEnabled, isBrowsePageEnabled, } from "../../../_internal/shared/listing-types/feature-flags.js";
|
|
@@ -75,6 +76,36 @@ const HOMEPAGE_SECTION_BROWSE_PAGE = {
|
|
|
75
76
|
brands: "brands",
|
|
76
77
|
stores: "stores",
|
|
77
78
|
};
|
|
79
|
+
/**
|
|
80
|
+
* The FOURTH axis — a section that funnels into a route closed by
|
|
81
|
+
* `closedPaths` or `disabledRoutes`, neither of which the three maps above
|
|
82
|
+
* can see.
|
|
83
|
+
*
|
|
84
|
+
* 🛑 THIS ONE EXISTS BECAUSE A SEED LOAD RE-ENABLED A SECTION. The blog rail
|
|
85
|
+
* was switched off per-document, `appkit-seed load` writes `set(merge: true)`
|
|
86
|
+
* over `enabled`, and the seed still said `true` — so the rail came back and
|
|
87
|
+
* the homepage shipped FIVE dead links (`/blog` plus four post URLs) into a
|
|
88
|
+
* section closed via `disabledRoutes`.
|
|
89
|
+
*
|
|
90
|
+
* A per-document `enabled: false` is a value a reseed can overwrite. A gate
|
|
91
|
+
* keyed on whether the destination is OPEN cannot be: the section stays dark
|
|
92
|
+
* for exactly as long as the route is shut, however the flag got flipped.
|
|
93
|
+
* That is the difference between fixing this instance and fixing the class.
|
|
94
|
+
*/
|
|
95
|
+
/*
|
|
96
|
+
* 🛑 `coupons` is NOT here. Its "View all offers" button points at
|
|
97
|
+
* `/promotions`, which is a real offers page with a live coupon behind it —
|
|
98
|
+
* so the fix was to REOPEN that route, not to hide a working rail. Closing a
|
|
99
|
+
* page and then hiding everything that links to it is the wrong direction
|
|
100
|
+
* when the page is one a shop wants.
|
|
101
|
+
*/
|
|
102
|
+
const HOMEPAGE_SECTION_ROUTE = {
|
|
103
|
+
"blog-articles": String(ROUTES.BLOG.LIST),
|
|
104
|
+
events: String(ROUTES.PUBLIC.EVENTS),
|
|
105
|
+
"event-raffles": String(ROUTES.PUBLIC.EVENTS),
|
|
106
|
+
reviews: String(ROUTES.PUBLIC.REVIEWS),
|
|
107
|
+
"featured-bundles": String(ROUTES.PUBLIC.BUNDLES),
|
|
108
|
+
};
|
|
78
109
|
import { getFeaturedProducts, getFeaturedAuctions, getFeaturedPreOrders } from "../../products/actions/product-actions.js";
|
|
79
110
|
import { listTopLevelCategories, listBrandCategories } from "../../categories/actions/category-actions.js";
|
|
80
111
|
import { listFeaturedBundles } from "../../../_internal/server/features/bundles/data.js";
|
|
@@ -189,6 +220,9 @@ export async function MarketplaceHomepageView({ adSlots, newsletterFormSlot, onB
|
|
|
189
220
|
const gatedBrowse = HOMEPAGE_SECTION_BROWSE_PAGE[type];
|
|
190
221
|
if (gatedBrowse && !isBrowsePageEnabled(gatedBrowse, siteSettings))
|
|
191
222
|
return false;
|
|
223
|
+
const gatedRoute = HOMEPAGE_SECTION_ROUTE[type];
|
|
224
|
+
if (gatedRoute && isClosedHref(gatedRoute, closedSurfaces))
|
|
225
|
+
return false;
|
|
192
226
|
return true;
|
|
193
227
|
});
|
|
194
228
|
/*
|
|
@@ -34,6 +34,8 @@ export interface WelcomeSectionProps {
|
|
|
34
34
|
heroImages?: readonly string[];
|
|
35
35
|
isLoading?: boolean;
|
|
36
36
|
brandLogoText?: string;
|
|
37
|
+
/** TLD badge for the wordmark, e.g. "in". Omitted renders no badge. */
|
|
38
|
+
brandTld?: string;
|
|
37
39
|
/**
|
|
38
40
|
* Wordmark caption under the hero logo, e.g. "Let It Rip India".
|
|
39
41
|
*
|
|
@@ -44,4 +46,4 @@ export interface WelcomeSectionProps {
|
|
|
44
46
|
brandCaption?: string;
|
|
45
47
|
className?: string;
|
|
46
48
|
}
|
|
47
|
-
export declare function WelcomeSection({ title, subtitle, pillLabel, showCTA, ctaLabel, ctaHref, onCtaClick, secondaryCtaLabel, secondaryCtaHref, onSecondaryCtaClick, trustChips, heroImages, isLoading, brandLogoText, brandCaption, className, }: WelcomeSectionProps): React.JSX.Element;
|
|
49
|
+
export declare function WelcomeSection({ title, subtitle, pillLabel, showCTA, ctaLabel, ctaHref, onCtaClick, secondaryCtaLabel, secondaryCtaHref, onSecondaryCtaClick, trustChips, heroImages, isLoading, brandLogoText, brandTld, brandCaption, className, }: WelcomeSectionProps): React.JSX.Element;
|
|
@@ -12,11 +12,11 @@ import { SiteLogo } from "../../../ui/components/SiteLogo.js";
|
|
|
12
12
|
import { SiteMark } from "../../../ui/components/SiteMark.js";
|
|
13
13
|
import { TextLink } from "../../../ui/components/TextLink.js";
|
|
14
14
|
// --- Component ---------------------------------------------------------------
|
|
15
|
-
export function WelcomeSection({ title, subtitle, pillLabel, showCTA = true, ctaLabel = "Shop now", ctaHref, onCtaClick, secondaryCtaLabel = "Browse products", secondaryCtaHref, onSecondaryCtaClick, trustChips = [], heroImages, isLoading = false, brandLogoText = "", brandCaption, className = "", }) {
|
|
15
|
+
export function WelcomeSection({ title, subtitle, pillLabel, showCTA = true, ctaLabel = "Shop now", ctaHref, onCtaClick, secondaryCtaLabel = "Browse products", secondaryCtaHref, onSecondaryCtaClick, trustChips = [], heroImages, isLoading = false, brandLogoText = "", brandTld, brandCaption, className = "", }) {
|
|
16
16
|
const themed = { textSecondary: THEMED_TEXT_SECONDARY };
|
|
17
17
|
const flex = { center: FLEX_CENTER };
|
|
18
18
|
if (isLoading) {
|
|
19
19
|
return (_jsx(Section, { className: `relative overflow-hidden pt-[0px] pb-[var(--appkit-space-16)] md:pt-[0px] md:pb-[6rem] ${className}`, paddingX: "x-md", children: _jsxs(Div, { className: "animate-pulse max-w-4xl mx-auto text-left", children: [_jsx(Div, { className: "h-6 w-52 mb-6", surface: "subtle", rounded: "full" }), _jsx(Div, { className: "h-20 mb-4 max-w-2xl", surface: "subtle", rounded: "lg" }), _jsx(Div, { className: "h-6 mb-8 max-w-lg", surface: "subtle", rounded: "lg" }), _jsxs(Row, { justify: "start", gap: "md", children: [_jsx(Div, { className: "h-12 w-36", surface: "subtle", rounded: "xl" }), _jsx(Div, { className: "h-12 w-36", surface: "subtle", rounded: "xl" })] })] }) }));
|
|
20
20
|
}
|
|
21
|
-
return (_jsxs(Section, { className: `relative overflow-hidden pt-[0px] pb-[var(--appkit-space-20)] md:pt-[0px] md:pb-[7rem] ${className}`, paddingX: "x-md", children: [_jsx(Div, { className: "pointer-events-none absolute -top-32 -left-32 w-[28rem] h-[28rem] bg-primary/10 blur-3xl", rounded: "full", "aria-hidden": "true" }), _jsx(Div, { className: "pointer-events-none absolute -bottom-40 -right-40 w-[36rem] h-[36rem] bg-secondary/10 dark:bg-secondary/15 blur-3xl", rounded: "full", "aria-hidden": "true" }), _jsx(Div, { className: "relative z-10 max-w-7xl mx-auto", children: _jsxs(Grid, { align: "center", gap: "2xl", className: "grid-cols-1 lg:grid-cols-2 xl:grid-cols-2 2xl:grid-cols-2", children: [_jsxs(Div, { className: "text-left", children: [pillLabel && (_jsx(Div, { children: _jsxs(Span, { layout: "inline-flex", gap: "md", size: "xs", weight: "medium", className: /* audit-variant-ok: colour+alpha border (primary-500/30); the `border` variant takes a token name, not an rgba step */ "border border-primary-500/30 bg-primary-500/10 px-[1.25rem] py-[0.375rem] tracking-[0.2em] text-primary-700 dark:text-primary-400 backdrop-blur-sm", rounded: "full", transform: "uppercase", children: [_jsx(Span, { className: "w-1.5 h-1.5 bg-primary-500 inline-block", rounded: "full", "aria-hidden": "true" }), pillLabel, _jsx(Span, { className: "w-1.5 h-1.5 bg-primary-500 inline-block", rounded: "full", "aria-hidden": "true" })] }) })), _jsx(Heading, { level: 1, variant: "none", gradient: "brand-tri", className: "mt-4 font-display leading-[1.1] tracking-tight break-words", size: "5xl", mdSize: "6xl", lgSize: "7xl", xlSize: "7xl", children: title }), subtitle && (_jsx(Text, { className: `mt-4 ${themed.textSecondary} max-w-xl leading-relaxed`, size: "xl", children: subtitle })), showCTA && (_jsxs(Row, { wrap: true, gap: "md", className: "mt-8", justify: "start", children: [ctaHref ? (_jsx(TextLink, { rounded: "xl", href: ctaHref, layout: "inline-flex", align: "center", justify: "center", className: "px-[2rem] py-[0.875rem] !bg-primary hover:!bg-primary-600 text-white dark:!bg-primary dark:hover:!bg-primary-600 dark:text-white btn-glow transition-all hover:scale-[1.02]", size: "base", weight: "bold", children: ctaLabel })) : (_jsx(Button, { variant: "primary", size: "lg", onClick: onCtaClick, children: ctaLabel })), secondaryCtaHref ? (_jsx(TextLink, { rounded: "xl", href: secondaryCtaHref, layout: "inline-flex", align: "center", justify: "center", className: "border-2 border-cobalt/40 dark:border-cobalt-400/40 px-[2rem] py-[0.875rem] text-cobalt-700 dark:text-cobalt-300 hover:bg-primary-surface transition-all hover:scale-[1.02]", size: "base", weight: "semibold", children: secondaryCtaLabel })) : (_jsx(Button, { variant: "outline", size: "lg", onClick: onSecondaryCtaClick, children: secondaryCtaLabel }))] })), trustChips.length > 0 && (_jsx(Row, { wrap: true, gap: "sm", className: "mt-6", justify: "start", children: trustChips.map((chip) => (_jsxs(Row, { align: "center", gap: "xs", surface: "default", border: "subtle", rounded: "full", shadow: "sm", className: "px-[0.875rem] py-[0.375rem]", children: [_jsx(Icon, { name: chip.iconName, size: "sm", tone: "success" }), _jsx(Span, { size: "xs", weight: "medium", color: "muted", children: chip.label })] }, chip.key))) }))] }), _jsx(Div, { className: "hidden lg:block", children: _jsx(ImageMosaic, { size: "hero", images: heroImages ?? [], alt: `${brandLogoText || "Marketplace"} collection highlights`, leadSlot: _jsxs(Div, { className: "absolute inset-0 bg-[image:var(--appkit-gradient-section-mesh)]", children: [_jsx(Div, { className: "absolute inset-0 bg-[image:var(--appkit-gradient-glass)]" }), _jsxs(Stack, { centered: true, gap: "sm", padding: "xl", className: `absolute inset-0 ${flex.center}`, children: [_jsx(SiteMark, { title: brandLogoText ||
|
|
21
|
+
return (_jsxs(Section, { className: `relative overflow-hidden pt-[0px] pb-[var(--appkit-space-20)] md:pt-[0px] md:pb-[7rem] ${className}`, paddingX: "x-md", children: [_jsx(Div, { className: "pointer-events-none absolute -top-32 -left-32 w-[28rem] h-[28rem] bg-primary/10 blur-3xl", rounded: "full", "aria-hidden": "true" }), _jsx(Div, { className: "pointer-events-none absolute -bottom-40 -right-40 w-[36rem] h-[36rem] bg-secondary/10 dark:bg-secondary/15 blur-3xl", rounded: "full", "aria-hidden": "true" }), _jsx(Div, { className: "relative z-10 max-w-7xl mx-auto", children: _jsxs(Grid, { align: "center", gap: "2xl", className: "grid-cols-1 lg:grid-cols-2 xl:grid-cols-2 2xl:grid-cols-2", children: [_jsxs(Div, { className: "text-left", children: [pillLabel && (_jsx(Div, { children: _jsxs(Span, { layout: "inline-flex", gap: "md", size: "xs", weight: "medium", className: /* audit-variant-ok: colour+alpha border (primary-500/30); the `border` variant takes a token name, not an rgba step */ "border border-primary-500/30 bg-primary-500/10 px-[1.25rem] py-[0.375rem] tracking-[0.2em] text-primary-700 dark:text-primary-400 backdrop-blur-sm", rounded: "full", transform: "uppercase", children: [_jsx(Span, { className: "w-1.5 h-1.5 bg-primary-500 inline-block", rounded: "full", "aria-hidden": "true" }), pillLabel, _jsx(Span, { className: "w-1.5 h-1.5 bg-primary-500 inline-block", rounded: "full", "aria-hidden": "true" })] }) })), _jsx(Heading, { level: 1, variant: "none", gradient: "brand-tri", className: "mt-4 font-display leading-[1.1] tracking-tight break-words", size: "5xl", mdSize: "6xl", lgSize: "7xl", xlSize: "7xl", children: title }), subtitle && (_jsx(Text, { className: `mt-4 ${themed.textSecondary} max-w-xl leading-relaxed`, size: "xl", children: subtitle })), showCTA && (_jsxs(Row, { wrap: true, gap: "md", className: "mt-8", justify: "start", children: [ctaHref ? (_jsx(TextLink, { rounded: "xl", href: ctaHref, layout: "inline-flex", align: "center", justify: "center", className: "px-[2rem] py-[0.875rem] !bg-primary hover:!bg-primary-600 text-white dark:!bg-primary dark:hover:!bg-primary-600 dark:text-white btn-glow transition-all hover:scale-[1.02]", size: "base", weight: "bold", children: ctaLabel })) : (_jsx(Button, { variant: "primary", size: "lg", onClick: onCtaClick, children: ctaLabel })), secondaryCtaHref ? (_jsx(TextLink, { rounded: "xl", href: secondaryCtaHref, layout: "inline-flex", align: "center", justify: "center", className: "border-2 border-cobalt/40 dark:border-cobalt-400/40 px-[2rem] py-[0.875rem] text-cobalt-700 dark:text-cobalt-300 hover:bg-primary-surface transition-all hover:scale-[1.02]", size: "base", weight: "semibold", children: secondaryCtaLabel })) : (_jsx(Button, { variant: "outline", size: "lg", onClick: onSecondaryCtaClick, children: secondaryCtaLabel }))] })), trustChips.length > 0 && (_jsx(Row, { wrap: true, gap: "sm", className: "mt-6", justify: "start", children: trustChips.map((chip) => (_jsxs(Row, { align: "center", gap: "xs", surface: "default", border: "subtle", rounded: "full", shadow: "sm", className: "px-[0.875rem] py-[0.375rem]", children: [_jsx(Icon, { name: chip.iconName, size: "sm", tone: "success" }), _jsx(Span, { size: "xs", weight: "medium", color: "muted", children: chip.label })] }, chip.key))) }))] }), _jsx(Div, { className: "hidden lg:block", children: _jsx(ImageMosaic, { size: "hero", images: heroImages ?? [], alt: `${brandLogoText || "Marketplace"} collection highlights`, leadSlot: _jsxs(Div, { className: "absolute inset-0 bg-[image:var(--appkit-gradient-section-mesh)]", children: [_jsx(Div, { className: "absolute inset-0 bg-[image:var(--appkit-gradient-glass)]" }), _jsxs(Stack, { centered: true, gap: "sm", padding: "xl", className: `absolute inset-0 ${flex.center}`, children: [_jsx(SiteMark, { title: brandLogoText || undefined, size: "xl" }), _jsx(SiteLogo, { title: brandLogoText || undefined, wordmark: brandLogoText || undefined, tld: brandTld, size: "xl" }), brandCaption && (_jsx(Text, { align: "center", size: "sm", weight: "semibold", transform: "uppercase", className: "tracking-widest text-primary-700 dark:text-primary-300", children: brandCaption }))] })] }) }) })] }) })] }));
|
|
22
22
|
}
|
|
@@ -48,6 +48,14 @@ export interface HomepageBrand {
|
|
|
48
48
|
name?: string;
|
|
49
49
|
/** Short mark shown in the hero panel, e.g. "LT". */
|
|
50
50
|
shortName?: string;
|
|
51
|
+
/**
|
|
52
|
+
* TLD badge drawn after the hero wordmark, e.g. `"in"`.
|
|
53
|
+
*
|
|
54
|
+
* Consumer-supplied for the same reason as `caption` below: a library must
|
|
55
|
+
* not assume its consumer's domain. It was a hardcoded `.in` inside
|
|
56
|
+
* `SiteLogo` until A5.
|
|
57
|
+
*/
|
|
58
|
+
tld?: string;
|
|
51
59
|
/**
|
|
52
60
|
* Caption under the hero brand panel's logo, e.g. "Let It Rip India".
|
|
53
61
|
*
|
|
@@ -112,7 +112,7 @@ const SECTION_REGISTRY = {
|
|
|
112
112
|
return (_jsx(WelcomeSection, { heroImages: heroImages, brandCaption: brand?.heroCaption, title: cleanTitle(cfg?.h1) || SECTION_TITLE.welcome,
|
|
113
113
|
// A real `subtitle` wins; `description` is the legacy source and is
|
|
114
114
|
// still parsed for documents that only carry it.
|
|
115
|
-
subtitle: cfg?.subtitle?.trim() || parseWelcomeDescription(cfg?.description), pillLabel: cfg?.pillLabel || SECTION_COPY.welcomePill, showCTA: cfg?.showCTA ?? true, ctaLabel: cfg?.ctaText || SECTION_COPY.welcomePrimaryCta, ctaHref: cfg?.ctaLink || ROUTES.PUBLIC.PRODUCTS, secondaryCtaLabel: cfg?.secondaryCtaText || SECTION_COPY.welcomeSecondaryCta, secondaryCtaHref: cfg?.secondaryCtaLink || ROUTES.PUBLIC.PRODUCTS, trustChips: [...WELCOME_TRUST_CHIPS], brandLogoText: brand?.shortName }));
|
|
115
|
+
subtitle: cfg?.subtitle?.trim() || parseWelcomeDescription(cfg?.description), pillLabel: cfg?.pillLabel || SECTION_COPY.welcomePill, showCTA: cfg?.showCTA ?? true, ctaLabel: cfg?.ctaText || SECTION_COPY.welcomePrimaryCta, ctaHref: cfg?.ctaLink || ROUTES.PUBLIC.PRODUCTS, secondaryCtaLabel: cfg?.secondaryCtaText || SECTION_COPY.welcomeSecondaryCta, secondaryCtaHref: cfg?.secondaryCtaLink || ROUTES.PUBLIC.PRODUCTS, trustChips: [...WELCOME_TRUST_CHIPS], brandLogoText: brand?.shortName, brandTld: brand?.tld }));
|
|
116
116
|
},
|
|
117
117
|
},
|
|
118
118
|
categories: {
|
|
@@ -63,6 +63,8 @@ export interface AppLayoutShellProps {
|
|
|
63
63
|
hiddenNavItems?: string[];
|
|
64
64
|
user?: BottomNavbarUser | null;
|
|
65
65
|
brandName: string;
|
|
66
|
+
/** TLD badge for the wordmark, forwarded to TitleBarLayout (A5). */
|
|
67
|
+
brandTld?: string;
|
|
66
68
|
brandShortName?: string;
|
|
67
69
|
/** Admin-uploaded site logo URL. Falls back to the SVG wordmark when empty. */
|
|
68
70
|
siteLogoUrl?: string;
|
|
@@ -178,4 +180,4 @@ export interface AppLayoutShellProps {
|
|
|
178
180
|
};
|
|
179
181
|
};
|
|
180
182
|
}
|
|
181
|
-
export declare function AppLayoutShell({ children, navItems, sidebarItems, sidebarSections, renderSidebarItemAction, sidebarPrimaryActions, sidebarTitle, hiddenNavItems, user, brandName, brandShortName, siteLogoUrl, logoHref, promotionsHref, cartHref, wishlistHref, messagesHref, unreadMessageCount, userId, profileHref, loginHref, registerHref, homeHref, shopHref, footer, showFooter, searchSlot, searchSlotRenderer, titleBarNavSlot, titleBarNotificationSlot, titleBarDevSlot, titleBarPromoStripText, showThemeToggle, suppressDashboardNav, hideSidebarToggle, onLogout, adminHref, storeHref, sellerHref, userOrdersHref, userWishlistHref, userSettingsHref, userReviewsHref, sidebarLocaleSlot, showThemeToggleInSidebar, sidebarProfileLabels, eventBannerSlot, showBottomNav, contentClassName, lightBackground, darkBackground, }: AppLayoutShellProps): React.JSX.Element;
|
|
183
|
+
export declare function AppLayoutShell({ children, navItems, sidebarItems, sidebarSections, renderSidebarItemAction, sidebarPrimaryActions, sidebarTitle, hiddenNavItems, user, brandName, brandTld, brandShortName, siteLogoUrl, logoHref, promotionsHref, cartHref, wishlistHref, messagesHref, unreadMessageCount, userId, profileHref, loginHref, registerHref, homeHref, shopHref, footer, showFooter, searchSlot, searchSlotRenderer, titleBarNavSlot, titleBarNotificationSlot, titleBarDevSlot, titleBarPromoStripText, showThemeToggle, suppressDashboardNav, hideSidebarToggle, onLogout, adminHref, storeHref, sellerHref, userOrdersHref, userWishlistHref, userSettingsHref, userReviewsHref, sidebarLocaleSlot, showThemeToggleInSidebar, sidebarProfileLabels, eventBannerSlot, showBottomNav, contentClassName, lightBackground, darkBackground, }: AppLayoutShellProps): React.JSX.Element;
|