@mohasinac/appkit 4.70.0 → 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.
Files changed (169) hide show
  1. package/PROVIDERS.md +190 -0
  2. package/dist/.build-stamp.json +3 -3
  3. package/dist/_internal/client/features/lottery/LotteryAdminSlotView.js +2 -1
  4. package/dist/_internal/server/features/checkout/actions.js +43 -8
  5. package/dist/_internal/server/features/orders/actions.js +2 -1
  6. package/dist/_internal/server/features/orders/adapters.js +18 -0
  7. package/dist/_internal/server/features/orders/balance.js +2 -1
  8. package/dist/_internal/server/features/orders/cancellation-refund.d.ts +0 -89
  9. package/dist/_internal/server/features/orders/cancellation-refund.js +3 -2
  10. package/dist/_internal/server/features/payouts/actions.js +2 -1
  11. package/dist/_internal/server/features/points/award.js +3 -2
  12. package/dist/_internal/server/features/protection-claims/actions.js +2 -1
  13. package/dist/_internal/server/features/refunds/actions.js +3 -2
  14. package/dist/_internal/server/features/shipping/courier-block.js +5 -2
  15. package/dist/_internal/server/features/shipping/reconcile.js +3 -2
  16. package/dist/_internal/server/features/shipping/reimbursement.js +3 -2
  17. package/dist/_internal/server/features/stores/adapters.d.ts +1 -1
  18. package/dist/_internal/server/features/stores/adapters.js +12 -2
  19. package/dist/_internal/server/jobs/core/dailyStatusDigest.js +7 -3
  20. package/dist/_internal/server/jobs/core/emiInstallmentReminder.js +2 -1
  21. package/dist/_internal/server/jobs/core/obligationSweep.js +5 -4
  22. package/dist/_internal/server/jobs/core/onScamReportCreate.js +2 -1
  23. package/dist/_internal/server/jobs/core/paymentReconcile.js +5 -4
  24. package/dist/_internal/shared/checkout/rules/auction.rule.js +25 -7
  25. package/dist/_internal/shared/checkout/rules/prize-draw.rule.js +26 -8
  26. package/dist/_internal/shared/config/schema.d.ts +15 -0
  27. package/dist/_internal/shared/content/published-values.d.ts +2 -2
  28. package/dist/_internal/shared/content/published-values.js +31 -12
  29. package/dist/_internal/shared/features/auctions/errors.js +2 -1
  30. package/dist/_internal/shared/features/orders/refund-copy.js +3 -2
  31. package/dist/_internal/shared/features/promotions/errors.js +2 -1
  32. package/dist/constants/field-names.d.ts +23 -0
  33. package/dist/constants/field-names.js +23 -0
  34. package/dist/constants/storage-keys.d.ts +90 -0
  35. package/dist/constants/storage-keys.js +119 -0
  36. package/dist/contracts/index.d.ts +1 -1
  37. package/dist/contracts/payment.d.ts +42 -0
  38. package/dist/contracts/registry.d.ts +8 -0
  39. package/dist/contracts/tax.d.ts +118 -0
  40. package/dist/contracts/tax.js +3 -0
  41. package/dist/core/brand-resolver.d.ts +92 -0
  42. package/dist/core/brand-resolver.js +120 -0
  43. package/dist/core/brand-text.d.ts +15 -0
  44. package/dist/core/brand-text.js +73 -0
  45. package/dist/core/hooks/useNewsletterSubscribe.d.ts +30 -0
  46. package/dist/core/hooks/useNewsletterSubscribe.js +121 -0
  47. package/dist/features/about/components/DeveloperView.js +2 -1
  48. package/dist/features/account/components/BuyerAccountGuideView.js +2 -1
  49. package/dist/features/account/components/BuyerAuctionsGuideView.js +4 -3
  50. package/dist/features/account/components/BuyerOrdersGuideView.js +6 -5
  51. package/dist/features/account/components/BuyerShoppingGuideView.js +3 -2
  52. package/dist/features/account/components/UserOffersPanel.js +2 -1
  53. package/dist/features/account/hooks/useCollapsedSections.js +5 -3
  54. package/dist/features/account/schemas/index.js +1 -1
  55. package/dist/features/admin/actions/notification-actions.js +9 -2
  56. package/dist/features/admin/components/AdminAnalyticsGuideView.js +2 -1
  57. package/dist/features/admin/components/AdminBundlesView.js +2 -1
  58. package/dist/features/admin/components/AdminCatalogGuideView.js +2 -1
  59. package/dist/features/admin/components/AdminContentGuideView.js +2 -1
  60. package/dist/features/admin/components/AdminFaqEditorView.js +2 -1
  61. package/dist/features/admin/components/AdminGatewayEventsView.js +2 -1
  62. package/dist/features/admin/components/AdminGuideHubView.js +2 -1
  63. package/dist/features/admin/components/AdminMoneyCasesView.js +2 -1
  64. package/dist/features/admin/components/AdminOrdersGuideView.js +2 -1
  65. package/dist/features/admin/components/AdminProductsView.js +3 -2
  66. package/dist/features/admin/components/AdminSectionsView.js +3 -2
  67. package/dist/features/admin/components/AdminSiteSettingsView.js +8 -7
  68. package/dist/features/admin/components/AdminStoresGuideView.js +2 -1
  69. package/dist/features/admin/components/AdminSupportTicketDetailView.js +2 -1
  70. package/dist/features/admin/components/AdminTeamGuideView.js +2 -1
  71. package/dist/features/admin/components/AdminTrustGuideView.js +3 -2
  72. package/dist/features/admin/components/AdminUsersGuideView.js +2 -1
  73. package/dist/features/admin/components/sections/adminSectionsTypes.js +1 -1
  74. package/dist/features/auth/actions/user-slug.js +21 -2
  75. package/dist/features/auth/hooks/useAuth.js +11 -4
  76. package/dist/features/cart/utils/pending-ops.js +16 -3
  77. package/dist/features/catalogue/utils/product-from-item.d.ts +18 -2
  78. package/dist/features/catalogue/utils/product-from-item.js +23 -4
  79. package/dist/features/categories/components/BundleItemsPicker.js +2 -1
  80. package/dist/features/categories/components/CategoryDetailPageView.js +2 -1
  81. package/dist/features/categories/components/ContextualTrail.d.ts +9 -2
  82. package/dist/features/categories/components/ContextualTrail.js +11 -3
  83. package/dist/features/categories/components/LastCategoryBeacon.js +2 -2
  84. package/dist/features/checkout/actions/checkout-value-otp-actions.js +9 -1
  85. package/dist/features/contact/email.js +11 -12
  86. package/dist/features/email/notification-templates.js +4 -5
  87. package/dist/features/email/render-static-markup.d.ts +34 -0
  88. package/dist/features/email/render-static-markup.js +4 -0
  89. package/dist/features/events/utils/event-charts.js +2 -1
  90. package/dist/features/homepage/components/AnnouncementBar.js +5 -3
  91. package/dist/features/homepage/components/WelcomeSection.d.ts +3 -1
  92. package/dist/features/homepage/components/WelcomeSection.js +2 -2
  93. package/dist/features/homepage/lib/section-renderer.d.ts +8 -0
  94. package/dist/features/homepage/lib/section-renderer.js +1 -1
  95. package/dist/features/layout/AppLayoutShell.d.ts +3 -1
  96. package/dist/features/layout/AppLayoutShell.js +2 -2
  97. package/dist/features/layout/TitleBarLayout.d.ts +8 -1
  98. package/dist/features/layout/TitleBarLayout.js +2 -2
  99. package/dist/features/media/MediaVideo.js +2 -1
  100. package/dist/features/orders/actions/refund-actions.js +3 -2
  101. package/dist/features/orders/components/RefundRequestView.js +15 -2
  102. package/dist/features/orders/repository/orders.repository.js +2 -1
  103. package/dist/features/orders/types/index.d.ts +14 -0
  104. package/dist/features/orders/utils/payment-proof-message.js +5 -2
  105. package/dist/features/price-index/components/PriceIndexToolView.js +6 -5
  106. package/dist/features/pricing/components/AdminPricingView.js +2 -1
  107. package/dist/features/print/components/labels.js +2 -1
  108. package/dist/features/products/components/CityCollectiblesView.js +2 -1
  109. package/dist/features/products/components/MakeOfferButton.js +2 -1
  110. package/dist/features/products/components/PrizeDrawCollage.js +3 -2
  111. package/dist/features/products/components/PrizeRevealModal.js +2 -1
  112. package/dist/features/products/repository/products.repository.js +2 -1
  113. package/dist/features/promotions/components/CouponCard.js +3 -2
  114. package/dist/features/promotions/components/CouponHelpDetails.js +2 -1
  115. package/dist/features/promotions/constants/coupon-help.d.ts +1 -1
  116. package/dist/features/promotions/constants/coupon-help.js +8 -1
  117. package/dist/features/scams/components/ScamAwarenessModal.js +2 -1
  118. package/dist/features/scams/components/ScamProfileView.js +5 -3
  119. package/dist/features/scams/components/ScamRegistryView.js +2 -1
  120. package/dist/features/scams/constants/scam-types.d.ts +13 -1
  121. package/dist/features/scams/constants/scam-types.js +67 -23
  122. package/dist/features/scams/schemas/firestore.js +1 -1
  123. package/dist/features/search/components/Search.js +5 -1
  124. package/dist/features/seller/actions/offer-actions.js +47 -9
  125. package/dist/features/seller/components/DispatchForm.js +3 -2
  126. package/dist/features/seller/components/PrintCenterView.js +4 -2
  127. package/dist/features/seller/components/SellerBundlesView.js +2 -1
  128. package/dist/features/seller/components/SellerPayoutSettingsView.js +3 -2
  129. package/dist/features/seller/components/SellerProductShell.js +3 -2
  130. package/dist/features/seller/components/SellerProductsView.js +4 -3
  131. package/dist/features/store-extensions/repository/store-extensions.repositories.d.ts +30 -4
  132. package/dist/features/store-extensions/repository/store-extensions.repositories.js +46 -7
  133. package/dist/features/store-extensions/schemas/firestore.d.ts +22 -3
  134. package/dist/features/store-extensions/schemas/firestore.js +20 -0
  135. package/dist/features/stores/components/StoreCapabilitiesGuideView.js +7 -6
  136. package/dist/features/stores/components/StoreFinanceGuideView.js +6 -5
  137. package/dist/features/stores/components/StoreGuideHubView.js +2 -1
  138. package/dist/features/stores/components/StoreListingsGuideView.js +2 -1
  139. package/dist/features/stores/components/StoreOrdersGuideView.js +5 -4
  140. package/dist/features/stores/components/StoreSettingsGuideView.js +2 -1
  141. package/dist/features/support/schemas/index.d.ts +2 -2
  142. package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.d.ts +1 -1
  143. package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +3 -2
  144. package/dist/features/whatsapp-bot/helpers/whatsapp.js +5 -2
  145. package/dist/index.d.ts +0 -1
  146. package/dist/index.js +0 -2
  147. package/dist/providers/payment-manual/index.d.ts +11 -0
  148. package/dist/providers/payment-manual/index.js +13 -0
  149. package/dist/providers/payment-phonepe/index.d.ts +23 -7
  150. package/dist/providers/payment-phonepe/index.js +15 -0
  151. package/dist/providers/payment-razorpay/index.d.ts +13 -0
  152. package/dist/providers/payment-razorpay/index.js +21 -1
  153. package/dist/providers/tax-india-gst/index.d.ts +45 -0
  154. package/dist/providers/tax-india-gst/index.js +78 -0
  155. package/dist/react/hooks/useScrollToTop.d.ts +5 -0
  156. package/dist/react/hooks/useScrollToTop.js +70 -0
  157. package/dist/schemas/registry.d.ts +2 -2
  158. package/dist/seo/json-ld.js +25 -4
  159. package/dist/ui/components/SiteLogo.d.ts +20 -2
  160. package/dist/ui/components/SiteLogo.js +28 -6
  161. package/dist/ui/components/SiteMark.d.ts +1 -1
  162. package/dist/ui/components/SiteMark.js +21 -6
  163. package/dist/utils/date.formatter.d.ts +20 -0
  164. package/dist/utils/date.formatter.js +25 -0
  165. package/dist/utils/number.formatter.d.ts +47 -0
  166. package/dist/utils/number.formatter.js +79 -1
  167. package/dist/validation/phone.validator.d.ts +0 -1
  168. package/dist/validation/phone.validator.js +17 -3
  169. package/package.json +17 -3
@@ -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 !== "letitrip_auth_close")
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("letitrip_oauth_redirect", url);
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 !== "letitrip_auth_close")
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("letitrip_oauth_redirect", url);
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
- const APP_ID = () => typeof process !== "undefined" && process.env.NEXT_PUBLIC_APP_ID
10
- ? process.env.NEXT_PUBLIC_APP_ID
11
- : "letitrip";
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
- /** The platform's own consignment store — used whenever a catalogue item is
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
- /** The platform's own consignment store — used whenever a catalogue item is
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
- export const CONSIGNMENT_STORE_ID = "store-letitrip-official";
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 || "Listed via LetItRip's personal catalogue.",
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 \u20B9", r.price.toLocaleString("en-IN")] })] })] })] }, r.id));
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 LetItRip` }) }) })), _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
+ 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
- /** The `sessionStorage` key. Read here, written by `<LastCategoryBeacon>`. */
45
- export declare const LAST_CATEGORY_KEY = "letitrip:last-category";
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
- /** The `sessionStorage` key. Read here, written by `<LastCategoryBeacon>`. */
48
- export const LAST_CATEGORY_KEY = "letitrip:last-category";
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(LAST_CATEGORY_KEY, { type: "session" });
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 { LAST_CATEGORY_KEY } from "./ContextualTrail.js";
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(LAST_CATEGORY_KEY, categoryId, { type: "session" });
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
- const siteName = process.env.NEXT_PUBLIC_SITE_NAME || "LetItRip";
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
- // Lazy-load `renderToStaticMarkup` via `require()` to avoid Next.js's
4
- // Server-Component static-trace error. Even with the `server-only`
5
- // directive above, a top-level `import { renderToStaticMarkup } from
6
- // "react-dom/server"` is flagged by Next.js when any module in the
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 process.env.NEXT_PUBLIC_SITE_NAME?.trim() || "App";
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 — this MUST stay a lazy require.
35
- function renderToStaticMarkup(el) {
36
- const m = require("react-dom/server");
37
- return m.renderToStaticMarkup(el);
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;
@@ -0,0 +1,4 @@
1
+ export function renderStaticMarkup(el) {
2
+ const m = require("react-dom/server");
3
+ return m.renderToStaticMarkup(el);
4
+ }
@@ -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) => `₹${INR.format(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
- const STORAGE_KEY = "letitrip:announcement-dismissed";
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(STORAGE_KEY);
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(STORAGE_KEY, JSON.stringify({ hash: hashBannerMessage(message), at: Date.now() }));
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 */
@@ -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 || "LetItRip.in", size: "xl" }), _jsx(SiteLogo, { title: brandLogoText || "LetItRip.in", 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 }))] })] }) }) })] }) })] }));
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;
@@ -189,7 +189,7 @@ function SidebarContent({ sidebarItems, sidebarSections, renderSidebarItemAction
189
189
  ].join(" "), children: action.label }, `${action.href}-${action.label}`))) })), isAuthenticated && (_jsx(CollapsibleNavGroup, { title: labels.sectionTitle, hasActive: !!activeProfileLink, children: _jsxs(Ul, { spacing: "2xs", children: [_jsx(Li, { children: _jsx(TextLink, { href: profileHref, variant: "none", className: activeProfileLink?.href === profileHref ? navItemActiveClass : navItemClass, "aria-current": activeProfileLink?.href === profileHref ? "page" : undefined, children: labels.profile }) }), userOrdersHref && (_jsx(Li, { children: _jsx(TextLink, { href: userOrdersHref, variant: "none", className: activeProfileLink?.href === userOrdersHref ? navItemActiveClass : navItemClass, "aria-current": activeProfileLink?.href === userOrdersHref ? "page" : undefined, children: labels.orders }) })), userWishlistHref && (_jsx(Li, { children: _jsx(TextLink, { href: userWishlistHref, variant: "none", className: activeProfileLink?.href === userWishlistHref ? navItemActiveClass : navItemClass, "aria-current": activeProfileLink?.href === userWishlistHref ? "page" : undefined, children: labels.wishlist }) })), userSettingsHref && (_jsx(Li, { children: _jsx(TextLink, { href: userSettingsHref, variant: "none", className: activeProfileLink?.href === userSettingsHref ? navItemActiveClass : navItemClass, "aria-current": activeProfileLink?.href === userSettingsHref ? "page" : undefined, children: labels.settings }) }))] }) })), isAuthenticated && user?.stats && (_jsxs(Div, { layout: "grid", gap: "2", className: "grid-cols-2", children: [user.stats.totalOrders != null && userOrdersHref && (_jsxs(TextLink, { href: userOrdersHref, variant: "none", className: CLS_STAT_BOX, children: [_jsx(Text, { className: "leading-none", color: "primary", size: "lg", weight: "bold", children: user.stats.totalOrders }), _jsx(Text, { className: CLS_STAT_LABEL, children: "Orders" })] })), user.stats.reviewsCount != null && userReviewsHref && (_jsxs(TextLink, { href: userReviewsHref, variant: "none", className: CLS_STAT_BOX, children: [_jsx(Text, { className: "leading-none", color: "primary", size: "lg", weight: "bold", children: user.stats.reviewsCount }), _jsx(Text, { className: CLS_STAT_LABEL, children: "Reviews" })] })), auctionsEnabled && user.stats.auctionsWon != null && userOrdersHref && (_jsxs(TextLink, { href: `${userOrdersHref}?orderType=auction`, variant: "none", className: CLS_STAT_BOX, children: [_jsx(Text, { className: "leading-none", color: "primary", size: "lg", weight: "bold", children: user.stats.auctionsWon }), _jsx(Text, { className: CLS_STAT_LABEL, children: "Auctions Won" })] }))] })), isAuthenticated && isAdminOrSeller && (adminHref || resolvedStoreHref) && (_jsx(CollapsibleNavGroup, { title: labels.dashboardSectionTitle, hasActive: !!activeDashboardLink, children: _jsxs(Ul, { spacing: "2xs", children: [adminHref && role === "admin" && (_jsx(Li, { children: _jsx(TextLink, { href: adminHref, variant: "none", className: activeDashboardLink?.href === adminHref ? navItemActiveClass : navItemClass, "aria-current": activeDashboardLink?.href === adminHref ? "page" : undefined, children: labels.adminDashboard }) })), resolvedStoreHref && isAdminOrSeller && (_jsx(Li, { children: _jsx(TextLink, { href: resolvedStoreHref, variant: "none", className: activeDashboardLink?.href === resolvedStoreHref ? navItemActiveClass : navItemClass, "aria-current": activeDashboardLink?.href === resolvedStoreHref ? "page" : undefined, children: labels.storeDashboard }) }))] }) })), normalizedSections.map((section, sectionIndex) => (_jsx(CollapsibleSidebarSection, { section: section, navItemClass: navItemClass, navItemActiveClass: navItemActiveClass, activeHref: activeHref, renderItemAction: renderSidebarItemAction }, `sidebar-section-${sectionIndex}`))), (sidebarLocaleSlot || showThemeToggleInSidebar || (isAuthenticated && onLogout)) && (_jsxs(Stack, { border: "default", className: "border-t border-[var(--appkit-color-border-subtle)]", padding: "t-md", gap: "3", children: [sidebarLocaleSlot, showThemeToggleInSidebar && (_jsxs("button", { type: "button", onClick: toggleTheme, className: "flex w-full items-center justify-end gap-[var(--appkit-space-3)] rounded-lg px-[var(--appkit-space-3)] py-[var(--appkit-space-2)] text-[length:var(--appkit-text-sm)] text-[var(--appkit-color-text-muted)] transition-colors hover:bg-primary-surface hover:text-primary-800 dark:hover:text-primary-200", children: [_jsx(Span, { "aria-hidden": "true", children: theme === "dark" ? "☀️" : "🌙" }), activeThemeName] })), isAuthenticated && onLogout && (_jsx("button", { type: "button", onClick: () => { onLogout(); onAfterLogout(); }, className: CLS_LOGOUT_BTN, children: labels.logout }))] }))] }));
190
190
  }
191
191
  // ─── Main component ────────────────────────────────────────────────────────────
192
- export function AppLayoutShell({ children, navItems, sidebarItems = [], sidebarSections, renderSidebarItemAction, sidebarPrimaryActions, sidebarTitle = "Navigation", hiddenNavItems, user, brandName, brandShortName, siteLogoUrl, logoHref, promotionsHref, cartHref, wishlistHref, messagesHref, unreadMessageCount, userId, profileHref, loginHref, registerHref, homeHref, shopHref, footer, showFooter = true, searchSlot, searchSlotRenderer, titleBarNavSlot, titleBarNotificationSlot, titleBarDevSlot, titleBarPromoStripText, showThemeToggle = false, suppressDashboardNav = false, hideSidebarToggle = false, onLogout, adminHref, storeHref, sellerHref, userOrdersHref, userWishlistHref, userSettingsHref, userReviewsHref, sidebarLocaleSlot, showThemeToggleInSidebar = false, sidebarProfileLabels, eventBannerSlot, showBottomNav = true, contentClassName, lightBackground = DEFAULT_LIGHT_BG, darkBackground = DEFAULT_DARK_BG, }) {
192
+ export function AppLayoutShell({ children, navItems, sidebarItems = [], sidebarSections, renderSidebarItemAction, sidebarPrimaryActions, sidebarTitle = "Navigation", hiddenNavItems, user, brandName, brandTld, brandShortName, siteLogoUrl, logoHref, promotionsHref, cartHref, wishlistHref, messagesHref, unreadMessageCount, userId, profileHref, loginHref, registerHref, homeHref, shopHref, footer, showFooter = true, searchSlot, searchSlotRenderer, titleBarNavSlot, titleBarNotificationSlot, titleBarDevSlot, titleBarPromoStripText, showThemeToggle = false, suppressDashboardNav = false, hideSidebarToggle = false, onLogout, adminHref, storeHref, sellerHref, userOrdersHref, userWishlistHref, userSettingsHref, userReviewsHref, sidebarLocaleSlot, showThemeToggleInSidebar = false, sidebarProfileLabels, eventBannerSlot, showBottomNav = true, contentClassName, lightBackground = DEFAULT_LIGHT_BG, darkBackground = DEFAULT_DARK_BG, }) {
193
193
  // QueryClientProvider used to be created here so AppLayoutShell could stand
194
194
  // alone. The real cause of the 2026-06-10/11 "No QueryClient set" prod crash
195
195
  // turned out to be a peer-dep duplicate of @tanstack/query-core under
@@ -256,7 +256,7 @@ export function AppLayoutShell({ children, navItems, sidebarItems = [], sidebarS
256
256
  opacity: darkBackground.overlay?.opacity ?? 0,
257
257
  },
258
258
  };
259
- return (_jsx(_Fragment, { children: _jsxs(Stack, { className: "min-h-screen w-full overflow-x-clip transition-colors duration-300", children: [_jsx(BackgroundRenderer, { mode: theme === "dark" ? "dark" : "light", lightMode: normalizedLightBackground, darkMode: normalizedDarkBackground }), _jsx(Div, { id: TOP_BANNER_SLOT_ID, className: "w-full" }), _jsxs(Div, { ref: headerRef, className: "sticky top-0 z-50 w-full", children: [_jsx(TitleBar, { onToggleSidebar: handleTogglePublicSidebar, sidebarOpen: sidebarOpen, onSearchToggle: () => setSearchOpen((prev) => !prev), searchOpen: searchOpen, brandName: brandName, brandShortName: brandShortName, siteLogoUrl: siteLogoUrl, logoHref: logoHref, promotionsHref: promotionsHref, cartHref: cartHref, wishlistHref: wishlistHref, messagesHref: messagesHref, unreadMessageCount: unreadMessageCount, userId: userId, profileHref: profileHref, loginHref: loginHref, registerHref: registerHref, user: user, navSlot: titleBarNavSlot, notificationSlot: titleBarNotificationSlot, devSlot: titleBarDevSlot, promoStripText: titleBarPromoStripText, isDark: theme === "dark", onToggleTheme: showThemeToggle ? toggleTheme : undefined, onBeforeToggleDashboardNav: handleBeforeDashboardNavToggle, suppressDashboardNav: suppressDashboardNav, hideSidebarToggle: hideSidebarToggle }), _jsx(NavbarWithSettings, { navItems: navItems, hiddenNavItems: hiddenNavItems, permissions: authUser?.permissions }), searchOpen && (searchSlotRenderer ? searchSlotRenderer(() => setSearchOpen(false)) : searchSlot), eventBannerSlot] }), _jsx(AutoBreadcrumbs, {}), _jsxs(Div, { layout: "flex", className: "relative w-full flex-1 overflow-x-clip", children: [_jsx(SidebarLayout, { isOpen: sidebarOpen, ariaLabel: "Secondary navigation", header: user ? (_jsx(SidebarUserHeader, { user: user, onClose: () => setSidebarOpen(false), profileHref: profileHref })) : (_jsx(SidebarGuestHeader, { sidebarTitle: sidebarTitle, onClose: () => setSidebarOpen(false) })), onClose: () => setSidebarOpen(false), children: sidebarContent }), _jsx(Main, { id: "main-content",
259
+ return (_jsx(_Fragment, { children: _jsxs(Stack, { className: "min-h-screen w-full overflow-x-clip transition-colors duration-300", children: [_jsx(BackgroundRenderer, { mode: theme === "dark" ? "dark" : "light", lightMode: normalizedLightBackground, darkMode: normalizedDarkBackground }), _jsx(Div, { id: TOP_BANNER_SLOT_ID, className: "w-full" }), _jsxs(Div, { ref: headerRef, className: "sticky top-0 z-50 w-full", children: [_jsx(TitleBar, { onToggleSidebar: handleTogglePublicSidebar, sidebarOpen: sidebarOpen, onSearchToggle: () => setSearchOpen((prev) => !prev), searchOpen: searchOpen, brandName: brandName, brandTld: brandTld, brandShortName: brandShortName, siteLogoUrl: siteLogoUrl, logoHref: logoHref, promotionsHref: promotionsHref, cartHref: cartHref, wishlistHref: wishlistHref, messagesHref: messagesHref, unreadMessageCount: unreadMessageCount, userId: userId, profileHref: profileHref, loginHref: loginHref, registerHref: registerHref, user: user, navSlot: titleBarNavSlot, notificationSlot: titleBarNotificationSlot, devSlot: titleBarDevSlot, promoStripText: titleBarPromoStripText, isDark: theme === "dark", onToggleTheme: showThemeToggle ? toggleTheme : undefined, onBeforeToggleDashboardNav: handleBeforeDashboardNavToggle, suppressDashboardNav: suppressDashboardNav, hideSidebarToggle: hideSidebarToggle }), _jsx(NavbarWithSettings, { navItems: navItems, hiddenNavItems: hiddenNavItems, permissions: authUser?.permissions }), searchOpen && (searchSlotRenderer ? searchSlotRenderer(() => setSearchOpen(false)) : searchSlot), eventBannerSlot] }), _jsx(AutoBreadcrumbs, {}), _jsxs(Div, { layout: "flex", className: "relative w-full flex-1 overflow-x-clip", children: [_jsx(SidebarLayout, { isOpen: sidebarOpen, ariaLabel: "Secondary navigation", header: user ? (_jsx(SidebarUserHeader, { user: user, onClose: () => setSidebarOpen(false), profileHref: profileHref })) : (_jsx(SidebarGuestHeader, { sidebarTitle: sidebarTitle, onClose: () => setSidebarOpen(false) })), onClose: () => setSidebarOpen(false), children: sidebarContent }), _jsx(Main, { id: "main-content",
260
260
  // Bottom-chrome clearance belongs to whichever element is LAST.
261
261
  //
262
262
  // With the footer rendered it is the footer's, and a margin here
@@ -14,6 +14,13 @@ export interface TitleBarLayoutProps {
14
14
  searchOpen: boolean;
15
15
  brandName: string;
16
16
  brandShortName?: string;
17
+ /**
18
+ * TLD badge for the SVG wordmark, e.g. `"in"`. Omitted renders no badge.
19
+ *
20
+ * A5 — the badge used to be a hardcoded `.in` inside `SiteLogo`, so every
21
+ * consumer of appkit got one marketplace's TLD drawn into its header.
22
+ */
23
+ brandTld?: string;
17
24
  /** Admin-uploaded site logo URL. Falls back to the SVG wordmark when empty. */
18
25
  siteLogoUrl?: string;
19
26
  logoHref: string;
@@ -74,4 +81,4 @@ export interface TitleBarLayoutProps {
74
81
  *
75
82
  * Receives all domain data as props — zero domain imports.
76
83
  */
77
- export declare function TitleBarLayout({ onToggleSidebar, sidebarOpen, onSearchToggle, searchOpen, brandName, brandShortName, siteLogoUrl, logoHref, promotionsHref, compareHref, wishlistHref, wishlistCount, cartHref, cartCount, profileHref, unreadNotificationCount, notificationsHref, messagesHref, unreadMessageCount, loginHref, registerHref, user, notificationSlot, devSlot, navSlot, promoStripText, isDark, onToggleTheme, hasDashboardNav, onToggleDashboardNav, hideSidebarToggle, id, className, }: TitleBarLayoutProps): React.JSX.Element;
84
+ export declare function TitleBarLayout({ onToggleSidebar, sidebarOpen, onSearchToggle, searchOpen, brandName, brandTld, brandShortName, siteLogoUrl, logoHref, promotionsHref, compareHref, wishlistHref, wishlistCount, cartHref, cartCount, profileHref, unreadNotificationCount, notificationsHref, messagesHref, unreadMessageCount, loginHref, registerHref, user, notificationSlot, devSlot, navSlot, promoStripText, isDark, onToggleTheme, hasDashboardNav, onToggleDashboardNav, hideSidebarToggle, id, className, }: TitleBarLayoutProps): React.JSX.Element;