@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.
- package/dist/_internal/client/features/layout/DashboardLayoutClient.js +7 -2
- package/dist/_internal/client/features/layout/SidebarCollapseToggle.d.ts +5 -1
- package/dist/_internal/client/features/layout/SidebarCollapseToggle.js +8 -2
- package/dist/_internal/client/features/layout/sidebarPositionClasses.d.ts +20 -0
- package/dist/_internal/client/features/layout/sidebarPositionClasses.js +20 -0
- package/dist/_internal/client/hand-mode/HandModeProvider.d.ts +21 -0
- package/dist/_internal/client/hand-mode/HandModeProvider.js +81 -0
- package/dist/_internal/client/hand-mode/index.d.ts +2 -0
- package/dist/_internal/client/hand-mode/index.js +1 -0
- package/dist/_internal/server/features/checkout/actions.d.ts +43 -0
- package/dist/_internal/server/features/checkout/actions.js +172 -81
- package/dist/_internal/server/features/checkout/index.d.ts +1 -1
- package/dist/_internal/server/features/checkout/index.js +1 -1
- package/dist/_internal/server/features/seo/robots.js +3 -2
- package/dist/_internal/server/features/seo/sitemap.js +70 -5
- package/dist/_internal/shared/config/schema.d.ts +1 -0
- package/dist/_internal/shared/listing-types/classified/config.js +1 -1
- package/dist/_internal/shared/listing-types/digital-code/config.js +1 -1
- package/dist/_internal/shared/listing-types/live/config.js +1 -1
- package/dist/client.d.ts +4 -0
- package/dist/client.js +9 -0
- package/dist/constants/api-endpoints.d.ts +3 -0
- package/dist/constants/api-endpoints.js +1 -0
- package/dist/features/about/components/AboutView.js +2 -2
- package/dist/features/about/components/DeveloperView.js +2 -2
- package/dist/features/about/schemas/firestore.d.ts +1 -0
- package/dist/features/account/components/UserSidebar.js +8 -2
- package/dist/features/account/hooks/useProfile.d.ts +2 -0
- package/dist/features/admin/components/AdminSidebar.js +8 -2
- package/dist/features/auth/schemas/firestore.d.ts +3 -0
- package/dist/features/cart/components/CartDrawer.js +4 -1
- package/dist/features/homepage/components/BrandsSection.js +3 -1
- package/dist/features/homepage/components/FeaturedBundlesSection.js +12 -1
- package/dist/features/homepage/components/GoogleReviewsSection.js +2 -2
- package/dist/features/homepage/components/HeroCarousel.js +3 -1
- package/dist/features/homepage/components/SectionCarousel.js +5 -2
- package/dist/features/homepage/components/SocialFeedSection.js +2 -2
- package/dist/features/homepage/components/WhatsAppCommunitySection.js +3 -1
- package/dist/features/layout/BackToTop.js +3 -1
- package/dist/features/layout/SidebarLayout.js +7 -1
- package/dist/features/seller/components/SellerSidebar.js +8 -2
- package/dist/features/shell/QuickFormDrawer.js +3 -1
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +31 -0
- package/dist/hand-mode/index.d.ts +12 -0
- package/dist/hand-mode/index.js +11 -0
- package/dist/react/contexts/SessionContext.d.ts +5 -0
- package/dist/react/contexts/SessionContext.js +2 -0
- package/dist/seed/site-settings-seed-data.js +3 -2
- package/dist/server-entry.d.ts +1 -1
- package/dist/server-entry.js +1 -1
- package/dist/styles.css +2 -1
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/Drawer.js +7 -4
- package/dist/ui/components/FilterDrawer.js +1 -1
- package/dist/ui/components/HandModeRow.d.ts +12 -0
- package/dist/ui/components/HandModeRow.js +18 -0
- package/dist/ui/components/Layout.d.ts +5 -1
- package/dist/ui/components/Layout.js +2 -1
- package/dist/ui/components/Layout.style.css +1 -0
- package/dist/ui/components/ListingFilterDrawer.js +3 -1
- package/dist/ui/components/Modal.js +3 -1
- package/dist/ui/components/SideDrawer.js +8 -5
- package/dist/ui/components/SideModal.js +7 -4
- package/dist/ui/index.d.ts +1 -0
- package/dist/ui/index.js +1 -0
- 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", "
|
|
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
|
-
|
|
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}
|
|
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(
|
|
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,
|
|
@@ -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.
|
|
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.
|
|
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.
|
|
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
|
}
|
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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:
|
|
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(
|
|
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
|
}
|