@mohasinac/appkit 4.9.4 → 4.9.5

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 (66) hide show
  1. package/dist/_internal/client/features/layout/DashboardLayoutClient.js +7 -2
  2. package/dist/_internal/client/features/layout/SidebarCollapseToggle.d.ts +5 -1
  3. package/dist/_internal/client/features/layout/SidebarCollapseToggle.js +8 -2
  4. package/dist/_internal/client/features/layout/sidebarPositionClasses.d.ts +20 -0
  5. package/dist/_internal/client/features/layout/sidebarPositionClasses.js +20 -0
  6. package/dist/_internal/client/hand-mode/HandModeProvider.d.ts +21 -0
  7. package/dist/_internal/client/hand-mode/HandModeProvider.js +81 -0
  8. package/dist/_internal/client/hand-mode/index.d.ts +2 -0
  9. package/dist/_internal/client/hand-mode/index.js +1 -0
  10. package/dist/_internal/server/features/checkout/actions.d.ts +43 -0
  11. package/dist/_internal/server/features/checkout/actions.js +172 -81
  12. package/dist/_internal/server/features/checkout/index.d.ts +1 -1
  13. package/dist/_internal/server/features/checkout/index.js +1 -1
  14. package/dist/_internal/server/features/seo/robots.js +3 -2
  15. package/dist/_internal/server/features/seo/sitemap.js +70 -5
  16. package/dist/_internal/shared/config/schema.d.ts +1 -0
  17. package/dist/_internal/shared/listing-types/classified/config.js +1 -1
  18. package/dist/_internal/shared/listing-types/digital-code/config.js +1 -1
  19. package/dist/_internal/shared/listing-types/live/config.js +1 -1
  20. package/dist/client.d.ts +4 -0
  21. package/dist/client.js +9 -0
  22. package/dist/constants/api-endpoints.d.ts +3 -0
  23. package/dist/constants/api-endpoints.js +1 -0
  24. package/dist/features/about/components/AboutView.js +2 -2
  25. package/dist/features/about/components/DeveloperView.js +2 -2
  26. package/dist/features/about/schemas/firestore.d.ts +1 -0
  27. package/dist/features/account/components/UserSidebar.js +8 -2
  28. package/dist/features/account/hooks/useProfile.d.ts +2 -0
  29. package/dist/features/admin/components/AdminSidebar.js +8 -2
  30. package/dist/features/auth/schemas/firestore.d.ts +3 -0
  31. package/dist/features/cart/components/CartDrawer.js +4 -1
  32. package/dist/features/homepage/components/BrandsSection.js +3 -1
  33. package/dist/features/homepage/components/FeaturedBundlesSection.js +12 -1
  34. package/dist/features/homepage/components/GoogleReviewsSection.js +2 -2
  35. package/dist/features/homepage/components/HeroCarousel.js +3 -1
  36. package/dist/features/homepage/components/SectionCarousel.js +5 -2
  37. package/dist/features/homepage/components/SocialFeedSection.js +2 -2
  38. package/dist/features/homepage/components/WhatsAppCommunitySection.js +3 -1
  39. package/dist/features/layout/BackToTop.js +3 -1
  40. package/dist/features/layout/SidebarLayout.js +7 -1
  41. package/dist/features/seller/components/SellerSidebar.js +8 -2
  42. package/dist/features/shell/QuickFormDrawer.js +3 -1
  43. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +31 -0
  44. package/dist/hand-mode/index.d.ts +12 -0
  45. package/dist/hand-mode/index.js +11 -0
  46. package/dist/react/contexts/SessionContext.d.ts +5 -0
  47. package/dist/react/contexts/SessionContext.js +2 -0
  48. package/dist/seed/site-settings-seed-data.js +3 -2
  49. package/dist/server-entry.d.ts +1 -1
  50. package/dist/server-entry.js +1 -1
  51. package/dist/styles.css +2 -1
  52. package/dist/tailwind-utilities.css +1 -1
  53. package/dist/ui/components/Drawer.js +7 -4
  54. package/dist/ui/components/FilterDrawer.js +1 -1
  55. package/dist/ui/components/HandModeRow.d.ts +12 -0
  56. package/dist/ui/components/HandModeRow.js +18 -0
  57. package/dist/ui/components/Layout.d.ts +5 -1
  58. package/dist/ui/components/Layout.js +2 -1
  59. package/dist/ui/components/Layout.style.css +1 -0
  60. package/dist/ui/components/ListingFilterDrawer.js +3 -1
  61. package/dist/ui/components/Modal.js +3 -1
  62. package/dist/ui/components/SideDrawer.js +8 -5
  63. package/dist/ui/components/SideModal.js +7 -4
  64. package/dist/ui/index.d.ts +1 -0
  65. package/dist/ui/index.js +1 -0
  66. package/package.json +1 -1
@@ -21,6 +21,7 @@ function staticPages(baseUrl) {
21
21
  page(String(ROUTES.PUBLIC.PRODUCTS), "hourly", 0.9),
22
22
  page(String(ROUTES.PUBLIC.AUCTIONS), "hourly", 0.9),
23
23
  page(String(ROUTES.PUBLIC.CATEGORIES), "weekly", 0.8),
24
+ page(String(ROUTES.PUBLIC.BRANDS), "weekly", 0.7),
24
25
  page(String(ROUTES.PUBLIC.BLOG), "daily", 0.7),
25
26
  page(String(ROUTES.PUBLIC.EVENTS), "daily", 0.7),
26
27
  page(String(ROUTES.PUBLIC.SELLERS), "weekly", 0.6),
@@ -39,6 +40,12 @@ function staticPages(baseUrl) {
39
40
  page(String(ROUTES.PUBLIC.REFUND_POLICY), "yearly", 0.3),
40
41
  page(String(ROUTES.PUBLIC.SHIPPING_POLICY), "yearly", 0.3),
41
42
  page(String(ROUTES.PUBLIC.PRE_ORDERS), "daily", 0.7),
43
+ page(String(ROUTES.PUBLIC.BUNDLES), "weekly", 0.6),
44
+ page(String(ROUTES.PUBLIC.PRIZE_DRAWS), "daily", 0.7),
45
+ page(String(ROUTES.PUBLIC.CLASSIFIED), "daily", 0.6),
46
+ page(String(ROUTES.PUBLIC.DIGITAL_CODES), "daily", 0.6),
47
+ page(String(ROUTES.PUBLIC.LIVE), "hourly", 0.7),
48
+ page(String(ROUTES.PUBLIC.ART), "weekly", 0.6),
42
49
  page(String(ROUTES.PUBLIC.FEES), "monthly", 0.4),
43
50
  page(String(ROUTES.PUBLIC.HOW_AUCTIONS_WORK), "monthly", 0.4),
44
51
  page(String(ROUTES.PUBLIC.HOW_PRE_ORDERS_WORK), "monthly", 0.4),
@@ -53,13 +60,16 @@ function staticPages(baseUrl) {
53
60
  page(String(ROUTES.PUBLIC.SCAM_FAQS), "weekly", 0.7),
54
61
  ];
55
62
  }
63
+ // "art" and "stickers" listing types render at the same PRODUCT_DETAIL route
64
+ // as "standard" (see route-map.ts's ART comment) — no dedicated detail page.
65
+ const PRODUCT_DETAIL_LISTING_TYPES = ["standard", "art", "stickers"];
56
66
  async function fetchProductUrls(baseUrl) {
57
67
  try {
58
68
  const db = getAdminDb();
59
69
  const snap = await db
60
70
  .collection(PRODUCT_COLLECTION)
61
71
  .where(PRODUCT_STATUS, "==", PRODUCT_STATUS_PUBLISHED)
62
- .where("listingType", "==", "standard")
72
+ .where("listingType", "in", PRODUCT_DETAIL_LISTING_TYPES)
63
73
  .select(PRODUCT_SLUG, PRODUCT_UPDATED_AT)
64
74
  .limit(5000)
65
75
  .get();
@@ -80,6 +90,38 @@ async function fetchProductUrls(baseUrl) {
80
90
  return [];
81
91
  }
82
92
  }
93
+ async function fetchListingTypeUrls(baseUrl, listingType, buildUrl, label) {
94
+ try {
95
+ const db = getAdminDb();
96
+ const snap = await db
97
+ .collection(PRODUCT_COLLECTION)
98
+ .where(PRODUCT_STATUS, "==", PRODUCT_STATUS_PUBLISHED)
99
+ .where("listingType", "==", listingType)
100
+ .select(PRODUCT_SLUG, PRODUCT_UPDATED_AT)
101
+ .limit(2000)
102
+ .get();
103
+ return snap.docs.map((doc) => {
104
+ const data = doc.data();
105
+ const slug = data[PRODUCT_SLUG] ?? doc.id;
106
+ return {
107
+ url: `${baseUrl}${buildUrl(slug)}`,
108
+ lastModified: data[PRODUCT_UPDATED_AT]?.toDate?.() ?? new Date(),
109
+ changeFrequency: "daily",
110
+ priority: 0.7,
111
+ };
112
+ });
113
+ }
114
+ catch (err) {
115
+ void normalizeError(err);
116
+ serverLogger.warn(`sitemap: failed to fetch ${label} URLs`, { error: err });
117
+ return [];
118
+ }
119
+ }
120
+ const fetchPreOrderUrls = (baseUrl) => fetchListingTypeUrls(baseUrl, "pre-order", (slug) => ROUTES.PUBLIC.PRE_ORDER_DETAIL(slug), "pre-order");
121
+ const fetchPrizeDrawUrls = (baseUrl) => fetchListingTypeUrls(baseUrl, "prize-draw", (slug) => ROUTES.PUBLIC.PRIZE_DRAW_DETAIL(slug), "prize-draw");
122
+ const fetchClassifiedUrls = (baseUrl) => fetchListingTypeUrls(baseUrl, "classified", (slug) => ROUTES.PUBLIC.CLASSIFIED_DETAIL(slug), "classified");
123
+ const fetchDigitalCodeUrls = (baseUrl) => fetchListingTypeUrls(baseUrl, "digital-code", (slug) => ROUTES.PUBLIC.DIGITAL_CODE_DETAIL(slug), "digital-code");
124
+ const fetchLiveUrls = (baseUrl) => fetchListingTypeUrls(baseUrl, "live", (slug) => ROUTES.PUBLIC.LIVE_DETAIL(slug), "live");
83
125
  async function fetchAuctionUrls(baseUrl) {
84
126
  try {
85
127
  const db = getAdminDb();
@@ -133,11 +175,17 @@ async function fetchEventUrls(baseUrl) {
133
175
  return [];
134
176
  }
135
177
  }
136
- async function fetchCategoryUrls(baseUrl) {
178
+ // categories is a discriminated-union collection (categoryType: "listing" |
179
+ // "brand" | "bundle" — see CLAUDE.md's SB-UNI-C/SB-UNI-D). Each discriminator
180
+ // renders at a different public route, so each needs its own filtered query;
181
+ // querying the collection without a categoryType filter (the prior bug here)
182
+ // mapped brand/bundle rows onto the wrong /categories/{slug} URL.
183
+ async function fetchCategoryTypeUrls(baseUrl, categoryType, buildUrl, label) {
137
184
  try {
138
185
  const db = getAdminDb();
139
186
  const snap = await db
140
187
  .collection(CATEGORIES_COLLECTION)
188
+ .where(CATEGORY_FIELDS.CATEGORY_TYPE, "==", categoryType)
141
189
  .where(CATEGORY_FIELDS.IS_ACTIVE, "==", true)
142
190
  .select(CATEGORY_FIELDS.SLUG, CATEGORY_FIELDS.UPDATED_AT)
143
191
  .limit(500)
@@ -146,7 +194,7 @@ async function fetchCategoryUrls(baseUrl) {
146
194
  const data = doc.data();
147
195
  const slug = data[CATEGORY_FIELDS.SLUG] ?? doc.id;
148
196
  return {
149
- url: `${baseUrl}/categories/${slug}`,
197
+ url: `${baseUrl}${buildUrl(slug)}`,
150
198
  lastModified: data[CATEGORY_FIELDS.UPDATED_AT]?.toDate?.() ?? new Date(),
151
199
  changeFrequency: "weekly",
152
200
  priority: 0.7,
@@ -155,10 +203,13 @@ async function fetchCategoryUrls(baseUrl) {
155
203
  }
156
204
  catch (err) {
157
205
  void normalizeError(err);
158
- serverLogger.warn("sitemap: failed to fetch category URLs", { error: err });
206
+ serverLogger.warn(`sitemap: failed to fetch ${label} URLs`, { error: err });
159
207
  return [];
160
208
  }
161
209
  }
210
+ const fetchCategoryUrls = (baseUrl) => fetchCategoryTypeUrls(baseUrl, "listing", (slug) => ROUTES.PUBLIC.CATEGORY_DETAIL(slug), "category");
211
+ const fetchBrandUrls = (baseUrl) => fetchCategoryTypeUrls(baseUrl, "brand", (slug) => ROUTES.PUBLIC.BRAND_DETAIL(slug), "brand");
212
+ const fetchBundleUrls = (baseUrl) => fetchCategoryTypeUrls(baseUrl, "bundle", (slug) => ROUTES.PUBLIC.BUNDLE_DETAIL(slug), "bundle");
162
213
  async function fetchBlogPostUrls(baseUrl) {
163
214
  try {
164
215
  const db = getAdminDb();
@@ -242,21 +293,35 @@ async function fetchScammerUrls(baseUrl) {
242
293
  }
243
294
  }
244
295
  export async function buildSitemap({ baseUrl }) {
245
- const [productUrls, categoryUrls, eventUrls, blogUrls, auctionUrls, storeUrls, scammerUrls] = await Promise.all([
296
+ const [productUrls, categoryUrls, brandUrls, bundleUrls, eventUrls, blogUrls, auctionUrls, preOrderUrls, prizeDrawUrls, classifiedUrls, digitalCodeUrls, liveUrls, storeUrls, scammerUrls,] = await Promise.all([
246
297
  fetchProductUrls(baseUrl),
247
298
  fetchCategoryUrls(baseUrl),
299
+ fetchBrandUrls(baseUrl),
300
+ fetchBundleUrls(baseUrl),
248
301
  fetchEventUrls(baseUrl),
249
302
  fetchBlogPostUrls(baseUrl),
250
303
  fetchAuctionUrls(baseUrl),
304
+ fetchPreOrderUrls(baseUrl),
305
+ fetchPrizeDrawUrls(baseUrl),
306
+ fetchClassifiedUrls(baseUrl),
307
+ fetchDigitalCodeUrls(baseUrl),
308
+ fetchLiveUrls(baseUrl),
251
309
  fetchStoreUrls(baseUrl),
252
310
  fetchScammerUrls(baseUrl),
253
311
  ]);
254
312
  return [
255
313
  ...staticPages(baseUrl),
256
314
  ...categoryUrls,
315
+ ...brandUrls,
316
+ ...bundleUrls,
257
317
  ...blogUrls,
258
318
  ...productUrls,
259
319
  ...auctionUrls,
320
+ ...preOrderUrls,
321
+ ...prizeDrawUrls,
322
+ ...classifiedUrls,
323
+ ...digitalCodeUrls,
324
+ ...liveUrls,
260
325
  ...eventUrls,
261
326
  ...storeUrls,
262
327
  ...scammerUrls,
@@ -100,6 +100,7 @@ export interface AppkitBrandConfig {
100
100
  youtube?: string;
101
101
  facebook?: string;
102
102
  linkedin?: string;
103
+ github?: string;
103
104
  };
104
105
  }
105
106
  export interface AppkitSeoConfig {
@@ -6,7 +6,7 @@ export const config = {
6
6
  listingType: LISTING_TYPE,
7
7
  slugPrefix: "classified-",
8
8
  cartLine: "blocked",
9
- detailRoute: (idOrSlug) => String(ROUTES.PUBLIC.PRODUCT_DETAIL(idOrSlug)),
9
+ detailRoute: (idOrSlug) => String(ROUTES.PUBLIC.CLASSIFIED_DETAIL(idOrSlug)),
10
10
  badge: undefined,
11
11
  priceLabel: "Asking Price (₹)",
12
12
  typeLabel: "Classified",
@@ -6,7 +6,7 @@ export const config = {
6
6
  listingType: LISTING_TYPE,
7
7
  slugPrefix: "digitalcode-",
8
8
  cartLine: "single-product",
9
- detailRoute: (idOrSlug) => String(ROUTES.PUBLIC.PRODUCT_DETAIL(idOrSlug)),
9
+ detailRoute: (idOrSlug) => String(ROUTES.PUBLIC.DIGITAL_CODE_DETAIL(idOrSlug)),
10
10
  badge: undefined,
11
11
  priceLabel: "Price per Code (₹)",
12
12
  typeLabel: "Digital Code",
@@ -6,7 +6,7 @@ export const config = {
6
6
  listingType: LISTING_TYPE,
7
7
  slugPrefix: "live-",
8
8
  cartLine: "single-product",
9
- detailRoute: (idOrSlug) => String(ROUTES.PUBLIC.PRODUCT_DETAIL(idOrSlug)),
9
+ detailRoute: (idOrSlug) => String(ROUTES.PUBLIC.LIVE_DETAIL(idOrSlug)),
10
10
  badge: undefined,
11
11
  priceLabel: "Price (₹)",
12
12
  typeLabel: "Live Item",
package/dist/client.d.ts CHANGED
@@ -24,11 +24,15 @@ export { registerClientRealtimeProvider, getClientRealtimeProvider, type IClient
24
24
  export { registerClientSessionAdapter, getClientSessionAdapter, type IClientSessionAdapter, type AdapterAuthUser, type AuthUnsubscribe, } from "./contracts/client-session";
25
25
  export { ThemeProvider, useTheme, buildThemeRegistry } from "./theme";
26
26
  export type { ModePreference, SiteSettingsThemeInput, ThemeContextValue, ThemeProviderProps, ThemeRegistry, } from "./theme";
27
+ export { HandModeProvider, useHandMode } from "./hand-mode";
28
+ export type { HandMode, HandModeContextValue, HandModeProviderProps, } from "./hand-mode";
27
29
  export { BUILT_IN_THEMES, DEFAULT_DARK_THEME, DEFAULT_LIGHT_THEME, REQUIRED_GRADIENT_KEYS, REQUIRED_THEME_TOKENS, getDefaultBuiltInTheme, } from "./tokens/themes";
28
30
  export type { GradientKey, RequiredThemeToken, ThemeMode, ThemeRecord, } from "./tokens/themes";
29
31
  export { ConfirmDeleteModal } from "./ui/components/ConfirmDeleteModal";
30
32
  export { EmptyState } from "./ui/components/EmptyState";
31
33
  export { Skeleton } from "./ui/components/Skeleton";
34
+ export { IconBox } from "./ui/components/IconBox";
35
+ export type { IconBoxProps, IconBoxRounded, IconBoxSize, IconBoxTone, } from "./ui/components/IconBox";
32
36
  export { Alert } from "./ui/components/Alert";
33
37
  export type { SkeletonProps } from "./ui/components/Skeleton";
34
38
  export { Divider } from "./ui/components/Divider";
package/dist/client.js CHANGED
@@ -32,6 +32,12 @@ export { registerClientSessionAdapter, getClientSessionAdapter, } from "./contra
32
32
  // Re-exported through the public `./theme` barrel to keep audit-appkit-reexports
33
33
  // clean (no `_internal/` symbols may surface through public barrels).
34
34
  export { ThemeProvider, useTheme, buildThemeRegistry } from "./theme";
35
+ // [CLIENT-ONLY] HandModeProvider — applies the "left-hand mode" layout
36
+ // preference to `<html>` via a `data-hand` attribute so drawers/sidebars/
37
+ // close-buttons/floating CTAs flip which screen edge they dock to.
38
+ // Re-exported through the public `./hand-mode` barrel to keep
39
+ // audit-appkit-reexports clean (no `_internal/` symbols in public barrels).
40
+ export { HandModeProvider, useHandMode } from "./hand-mode";
35
41
  export { BUILT_IN_THEMES, DEFAULT_DARK_THEME, DEFAULT_LIGHT_THEME, REQUIRED_GRADIENT_KEYS, REQUIRED_THEME_TOKENS, getDefaultBuiltInTheme, } from "./tokens/themes";
36
42
  // [CLIENT-ONLY]-Cannot run in SSR mode â€" uses browser-only APIs (window, navigator, localStorage, matchMedia, DOM events) that do not exist in Node.js.
37
43
  // ConfirmDeleteModal - Component for confirm delete modal.
@@ -39,6 +45,9 @@ export { ConfirmDeleteModal } from "./ui/components/ConfirmDeleteModal";
39
45
  // S-STORE — pure-UI primitives + Seller view consumed by new dashboard pages
40
46
  export { EmptyState } from "./ui/components/EmptyState";
41
47
  export { Skeleton } from "./ui/components/Skeleton";
48
+ // IconBox — square-icon container primitive; was exported from index.ts only,
49
+ // missing here even though CLAUDE.md documents it as a client-usable pattern.
50
+ export { IconBox } from "./ui/components/IconBox";
42
51
  export { Alert } from "./ui/components/Alert";
43
52
  export { Divider } from "./ui/components/Divider";
44
53
  export { Tabs, TabsList, TabsTrigger, TabsContent } from "./ui/components/Tabs";
@@ -249,6 +249,7 @@ export declare const CATEGORY_ENDPOINTS: {
249
249
  export declare const CHECKOUT_ENDPOINTS: {
250
250
  readonly PREFLIGHT: "/api/checkout/preflight";
251
251
  readonly PLACE_ORDER: "/api/checkout";
252
+ readonly PRICING_PREVIEW: "/api/checkout/pricing-preview";
252
253
  };
253
254
  export declare const PAYMENT_ENDPOINTS: {
254
255
  readonly CREATE_ORDER: "/api/payment/create-order";
@@ -703,6 +704,7 @@ export declare const API_ENDPOINTS: {
703
704
  readonly CHECKOUT: {
704
705
  readonly PREFLIGHT: "/api/checkout/preflight";
705
706
  readonly PLACE_ORDER: "/api/checkout";
707
+ readonly PRICING_PREVIEW: "/api/checkout/pricing-preview";
706
708
  };
707
709
  readonly PAYMENT: {
708
710
  readonly CREATE_ORDER: "/api/payment/create-order";
@@ -1156,6 +1158,7 @@ export declare const API_ROUTES: {
1156
1158
  readonly CHECKOUT: {
1157
1159
  readonly PREFLIGHT: "/api/checkout/preflight";
1158
1160
  readonly PLACE_ORDER: "/api/checkout";
1161
+ readonly PRICING_PREVIEW: "/api/checkout/pricing-preview";
1159
1162
  };
1160
1163
  readonly PAYMENT: {
1161
1164
  readonly CREATE_ORDER: "/api/payment/create-order";
@@ -295,6 +295,7 @@ export const CATEGORY_ENDPOINTS = {
295
295
  export const CHECKOUT_ENDPOINTS = {
296
296
  PREFLIGHT: "/api/checkout/preflight",
297
297
  PLACE_ORDER: "/api/checkout",
298
+ PRICING_PREVIEW: "/api/checkout/pricing-preview",
298
299
  };
299
300
  // ---------------------------------------------------------------------------
300
301
  // Payment
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Avatar, Badge, Card, Div, Heading, Row, Section, Span, Stack, Text } from "../../../ui";
2
+ import { Anchor, Avatar, Badge, Card, Div, Heading, Row, Section, Span, Stack, Text } from "../../../ui";
3
3
  export function AboutView({ labels = {}, howItems = [], valueItems = [], milestones = [], teamMembers = [], ctaBannerClass = "", renderCtaButtons, }) {
4
- return (_jsxs(Div, { className: "-mx-4 md:-mx-6 lg:-mx-8 -mt-6 sm:-mt-8 lg:-mt-10", children: [_jsx(Section, { color: "inverse", tone: "accent-banner", padding: "y-4xl", children: _jsxs(Div, { paddingX: "x-page", className: "max-w-4xl mx-auto text-center", children: [_jsx(Heading, { color: "inverse", level: 1, variant: "none", className: "mb-6", mdSize: "5xl", size: "4xl", weight: "bold", children: labels.title }), _jsx(Text, { color: "inverse", variant: "none", className: "/80 max-w-2xl mx-auto", size: "xl", children: labels.subtitle })] }) }), _jsxs(Div, { paddingX: "x-page", className: "max-w-5xl mx-auto", padding: "y-4xl", children: [_jsxs(Section, { className: "text-center max-w-3xl mx-auto", children: [_jsx(Heading, { level: 2, className: "mb-4", children: labels.missionTitle }), _jsx(Text, { size: "lg", variant: "secondary", className: "leading-relaxed", children: labels.missionText })] }), _jsxs(Section, { children: [_jsx(Heading, { level: 2, className: "mb-12", align: "center", children: labels.howItWorksTitle }), _jsx(Div, { className: "grid md:grid-cols-3", gap: "6", children: howItems.map(({ title, text, icon, tone }) => (_jsxs(Card, { variant: `gradient-${tone ?? "indigo"}`, padding: "md", spacing: "sm", children: [_jsx(Div, { children: _jsx(Span, { size: "4xl", children: icon }) }), _jsx(Heading, { level: 3, children: title }), _jsx(Text, { size: "sm", variant: "secondary", className: "leading-relaxed", children: text })] }, title))) })] }), _jsxs(Section, { children: [_jsx(Heading, { level: 2, className: "mb-12", align: "center", children: labels.valuesTitle }), _jsx(Div, { className: "grid md:grid-cols-3", gap: "5", children: valueItems.map(({ title, text, icon }) => (_jsxs(Stack, { gap: "3", surface: "default", padding: "lg", rounded: "xl", border: "default", children: [_jsx(Div, { children: _jsx(Span, { size: "3xl", children: icon }) }), _jsx(Heading, { level: 3, children: title }), _jsx(Text, { size: "sm", variant: "secondary", className: "leading-relaxed", children: text })] }, title))) })] }), _jsxs(Section, { children: [_jsx(Heading, { level: 2, className: "mb-10", align: "center", children: labels.milestonesTitle }), _jsx(Stack, { className: "relative border-l-2 border-primary/30 max-w-2xl mx-auto pl-[2rem]", gap: "xl", children: milestones.map(({ year, text }) => (_jsxs(Div, { className: "relative", children: [_jsx(Div, { className: "absolute -left-10 top-1 w-4 h-4 bg-primary border-2 border-white", rounded: "full" }), _jsx(Span, { size: "xs", weight: "bold", className: "text-primary tracking-wide", transform: "uppercase", children: year }), _jsx(Text, { className: "mt-1", children: text })] }, year))) })] }), teamMembers.length > 0 && (_jsxs(Section, { children: [_jsx(Heading, { level: 2, className: "mb-2", align: "center", children: labels.teamTitle }), labels.teamSubtitle && (_jsx(Text, { size: "lg", variant: "secondary", align: "center", className: "mb-12 max-w-2xl mx-auto", children: labels.teamSubtitle })), _jsx(Div, { className: "grid md:grid-cols-3", gap: "6", children: teamMembers.map((member) => (_jsxs(Stack, { gap: "3", surface: "default", padding: "lg", rounded: "xl", border: "default", align: "center", className: "text-center", children: [_jsx(Avatar, { src: member.photoUrl, name: member.name, alt: member.name, size: "lg" }), _jsx(Heading, { level: 3, children: member.name }), _jsxs(Row, { gap: "xs", wrap: true, justify: "center", children: [_jsx(Span, { size: "xs", weight: "bold", className: "text-primary tracking-wide", transform: "uppercase", children: member.role }), member.isFounder && _jsx(Badge, { variant: "primary", size: "xs", children: "Founder" }), member.isDeveloper && _jsx(Badge, { variant: "secondary", size: "xs", children: "Developer" })] }), _jsx(Text, { size: "sm", variant: "secondary", className: "leading-relaxed", children: member.bio })] }, member.name))) })] })), _jsxs(Section, { color: "inverse", tone: "accent-banner", className: `text-center ${ctaBannerClass}`, padding: "xl", rounded: "2xl", children: [_jsx(Heading, { color: "inverse", level: 2, variant: "none", className: "mb-8", size: "3xl", weight: "bold", children: labels.ctaTitle }), renderCtaButtons ? (renderCtaButtons()) : (_jsx(Row, { justify: "center", gap: "3", wrap: true, children: _jsxs(Span, { color: "inverse", size: "sm", className: "/70", children: [labels.ctaSell, " \u00B7 ", labels.ctaShop] }) }))] })] })] }));
4
+ return (_jsxs(Div, { className: "-mx-4 md:-mx-6 lg:-mx-8 -mt-6 sm:-mt-8 lg:-mt-10", children: [_jsx(Section, { color: "inverse", tone: "accent-banner", padding: "y-4xl", children: _jsxs(Div, { paddingX: "x-page", className: "max-w-4xl mx-auto text-center", children: [_jsx(Heading, { color: "inverse", level: 1, variant: "none", className: "mb-6", mdSize: "5xl", size: "4xl", weight: "bold", children: labels.title }), _jsx(Text, { color: "inverse", variant: "none", className: "/80 max-w-2xl mx-auto", size: "xl", children: labels.subtitle })] }) }), _jsxs(Div, { paddingX: "x-page", className: "max-w-5xl mx-auto", padding: "y-4xl", children: [_jsxs(Section, { className: "text-center max-w-3xl mx-auto", children: [_jsx(Heading, { level: 2, className: "mb-4", children: labels.missionTitle }), _jsx(Text, { size: "lg", variant: "secondary", className: "leading-relaxed", children: labels.missionText })] }), _jsxs(Section, { children: [_jsx(Heading, { level: 2, className: "mb-12", align: "center", children: labels.howItWorksTitle }), _jsx(Div, { className: "grid md:grid-cols-3", gap: "6", children: howItems.map(({ title, text, icon, tone }) => (_jsxs(Card, { variant: `gradient-${tone ?? "indigo"}`, padding: "md", spacing: "sm", children: [_jsx(Div, { children: _jsx(Span, { size: "4xl", children: icon }) }), _jsx(Heading, { level: 3, children: title }), _jsx(Text, { size: "sm", variant: "secondary", className: "leading-relaxed", children: text })] }, title))) })] }), _jsxs(Section, { children: [_jsx(Heading, { level: 2, className: "mb-12", align: "center", children: labels.valuesTitle }), _jsx(Div, { className: "grid md:grid-cols-3", gap: "5", children: valueItems.map(({ title, text, icon }) => (_jsxs(Stack, { gap: "3", surface: "default", padding: "lg", rounded: "xl", border: "default", children: [_jsx(Div, { children: _jsx(Span, { size: "3xl", children: icon }) }), _jsx(Heading, { level: 3, children: title }), _jsx(Text, { size: "sm", variant: "secondary", className: "leading-relaxed", children: text })] }, title))) })] }), _jsxs(Section, { children: [_jsx(Heading, { level: 2, className: "mb-10", align: "center", children: labels.milestonesTitle }), _jsx(Stack, { className: "relative border-l-2 border-primary/30 max-w-2xl mx-auto pl-[2rem]", gap: "xl", children: milestones.map(({ year, text }) => (_jsxs(Div, { className: "relative", children: [_jsx(Div, { className: "absolute -left-10 top-1 w-4 h-4 bg-primary border-2 border-white", rounded: "full" }), _jsx(Span, { size: "xs", weight: "bold", className: "text-primary tracking-wide", transform: "uppercase", children: year }), _jsx(Text, { className: "mt-1", children: text })] }, year))) })] }), teamMembers.length > 0 && (_jsxs(Section, { children: [_jsx(Heading, { level: 2, className: "mb-2", align: "center", children: labels.teamTitle }), labels.teamSubtitle && (_jsx(Text, { size: "lg", variant: "secondary", align: "center", className: "mb-12 max-w-2xl mx-auto", children: labels.teamSubtitle })), _jsx(Div, { className: "grid md:grid-cols-3", gap: "6", children: teamMembers.map((member) => (_jsxs(Stack, { gap: "3", surface: "default", padding: "lg", rounded: "xl", border: "default", align: "center", className: "text-center", children: [_jsx(Avatar, { src: member.photoUrl, name: member.name, alt: member.name, size: "lg" }), _jsx(Heading, { level: 3, children: member.name }), _jsxs(Row, { gap: "xs", wrap: true, justify: "center", children: [_jsx(Span, { size: "xs", weight: "bold", className: "text-primary tracking-wide", transform: "uppercase", children: member.role }), member.isFounder && _jsx(Badge, { variant: "primary", size: "xs", children: "Founder" }), member.isDeveloper && _jsx(Badge, { variant: "secondary", size: "xs", children: "Developer" })] }), _jsx(Text, { size: "sm", variant: "secondary", className: "leading-relaxed", children: member.bio }), member.githubUrl && (_jsx(Anchor, { href: member.githubUrl, tone: "muted", size: "sm", underline: "hover", children: "GitHub \u2197" }))] }, member.name))) })] })), _jsxs(Section, { color: "inverse", tone: "accent-banner", className: `text-center ${ctaBannerClass}`, padding: "xl", rounded: "2xl", children: [_jsx(Heading, { color: "inverse", level: 2, variant: "none", className: "mb-8", size: "3xl", weight: "bold", children: labels.ctaTitle }), renderCtaButtons ? (renderCtaButtons()) : (_jsx(Row, { justify: "center", gap: "3", wrap: true, children: _jsxs(Span, { color: "inverse", size: "sm", className: "/70", children: [labels.ctaSell, " \u00B7 ", labels.ctaShop] }) }))] })] })] }));
5
5
  }
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { normalizeError } from "../../../errors/normalize";
3
- import { Avatar, Badge, Div, Heading, Row, Section, Span, Stack, Text, TextLink } from "../../../ui";
3
+ import { Anchor, Avatar, Badge, Div, Heading, Row, Section, Span, Stack, Text, TextLink } from "../../../ui";
4
4
  import { ROUTES } from "../../../constants";
5
5
  import { PAGE_CONTAINER } from "../../../_internal/shared/styles/page";
6
6
  import { siteSettingsRepository } from "../../../repositories";
@@ -25,5 +25,5 @@ export async function DeveloperView({ labels = {} }) {
25
25
  // Firestore unavailable — render the empty state below rather than crash.
26
26
  }
27
27
  const developers = teamMembers.filter((m) => m.isDeveloper);
28
- return (_jsxs(Div, { className: "-mx-4 md:-mx-6 lg:-mx-8 -mt-6 sm:-mt-8 lg:-mt-10", children: [_jsx(Section, { color: "inverse", tone: "accent-banner", padding: "banner", children: _jsxs(Div, { className: `${page.container.sm}`, children: [_jsx(Heading, { color: "inverse", level: 1, variant: "none", className: "mb-3", children: labels.title ?? "Meet the Developer" }), _jsx(Text, { color: "inverse", variant: "none", className: "/80", size: "sm", children: labels.subtitle ?? "The person building LetItRip, one feature at a time." })] }) }), _jsxs(Div, { className: `${page.container.sm}`, padding: "content-banner", children: [developers.length === 0 ? (_jsx(Text, { variant: "secondary", children: labels.empty ?? "Developer profile coming soon." })) : (_jsx(Stack, { gap: "xl", children: developers.map((dev) => (_jsxs(Stack, { gap: "md", surface: "default", padding: "lg", rounded: "xl", border: "default", children: [_jsxs(Row, { gap: "md", align: "center", children: [_jsx(Avatar, { src: dev.photoUrl, name: dev.name, alt: dev.name, size: "lg" }), _jsxs(Stack, { gap: "xs", children: [_jsx(Heading, { level: 2, children: dev.name }), _jsxs(Row, { gap: "xs", wrap: true, children: [_jsx(Span, { size: "xs", weight: "bold", className: "text-primary tracking-wide", transform: "uppercase", children: dev.role }), dev.isFounder && _jsx(Badge, { variant: "primary", size: "xs", children: "Founder" }), _jsx(Badge, { variant: "secondary", size: "xs", children: "Developer" })] })] })] }), _jsx(Text, { variant: "secondary", className: "leading-relaxed", children: dev.bio })] }, dev.name))) })), milestones.length > 0 && (_jsxs(Div, { className: "mt-14 border-t", border: "default", padding: "t-xl", children: [_jsx(Heading, { level: 3, className: "mb-6", color: "muted", size: "sm", weight: "semibold", transform: "uppercase", children: labels.journeyTitle ?? "The Journey" }), _jsx(Stack, { className: "relative border-l-2 border-primary/30 max-w-2xl pl-[2rem]", gap: "lg", children: milestones.map(({ year, text }) => (_jsxs(Div, { className: "relative", children: [_jsx(Div, { className: "absolute -left-10 top-1 w-4 h-4 bg-primary border-2 border-white", rounded: "full" }), _jsx(Span, { size: "xs", weight: "bold", className: "text-primary tracking-wide", transform: "uppercase", children: year }), _jsx(Text, { className: "mt-1", children: text })] }, year))) })] })), _jsx(Div, { className: "mt-10", children: _jsx(TextLink, { href: String(ROUTES.PUBLIC.ABOUT), variant: "muted", size: "sm", children: labels.backToTeam ?? "← Meet the whole team" }) })] })] }));
28
+ return (_jsxs(Div, { className: "-mx-4 md:-mx-6 lg:-mx-8 -mt-6 sm:-mt-8 lg:-mt-10", children: [_jsx(Section, { color: "inverse", tone: "accent-banner", padding: "banner", children: _jsxs(Div, { className: `${page.container.sm}`, children: [_jsx(Heading, { color: "inverse", level: 1, variant: "none", className: "mb-3", children: labels.title ?? "Meet the Developer" }), _jsx(Text, { color: "inverse", variant: "none", className: "/80", size: "sm", children: labels.subtitle ?? "The person building LetItRip, one feature at a time." })] }) }), _jsxs(Div, { className: `${page.container.sm}`, padding: "content-banner", children: [developers.length === 0 ? (_jsx(Text, { variant: "secondary", children: labels.empty ?? "Developer profile coming soon." })) : (_jsx(Stack, { gap: "xl", children: developers.map((dev) => (_jsxs(Stack, { gap: "md", surface: "default", padding: "lg", rounded: "xl", border: "default", children: [_jsxs(Row, { gap: "md", align: "center", children: [_jsx(Avatar, { src: dev.photoUrl, name: dev.name, alt: dev.name, size: "lg" }), _jsxs(Stack, { gap: "xs", children: [_jsx(Heading, { level: 2, children: dev.name }), _jsxs(Row, { gap: "xs", wrap: true, children: [_jsx(Span, { size: "xs", weight: "bold", className: "text-primary tracking-wide", transform: "uppercase", children: dev.role }), dev.isFounder && _jsx(Badge, { variant: "primary", size: "xs", children: "Founder" }), _jsx(Badge, { variant: "secondary", size: "xs", children: "Developer" })] })] })] }), _jsx(Text, { variant: "secondary", className: "leading-relaxed", children: dev.bio }), dev.githubUrl && (_jsx(Anchor, { href: dev.githubUrl, tone: "brand", size: "sm", underline: "hover", children: "GitHub \u2197" }))] }, dev.name))) })), milestones.length > 0 && (_jsxs(Div, { className: "mt-14 border-t", border: "default", padding: "t-xl", children: [_jsx(Heading, { level: 3, className: "mb-6", color: "muted", size: "sm", weight: "semibold", transform: "uppercase", children: labels.journeyTitle ?? "The Journey" }), _jsx(Stack, { className: "relative border-l-2 border-primary/30 max-w-2xl pl-[2rem]", gap: "lg", children: milestones.map(({ year, text }) => (_jsxs(Div, { className: "relative", children: [_jsx(Div, { className: "absolute -left-10 top-1 w-4 h-4 bg-primary border-2 border-white", rounded: "full" }), _jsx(Span, { size: "xs", weight: "bold", className: "text-primary tracking-wide", transform: "uppercase", children: year }), _jsx(Text, { className: "mt-1", children: text })] }, year))) })] })), _jsx(Div, { className: "mt-10", children: _jsx(TextLink, { href: String(ROUTES.PUBLIC.ABOUT), variant: "muted", size: "sm", children: labels.backToTeam ?? "← Meet the whole team" }) })] })] }));
29
29
  }
@@ -26,6 +26,7 @@ export interface AboutTeamMember {
26
26
  photoUrl?: string;
27
27
  isFounder?: boolean;
28
28
  isDeveloper?: boolean;
29
+ githubUrl?: string;
29
30
  }
30
31
  export interface AboutContentDocument {
31
32
  title: string;
@@ -9,6 +9,8 @@ import { BottomSheet } from "../../layout/BottomSheet";
9
9
  import { SidebarCollapseToggle } from "../../../_internal/client/features/layout/SidebarCollapseToggle";
10
10
  import { useSidebarSearch } from "../../../_internal/client/features/layout/useSidebarSearch";
11
11
  import { findActiveNavGroup, findActiveNavItem } from "../../../_internal/client/features/layout/navActive";
12
+ import { getSidebarRailClasses, getSidebarOverlayClasses } from "../../../_internal/client/features/layout/sidebarPositionClasses";
13
+ import { useHandMode } from "../../../_internal/client/hand-mode";
12
14
  const __O = {
13
15
  hidden: "overflow-hidden",
14
16
  yAuto: "overflow-y-auto",
@@ -75,11 +77,15 @@ function DrawerContent({ groups, items, activeHref, onItemClick, }) {
75
77
  })] }));
76
78
  }
77
79
  function DrawerPanel({ title, onClose, children, }) {
78
- return (_jsxs(Div, { className: "hidden lg:block", children: [_jsx(Div, { surface: "overlay-xs", className: "fixed inset-0 z-40 backdrop-blur-sm", onClick: onClose, "aria-hidden": "true" }), _jsxs(Stack, { border: "default", shadow: "2xl", role: "dialog", "aria-modal": "true", "aria-label": title, className: "fixed top-0 right-0 z-50 h-full w-64 border-l", surface: "default", children: [_jsxs(Row, { border: "bottom-subtle", paddingY: "y-sm-tall", className: "shrink-0", padding: "x-md", align: "center", justify: "between", children: [_jsx(Span, { size: "xs", weight: "semibold", transform: "uppercase", color: "muted", children: title }), _jsx(IconButton, { "aria-label": "Close", variant: "ghost", size: "sm", onClick: onClose, icon: _jsx("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) })] }), _jsx(Div, { className: `flex-1 ${__O.yAuto}`, children: children })] })] }));
80
+ const { hand } = useHandMode();
81
+ const { edgeClass, borderClass } = getSidebarOverlayClasses(hand);
82
+ return (_jsxs(Div, { className: "hidden lg:block", children: [_jsx(Div, { surface: "overlay-xs", className: "fixed inset-0 z-40 backdrop-blur-sm", onClick: onClose, "aria-hidden": "true" }), _jsxs(Stack, { border: "default", shadow: "2xl", role: "dialog", "aria-modal": "true", "aria-label": title, className: `fixed top-0 ${edgeClass} z-50 h-full w-64 ${borderClass}`, surface: "default", children: [_jsxs(Row, { border: "bottom-subtle", paddingY: "y-sm-tall", className: "shrink-0", padding: "x-md", align: "center", justify: "between", children: [_jsx(Span, { size: "xs", weight: "semibold", transform: "uppercase", color: "muted", children: title }), _jsx(IconButton, { "aria-label": "Close", variant: "ghost", size: "sm", onClick: onClose, icon: _jsx("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) })] }), _jsx(Div, { className: `flex-1 ${__O.yAuto}`, children: children })] })] }));
79
83
  }
80
84
  export function UserSidebar({ items, groups, mobileOpen = false, onCloseMobile, desktopOpen = false, onToggle, variant = "overlay", renderFooter }) {
81
85
  const pathname = usePathname();
82
86
  const close = onCloseMobile ?? (() => { });
87
+ const { hand } = useHandMode();
88
+ const rail = getSidebarRailClasses(hand);
83
89
  const [mounted, setMounted] = useState(false);
84
90
  useEffect(() => { setMounted(true); }, []);
85
91
  useEffect(() => {
@@ -91,7 +97,7 @@ export function UserSidebar({ items, groups, mobileOpen = false, onCloseMobile,
91
97
  // ── Persistent sidebar variant (desktop left slide-over, mobile bottom-sheet) ──────
92
98
  if (variant === "sidebar") {
93
99
  const handleToggle = onToggle ?? close;
94
- return (_jsxs(_Fragment, { children: [desktopOpen && (_jsx(Div, { surface: "overlay-xs", className: "hidden lg:block fixed inset-0 backdrop-blur-[2px] z-30", onClick: handleToggle, "aria-hidden": "true" })), _jsxs(Div, { className: `hidden lg:flex fixed left-0 z-40 transition-transform duration-300 top-[var(--header-height,3.5rem)] h-[calc(100vh-var(--header-height,3.5rem))] w-[13rem] ${desktopOpen ? "translate-x-0" : "-translate-x-[calc(100%-1.25rem)]"}`, children: [_jsxs(Stack, { border: "default", surface: "default", className: `flex-1 border-r dark:border-[var(--appkit-color-border)] ${__O.hidden}`, shadow: "xl", children: [_jsx(Div, { border: "bottom-subtle", paddingY: "y-sm-tall", className: "shrink-0", padding: "x-md", children: _jsx(Span, { size: "xs", weight: "semibold", transform: "uppercase", color: "muted", children: "My Account" }) }), _jsx(Div, { className: `flex-1 ${__O.yAuto}`, children: _jsx(DrawerContent, { groups: groups, items: items, activeHref: pathname }) }), renderFooter && _jsx(Div, { border: "top", padding: "inline", children: renderFooter() })] }), _jsx(SidebarCollapseToggle, { expanded: desktopOpen, onToggle: handleToggle })] }), _jsx(Div, { className: "lg:hidden", children: _jsx(BottomSheet, { open: mobileOpen, onClose: close, title: "My Account", children: _jsx(DrawerContent, { groups: groups, items: items, activeHref: pathname, onItemClick: close }) }) })] }));
100
+ return (_jsxs(_Fragment, { children: [desktopOpen && (_jsx(Div, { surface: "overlay-xs", className: "hidden lg:block fixed inset-0 backdrop-blur-[2px] z-30", onClick: handleToggle, "aria-hidden": "true" })), _jsxs(Div, { className: `hidden lg:flex fixed ${rail.edgeClass} z-40 transition-transform duration-300 top-[var(--header-height,3.5rem)] h-[calc(100vh-var(--header-height,3.5rem))] w-[13rem] ${desktopOpen ? "translate-x-0" : rail.collapsedTranslateClass}`, children: [_jsxs(Stack, { border: "default", surface: "default", className: `flex-1 ${rail.borderClass} dark:border-[var(--appkit-color-border)] ${__O.hidden}`, shadow: "xl", children: [_jsx(Div, { border: "bottom-subtle", paddingY: "y-sm-tall", className: "shrink-0", padding: "x-md", children: _jsx(Span, { size: "xs", weight: "semibold", transform: "uppercase", color: "muted", children: "My Account" }) }), _jsx(Div, { className: `flex-1 ${__O.yAuto}`, children: _jsx(DrawerContent, { groups: groups, items: items, activeHref: pathname }) }), renderFooter && _jsx(Div, { border: "top", padding: "inline", children: renderFooter() })] }), _jsx(SidebarCollapseToggle, { expanded: desktopOpen, onToggle: handleToggle, edge: hand })] }), _jsx(Div, { className: "lg:hidden", children: _jsx(BottomSheet, { open: mobileOpen, onClose: close, title: "My Account", children: _jsx(DrawerContent, { groups: groups, items: items, activeHref: pathname, onItemClick: close }) }) })] }));
95
101
  }
96
102
  // ── Overlay variant (default) — portal on desktop, bottom-sheet on mobile ─
97
103
  const content = (_jsx(DrawerContent, { groups: groups, items: items, activeHref: pathname, onItemClick: close }));
@@ -14,6 +14,7 @@ export interface UserProfile {
14
14
  uiPreferences?: {
15
15
  collapsedSections?: string[];
16
16
  dataViewMode?: "table" | "grid" | "list";
17
+ handMode?: "left" | "right";
17
18
  };
18
19
  }
19
20
  export interface UpdateCurrentProfileInput {
@@ -33,6 +34,7 @@ export interface UpdateCurrentProfileInput {
33
34
  uiPreferences?: {
34
35
  collapsedSections?: string[];
35
36
  dataViewMode?: "table" | "grid" | "list";
37
+ handMode?: "left" | "right";
36
38
  };
37
39
  }
38
40
  export declare function useCurrentProfile(options?: {
@@ -8,6 +8,8 @@ import { BottomSheet } from "../../layout/BottomSheet";
8
8
  import { SidebarCollapseToggle } from "../../../_internal/client/features/layout/SidebarCollapseToggle";
9
9
  import { useSidebarSearch } from "../../../_internal/client/features/layout/useSidebarSearch";
10
10
  import { findActiveNavGroup, findActiveNavItem } from "../../../_internal/client/features/layout/navActive";
11
+ import { getSidebarRailClasses, getSidebarOverlayClasses } from "../../../_internal/client/features/layout/sidebarPositionClasses";
12
+ import { useHandMode } from "../../../_internal/client/hand-mode";
11
13
  const __O = {
12
14
  hidden: "overflow-hidden",
13
15
  yAuto: "overflow-y-auto",
@@ -50,10 +52,14 @@ function GroupsContent({ groups, activePath, onItemClick, }) {
50
52
  })] }));
51
53
  }
52
54
  function DrawerPanel({ title, onClose, children, }) {
53
- return (_jsxs(Div, { className: "hidden lg:block", children: [_jsx(Div, { surface: "overlay-xs", className: "fixed inset-0 z-40 backdrop-blur-sm", onClick: onClose, "aria-hidden": "true" }), _jsxs(Stack, { border: "default", shadow: "2xl", role: "dialog", "aria-modal": "true", "aria-label": title, className: "fixed top-0 right-0 z-50 h-full w-64 border-l", surface: "default", children: [_jsxs(Row, { border: "bottom-subtle", paddingY: "y-sm-tall", className: "shrink-0", padding: "x-md", align: "center", justify: "between", children: [_jsx(Span, { size: "xs", weight: "semibold", transform: "uppercase", color: "muted", children: title }), _jsx(IconButton, { "aria-label": "Close", variant: "ghost", size: "sm", onClick: onClose, icon: _jsx("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) })] }), _jsx(Div, { className: `flex-1 ${__O.yAuto}`, children: children })] })] }));
55
+ const { hand } = useHandMode();
56
+ const { edgeClass, borderClass } = getSidebarOverlayClasses(hand);
57
+ return (_jsxs(Div, { className: "hidden lg:block", children: [_jsx(Div, { surface: "overlay-xs", className: "fixed inset-0 z-40 backdrop-blur-sm", onClick: onClose, "aria-hidden": "true" }), _jsxs(Stack, { border: "default", shadow: "2xl", role: "dialog", "aria-modal": "true", "aria-label": title, className: `fixed top-0 ${edgeClass} z-50 h-full w-64 ${borderClass}`, surface: "default", children: [_jsxs(Row, { border: "bottom-subtle", paddingY: "y-sm-tall", className: "shrink-0", padding: "x-md", align: "center", justify: "between", children: [_jsx(Span, { size: "xs", weight: "semibold", transform: "uppercase", color: "muted", children: title }), _jsx(IconButton, { "aria-label": "Close", variant: "ghost", size: "sm", onClick: onClose, icon: _jsx("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) })] }), _jsx(Div, { className: `flex-1 ${__O.yAuto}`, children: children })] })] }));
54
58
  }
55
59
  export function AdminSidebar({ renderNavItems, renderHeader, renderFooter, activePath = "", groups, mobileOpen = false, desktopOpen = false, variant = "overlay", onCloseMobile, onToggle, }) {
56
60
  const close = onCloseMobile ?? (() => { });
61
+ const { hand } = useHandMode();
62
+ const rail = getSidebarRailClasses(hand);
57
63
  const [mounted, setMounted] = useState(false);
58
64
  useEffect(() => { setMounted(true); }, []);
59
65
  useEffect(() => {
@@ -64,7 +70,7 @@ export function AdminSidebar({ renderNavItems, renderHeader, renderFooter, activ
64
70
  const mobileNavContent = groups ? (_jsx(GroupsContent, { groups: groups, activePath: activePath, onItemClick: close })) : (_jsxs(Nav, { "aria-label": "Admin sidebar", padding: "y-sm", children: [renderHeader?.(), renderNavItems?.(activePath), renderFooter?.()] }));
65
71
  if (variant === "sidebar") {
66
72
  const handleToggle = onToggle ?? close;
67
- return (_jsxs(_Fragment, { children: [desktopOpen && (_jsx(Div, { surface: "overlay-xs", className: "hidden lg:block fixed inset-0 backdrop-blur-[2px] z-30", onClick: handleToggle, "aria-hidden": "true" })), _jsxs(Div, { className: `hidden lg:flex fixed left-0 z-40 transition-transform duration-300 top-[var(--header-height,3.5rem)] h-[calc(100vh-var(--header-height,3.5rem))] w-[18rem] ${desktopOpen ? "translate-x-0" : "-translate-x-[calc(100%-1.25rem)]"}`, children: [_jsxs(Stack, { border: "default", surface: "sidePanel", className: `flex-1 border-r dark:border-[var(--appkit-color-border)] ${__O.hidden}`, shadow: "xl", children: [_jsx(Div, { border: "bottom-subtle", paddingY: "y-sm-tall", className: "shrink-0", padding: "x-md", children: renderHeader ? renderHeader() : _jsx(Span, { size: "xs", weight: "semibold", transform: "uppercase", color: "muted", children: "Admin Panel" }) }), _jsx(Div, { className: `flex-1 ${__O.yAuto}`, children: navContent }), renderFooter && _jsx(Div, { border: "top", padding: "inline", children: renderFooter() })] }), _jsx(SidebarCollapseToggle, { expanded: desktopOpen, onToggle: handleToggle })] }), _jsx(Div, { className: "lg:hidden", children: _jsx(BottomSheet, { open: mobileOpen, onClose: close, title: "Admin Panel", children: mobileNavContent }) })] }));
73
+ return (_jsxs(_Fragment, { children: [desktopOpen && (_jsx(Div, { surface: "overlay-xs", className: "hidden lg:block fixed inset-0 backdrop-blur-[2px] z-30", onClick: handleToggle, "aria-hidden": "true" })), _jsxs(Div, { className: `hidden lg:flex fixed ${rail.edgeClass} z-40 transition-transform duration-300 top-[var(--header-height,3.5rem)] h-[calc(100vh-var(--header-height,3.5rem))] w-[18rem] ${desktopOpen ? "translate-x-0" : rail.collapsedTranslateClass}`, children: [_jsxs(Stack, { border: "default", surface: "sidePanel", className: `flex-1 ${rail.borderClass} dark:border-[var(--appkit-color-border)] ${__O.hidden}`, shadow: "xl", children: [_jsx(Div, { border: "bottom-subtle", paddingY: "y-sm-tall", className: "shrink-0", padding: "x-md", children: renderHeader ? renderHeader() : _jsx(Span, { size: "xs", weight: "semibold", transform: "uppercase", color: "muted", children: "Admin Panel" }) }), _jsx(Div, { className: `flex-1 ${__O.yAuto}`, children: navContent }), renderFooter && _jsx(Div, { border: "top", padding: "inline", children: renderFooter() })] }), _jsx(SidebarCollapseToggle, { expanded: desktopOpen, onToggle: handleToggle, edge: hand })] }), _jsx(Div, { className: "lg:hidden", children: _jsx(BottomSheet, { open: mobileOpen, onClose: close, title: "Admin Panel", children: mobileNavContent }) })] }));
68
74
  }
69
75
  return (_jsxs(_Fragment, { children: [mounted && mobileOpen &&
70
76
  createPortal(_jsx(DrawerPanel, { title: "Admin Panel", onClose: close, children: navContent }), document.body), _jsx(Div, { className: "lg:hidden", children: _jsx(BottomSheet, { open: mobileOpen, onClose: close, title: "Admin Panel", children: mobileNavContent }) })] }));
@@ -122,6 +122,9 @@ export interface UserDocument extends BaseDocument {
122
122
  * stay unique). Absent/missing IDs default to collapsed for a section the
123
123
  * user has never interacted with. See useCollapsedSections. */
124
124
  collapsedSections?: string[];
125
+ /** Which side CTA panels/drawers/sidebars dock to and where close buttons
126
+ * sit. Absent/"right" = default (right-docked). "left" = left-hand mode. */
127
+ handMode?: "left" | "right";
125
128
  };
126
129
  }
127
130
  export interface UserSoftBan {
@@ -1,8 +1,10 @@
1
+ "use client";
1
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
3
  import { Aside, Button, Div, Heading, Row, Span, Stack, Text, TextLink } from "../../../ui";
3
4
  import { MediaImage } from "../../media/MediaImage";
4
5
  import { formatCurrency } from "../../../utils/number.formatter";
5
6
  import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
7
+ import { useHandMode } from "../../../_internal/client/hand-mode";
6
8
  const __P = {
7
9
  p4: "p-[var(--appkit-space-4)]",
8
10
  };
@@ -18,7 +20,8 @@ export function CartItemRow({ item, onQtyChange, onRemove, href, isOutOfStock =
18
20
  .join(", ") })), _jsxs(Row, { justify: "between", children: [_jsx(Text, { className: "text-[var(--appkit-color-text)]", weight: "semibold", children: formatCurrency(item.meta.price * item.quantity, item.meta.currency) }), onQtyChange && !isOutOfStock && (_jsxs(Row, { gap: "sm", children: [_jsx(Button, { rounded: "full", onClick: () => onQtyChange(item.id, item.quantity - 1), disabled: item.quantity <= 1, variant: "outline", size: "sm", className: "h-7 w-7 justify-center border border-neutral-200 border-[var(--appkit-color-border)] text-[length:var(--appkit-text-sm)] disabled:opacity-40", children: "\u2212" }), _jsx(Span, { size: "sm", className: "min-w-[1.5rem]", align: "center", children: item.quantity }), _jsx(Button, { rounded: "full", onClick: () => onQtyChange(item.id, item.quantity + 1), variant: "outline", size: "sm", className: "h-7 w-7 justify-center border border-neutral-200 border-[var(--appkit-color-border)] text-[length:var(--appkit-text-sm)]", children: "+" })] })), isOutOfStock && (_jsxs(Span, { size: "xs", color: "muted", children: ["Qty: ", item.quantity] }))] })] }), onRemove && (_jsx(Button, { onClick: () => onRemove(item.id), variant: "ghost", size: "sm", "aria-label": ACTIONS.CART["remove-item"].ariaLabel, className: CLS_REMOVE_BTN, children: "\u2715" }))] }));
19
21
  }
20
22
  export function CartDrawer({ isOpen, onClose, items, subtotal = 0, currency, isLoading, onQtyChange, onRemove, onCheckout, labels = {}, }) {
23
+ const { hand } = useHandMode();
21
24
  if (!isOpen)
22
25
  return null;
23
- return (_jsxs(_Fragment, { children: [_jsx(Div, { surface: "overlay-xs", role: "presentation", className: "fixed inset-0 z-40", onClick: onClose }), _jsx(Aside, { surface: "sidePanel", shadow: "xl", className: "fixed inset-y-0 right-0 z-50 w-full max-w-sm", children: _jsxs(Stack, { className: "h-full", children: [_jsxs(Row, { justify: "between", border: "bottom", className: __P.p4, children: [_jsx(Heading, { level: 2, size: "lg", weight: "semibold", children: labels.title ?? "Cart" }), _jsx(Button, { onClick: onClose, variant: "ghost", size: "sm", "aria-label": "Close cart", textColor: "faint", className: "hover:text-[var(--appkit-color-text)] dark:hover:text-[var(--appkit-color-text)]", children: "\u2715" })] }), _jsx(Stack, { className: `flex-1 ${__O.yAuto} ${__P.p4}`, gap: "3", children: isLoading ? (_jsx(Row, { justify: "center", padding: "y-3xl", children: _jsx(Div, { className: "h-8 w-8 animate-spin border-2 border-neutral-300 border-t-neutral-800 dark:border-t-zinc-200", rounded: "full" }) })) : items.length === 0 ? (_jsx(Text, { paddingY: "3xl", className: "text-[var(--appkit-color-text-muted)]", size: "sm", align: "center", children: labels.empty ?? "Your cart is empty" })) : (items.map((item) => (_jsx(CartItemRow, { item: item, onQtyChange: onQtyChange, onRemove: onRemove }, item.id)))) }), items.length > 0 && (_jsxs(Stack, { border: "top", className: __P.p4, gap: "md", children: [_jsxs(Row, { textSize: "sm", justify: "between", children: [_jsx(Span, { className: "text-[var(--appkit-color-text-muted)]", children: labels.subtotal ?? "Subtotal" }), _jsx(Span, { weight: "semibold", children: formatCurrency(subtotal, currency) })] }), onCheckout && (_jsx(Button, { rounded: "xl", onClick: onCheckout, variant: "primary", textSize: "sm", className: "w-full bg-neutral-900 py-[0.75rem] font-[600] text-white transition hover:bg-neutral-800", children: labels.checkout ?? ACTIONS.CART["checkout"].label }))] }))] }) })] }));
26
+ return (_jsxs(_Fragment, { children: [_jsx(Div, { surface: "overlay-xs", role: "presentation", className: "fixed inset-0 z-40", onClick: onClose }), _jsx(Aside, { surface: "sidePanel", shadow: "xl", className: `fixed inset-y-0 ${hand === "left" ? "left-0" : "right-0"} z-50 w-full max-w-sm`, children: _jsxs(Stack, { className: "h-full", children: [_jsxs(Row, { justify: "between", border: "bottom", className: __P.p4, reverse: hand === "left", children: [_jsx(Heading, { level: 2, size: "lg", weight: "semibold", children: labels.title ?? "Cart" }), _jsx(Button, { onClick: onClose, variant: "ghost", size: "sm", "aria-label": "Close cart", textColor: "faint", className: "hover:text-[var(--appkit-color-text)] dark:hover:text-[var(--appkit-color-text)]", children: "\u2715" })] }), _jsx(Stack, { className: `flex-1 ${__O.yAuto} ${__P.p4}`, gap: "3", children: isLoading ? (_jsx(Row, { justify: "center", padding: "y-3xl", children: _jsx(Div, { className: "h-8 w-8 animate-spin border-2 border-neutral-300 border-t-neutral-800 dark:border-t-zinc-200", rounded: "full" }) })) : items.length === 0 ? (_jsx(Text, { paddingY: "3xl", className: "text-[var(--appkit-color-text-muted)]", size: "sm", align: "center", children: labels.empty ?? "Your cart is empty" })) : (items.map((item) => (_jsx(CartItemRow, { item: item, onQtyChange: onQtyChange, onRemove: onRemove }, item.id)))) }), items.length > 0 && (_jsxs(Stack, { border: "top", className: __P.p4, gap: "md", children: [_jsxs(Row, { textSize: "sm", justify: "between", children: [_jsx(Span, { className: "text-[var(--appkit-color-text-muted)]", children: labels.subtotal ?? "Subtotal" }), _jsx(Span, { weight: "semibold", children: formatCurrency(subtotal, currency) })] }), onCheckout && (_jsx(Button, { rounded: "xl", onClick: onCheckout, variant: "primary", textSize: "sm", className: "w-full bg-neutral-900 py-[0.75rem] font-[600] text-white transition hover:bg-neutral-800", children: labels.checkout ?? ACTIONS.CART["checkout"].label }))] }))] }) })] }));
24
27
  }
@@ -8,6 +8,7 @@ import { Button, Div, Heading, HorizontalScroller, Row, Section, Text } from "..
8
8
  import { ROUTES } from "../../../next";
9
9
  import { resolveMediaUrl } from "../../../utils/media-url";
10
10
  import { useTopBrands } from "../hooks/useTopBrands";
11
+ import { useHandMode } from "../../../_internal/client/hand-mode";
11
12
  const __O = {
12
13
  hidden: "overflow-hidden",
13
14
  };
@@ -32,6 +33,7 @@ function BrandLogo({ brand }) {
32
33
  }
33
34
  export function BrandsSection({ title = "Top Brands", subtitle, limit = 12, viewMoreHref, viewMoreLabel = "All brands →", className = "", initialItems, cta, filters, autoScroll = true, scrollInterval = 4000, loop = true, }) {
34
35
  const themed = { textPrimary: THEMED_TEXT_PRIMARY };
36
+ const { hand } = useHandMode();
35
37
  const { data: allBrands = [], isLoading } = useTopBrands(limit, { initialData: initialItems });
36
38
  const [activeFilter, setActiveFilter] = useState("all");
37
39
  // Available filter chips: "All" + "Featured" when featuredOnly filter is configured
@@ -43,5 +45,5 @@ export function BrandsSection({ title = "Top Brands", subtitle, limit = 12, view
43
45
  });
44
46
  if (!isLoading && allBrands.length === 0)
45
47
  return null;
46
- return (_jsx(Section, { className: `${className}`, surface: "subtle", paddingY: "y-3xl", paddingX: "x-md", children: _jsxs(Div, { className: "mx-auto max-w-7xl", children: [_jsxs(Row, { className: "mb-6", align: "center", justify: "between", children: [_jsxs(_Fragment, { children: [_jsx(Heading, { level: 2, className: themed.textPrimary, size: "2xl", mdSize: "3xl", weight: "bold", children: title }), subtitle && (_jsx(Text, { variant: "secondary", className: "mt-1", size: "sm", children: subtitle }))] }), !cta && viewMoreHref && (_jsx(Link, { href: viewMoreHref, className: "text-[length:var(--appkit-text-sm)] font-medium text-[var(--appkit-color-primary)] hover:underline", children: viewMoreLabel }))] }), showFeaturedChip && !isLoading && (_jsxs(Row, { wrap: true, gap: "sm", className: "mb-4", children: [_jsx(BrandFilterChip, { label: "All", active: activeFilter === "all", onClick: () => setActiveFilter("all") }), _jsx(BrandFilterChip, { label: "Featured", active: activeFilter === "featured", onClick: () => setActiveFilter(activeFilter === "featured" ? "all" : "featured") })] })), isLoading ? (_jsx(Div, { layout: "flex", gap: "3", className: `${__O.hidden}`, children: Array.from({ length: 8 }).map((_, i) => (_jsx(Div, { className: "h-32 w-36 flex-none animate-pulse md:h-40", surface: "subtle", rounded: "xl" }, i))) })) : (_jsx(HorizontalScroller, { items: brands, renderItem: (brand) => _jsx(BrandLogo, { brand: brand }), keyExtractor: (brand) => brand.id, gap: 12, showArrows: true, showScrollbar: false, autoScroll: autoScroll, autoScrollInterval: scrollInterval, pauseOnHover: true, loop: loop })), cta && !isLoading && (_jsx(Div, { className: "mt-6 text-left", children: _jsx(Link, { href: cta.href, className: CTA_CLASSES[cta.variant], children: cta.label }) }))] }) }));
48
+ return (_jsx(Section, { className: `${className}`, surface: "subtle", paddingY: "y-3xl", paddingX: "x-md", children: _jsxs(Div, { className: "mx-auto max-w-7xl", children: [_jsxs(Row, { className: "mb-6", align: "center", justify: "between", reverse: hand === "left", children: [_jsxs(_Fragment, { children: [_jsx(Heading, { level: 2, className: themed.textPrimary, size: "2xl", mdSize: "3xl", weight: "bold", children: title }), subtitle && (_jsx(Text, { variant: "secondary", className: "mt-1", size: "sm", children: subtitle }))] }), !cta && viewMoreHref && (_jsx(Link, { href: viewMoreHref, className: "text-[length:var(--appkit-text-sm)] font-medium text-[var(--appkit-color-primary)] hover:underline", children: viewMoreLabel }))] }), showFeaturedChip && !isLoading && (_jsxs(Row, { wrap: true, gap: "sm", className: "mb-4", children: [_jsx(BrandFilterChip, { label: "All", active: activeFilter === "all", onClick: () => setActiveFilter("all") }), _jsx(BrandFilterChip, { label: "Featured", active: activeFilter === "featured", onClick: () => setActiveFilter(activeFilter === "featured" ? "all" : "featured") })] })), isLoading ? (_jsx(Div, { layout: "flex", gap: "3", className: `${__O.hidden}`, children: Array.from({ length: 8 }).map((_, i) => (_jsx(Div, { className: "h-32 w-36 flex-none animate-pulse md:h-40", surface: "subtle", rounded: "xl" }, i))) })) : (_jsx(HorizontalScroller, { items: brands, renderItem: (brand) => _jsx(BrandLogo, { brand: brand }), keyExtractor: (brand) => brand.id, gap: 12, showArrows: true, showScrollbar: false, autoScroll: autoScroll, autoScrollInterval: scrollInterval, pauseOnHover: true, loop: loop })), cta && !isLoading && (_jsx(Div, { className: "mt-6 text-left", children: _jsx(Link, { href: cta.href, className: CTA_CLASSES[cta.variant], children: cta.label }) }))] }) }));
47
49
  }
@@ -1,3 +1,12 @@
1
+ /**
2
+ * FeaturedBundlesSection — S-SBUNI-3 2026-05-13 rebuild.
3
+ *
4
+ * Rebuilt after SB-UNI-V deletion. Renders a horizontal grid of bundle
5
+ * cards driven by `initialItems` (CategoryDocument with categoryType:"bundle").
6
+ * Server-friendly — no data fetching here; the renderer passes pre-fetched
7
+ * items from `MarketplaceHomepageView.sectionData.bundles`.
8
+ */
9
+ "use client";
1
10
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
11
  import Link from "next/link";
3
12
  import { Badge, Div, Heading, Row, Section, Stack, Text } from "../../../ui";
@@ -6,6 +15,7 @@ import { ROUTES } from "../../../next/routing/route-map";
6
15
  import { formatCurrency } from "../../../utils/number.formatter";
7
16
  import { BUNDLE_COPY, BUNDLE_STOCK_VARIANT, } from "../../../_internal/shared/features/categories/bundle-copy";
8
17
  import { computeBundleDiscount } from "../../../_internal/shared/features/categories/bundle-pricing";
18
+ import { useHandMode } from "../../../_internal/client/hand-mode";
9
19
  import { BundleBuyNowCta } from "../../categories/components/BundleBuyNowCta";
10
20
  const __P = {
11
21
  p3: "p-[var(--appkit-space-3)]",
@@ -15,10 +25,11 @@ const __O = {
15
25
  };
16
26
  const PLACEHOLDER_EMOJI = "📦";
17
27
  export function FeaturedBundlesSection({ title = BUNDLE_COPY.featuredDefaultTitle, description, initialItems = [], className = "", onBuyNow, }) {
28
+ const { hand } = useHandMode();
18
29
  const items = initialItems.filter((c) => c.categoryType === "bundle");
19
30
  if (items.length === 0)
20
31
  return null;
21
- return (_jsx(Section, { className: `${className}`, padding: "y-2xl", children: _jsxs(Stack, { gap: "md", children: [_jsxs(Row, { gap: "sm", align: "center", justify: "between", wrap: true, children: [_jsxs(Stack, { gap: "xs", children: [_jsx(Heading, { level: 2, size: "2xl", weight: "semibold", color: "primary", children: title }), description && (_jsx(Text, { size: "sm", color: "muted", children: description }))] }), _jsx(Link, { href: String(ROUTES.PUBLIC.BUNDLES), className: "text-[length:var(--appkit-text-sm)] font-medium text-[var(--appkit-color-primary)] hover:underline", children: BUNDLE_COPY.featured.viewAll })] }), _jsx(Div, { layout: "grid", gap: "3", className: "grid-cols-2 sm:grid-cols-3 lg:grid-cols-4", children: items.map((bundle) => (_jsx(FeaturedBundleCard, { bundle: bundle, onBuyNow: onBuyNow }, bundle.id))) })] }) }));
32
+ return (_jsx(Section, { className: `${className}`, padding: "y-2xl", children: _jsxs(Stack, { gap: "md", children: [_jsxs(Row, { gap: "sm", align: "center", justify: "between", wrap: true, reverse: hand === "left", children: [_jsxs(Stack, { gap: "xs", children: [_jsx(Heading, { level: 2, size: "2xl", weight: "semibold", color: "primary", children: title }), description && (_jsx(Text, { size: "sm", color: "muted", children: description }))] }), _jsx(Link, { href: String(ROUTES.PUBLIC.BUNDLES), className: "text-[length:var(--appkit-text-sm)] font-medium text-[var(--appkit-color-primary)] hover:underline", children: BUNDLE_COPY.featured.viewAll })] }), _jsx(Div, { layout: "grid", gap: "3", className: "grid-cols-2 sm:grid-cols-3 lg:grid-cols-4", children: items.map((bundle) => (_jsx(FeaturedBundleCard, { bundle: bundle, onBuyNow: onBuyNow }, bundle.id))) })] }) }));
22
33
  }
23
34
  function FeaturedBundleCard({ bundle, onBuyNow }) {
24
35
  const stock = bundle.bundleStockStatus ?? "in_stock";
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { normalizeError } from "../../../errors/normalize";
3
- import { Anchor, Div, Heading, Row, Section, Span, Stack, Text } from "../../../ui";
3
+ import { Anchor, Div, Heading, HandModeRow, Row, Section, Span, Stack, Text } from "../../../ui";
4
4
  import { MediaImage } from "../../media/MediaImage";
5
5
  import { siteSettingsRepository } from "../../admin/repository/site-settings.repository";
6
6
  import { fetchGoogleReviews } from "../lib/google-reviews-fetcher";
@@ -68,5 +68,5 @@ export async function GoogleReviewsSection(config) {
68
68
  : "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-[var(--appkit-space-4)]";
69
69
  const cardClass = layout === "carousel" ? "flex-shrink-0 w-[85%] sm:w-72 snap-start" : "";
70
70
  const mapsHref = googleMapsUrl || (placeId ? `https://search.google.com/local/reviews?placeid=${placeId}` : "");
71
- return (_jsx(Section, { padding: "y-3xl", surface: "muted", children: _jsxs(Div, { className: "w-full max-w-7xl mx-auto", padding: "x-md", children: [_jsxs(Row, { className: "mb-8", align: "end", justify: "between", gap: "md", children: [_jsxs(_Fragment, { children: [_jsx(Heading, { level: 2, children: "What Our Customers Say" }), aggregateRating > 0 && (_jsxs(Row, { className: "mt-1", align: "center", gap: "sm", children: [_jsx(StarRating, { rating: Math.round(aggregateRating) }), _jsxs(Text, { size: "sm", variant: "muted", children: [aggregateRating.toFixed(1), " \u00B7 ", totalRatings.toLocaleString(), " reviews"] })] }))] }), linkToGoogleMaps && mapsHref && (_jsx(Anchor, { href: mapsHref, tone: "none", underline: "hover", className: CLS_GMAPS_LINK, children: "View on Google \u2192" }))] }), reviews.length === 0 ? (_jsx(EmptyState, {})) : (_jsx(Div, { className: gridClass, children: reviews.map((review, i) => (_jsx(Div, { className: cardClass, children: _jsx(ReviewCard, { review: review, showRating: showRating, showDate: showDate }) }, `${review.authorName}-${i}`))) }))] }) }));
71
+ return (_jsx(Section, { padding: "y-3xl", surface: "muted", children: _jsxs(Div, { className: "w-full max-w-7xl mx-auto", padding: "x-md", children: [_jsxs(HandModeRow, { className: "mb-8", align: "end", justify: "between", gap: "md", children: [_jsxs(_Fragment, { children: [_jsx(Heading, { level: 2, children: "What Our Customers Say" }), aggregateRating > 0 && (_jsxs(Row, { className: "mt-1", align: "center", gap: "sm", children: [_jsx(StarRating, { rating: Math.round(aggregateRating) }), _jsxs(Text, { size: "sm", variant: "muted", children: [aggregateRating.toFixed(1), " \u00B7 ", totalRatings.toLocaleString(), " reviews"] })] }))] }), linkToGoogleMaps && mapsHref && (_jsx(Anchor, { href: mapsHref, tone: "none", underline: "hover", className: CLS_GMAPS_LINK, children: "View on Google \u2192" }))] }), reviews.length === 0 ? (_jsx(EmptyState, {})) : (_jsx(Div, { className: gridClass, children: reviews.map((review, i) => (_jsx(Div, { className: cardClass, children: _jsx(ReviewCard, { review: review, showRating: showRating, showDate: showDate }) }, `${review.authorName}-${i}`))) }))] }) }));
72
72
  }