@mohasinac/appkit 3.2.5 → 3.2.7
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/SidebarCollapseToggle.js +1 -1
- package/dist/_internal/client/features/lottery/LotteryAdminEditView.js +3 -3
- package/dist/_internal/server/features/products/data.d.ts +2 -1
- package/dist/_internal/server/jobs/core/countersReconcile.js +2 -1
- package/dist/_internal/shared/actions/action-registry.js +62 -0
- package/dist/_internal/shared/features/products/types.d.ts +3 -2
- package/dist/client.d.ts +2 -0
- package/dist/client.js +1 -0
- package/dist/constants/api-endpoints.d.ts +12 -0
- package/dist/constants/api-endpoints.js +4 -0
- package/dist/constants/field-names.d.ts +2 -0
- package/dist/constants/field-names.js +2 -0
- package/dist/features/about/components/PublicProfileView.js +8 -5
- package/dist/features/account/components/UserOffersPanel.js +0 -1
- package/dist/features/addresses/repository/addresses.repository.d.ts +14 -1
- package/dist/features/addresses/repository/addresses.repository.js +122 -1
- package/dist/features/addresses/schemas/firestore.d.ts +22 -3
- package/dist/features/addresses/schemas/firestore.js +25 -0
- package/dist/features/addresses/schemas/index.d.ts +2 -2
- package/dist/features/admin/components/AdminAddressClustersView.d.ts +5 -0
- package/dist/features/admin/components/AdminAddressClustersView.js +38 -0
- package/dist/features/admin/components/AdminAddressesView.d.ts +5 -0
- package/dist/features/admin/components/AdminAddressesView.js +137 -0
- package/dist/features/admin/components/AdminCarouselEditorView.js +1 -4
- package/dist/features/admin/components/AdminFulfillmentView.js +2 -3
- package/dist/features/admin/components/AdminOrderEditorView.js +1 -3
- package/dist/features/admin/components/AdminPaymentClustersView.d.ts +5 -0
- package/dist/features/admin/components/AdminPaymentClustersView.js +37 -0
- package/dist/features/admin/components/AdminPaymentMethodsView.d.ts +5 -0
- package/dist/features/admin/components/AdminPaymentMethodsView.js +130 -0
- package/dist/features/admin/components/AdminSiteSettingsView.js +2 -2
- package/dist/features/admin/components/index.d.ts +8 -0
- package/dist/features/admin/components/index.js +4 -0
- package/dist/features/admin/schemas/firestore.d.ts +3 -5
- package/dist/features/admin/types/product.types.d.ts +2 -1
- package/dist/features/auth/actions/profile-actions.d.ts +3 -1
- package/dist/features/auth/actions/profile-actions.js +29 -2
- package/dist/features/auth/schemas/firestore.d.ts +8 -10
- package/dist/features/cart/schemas/index.d.ts +6 -6
- package/dist/features/categories/components/CategoryBundlesListing.js +1 -3
- package/dist/features/checkout/schemas/firestore.d.ts +4 -4
- package/dist/features/copilot/components/AdminCopilotView.js +4 -4
- package/dist/features/events/schemas/firestore.d.ts +2 -2
- package/dist/features/grouped/schemas/firestore.d.ts +2 -4
- package/dist/features/history/hooks/useHistory.js +3 -2
- package/dist/features/homepage/actions/homepage-section-actions.d.ts +6 -6
- package/dist/features/homepage/actions/homepage-section-actions.js +2 -2
- package/dist/features/homepage/components/PromoGrid.js +1 -1
- package/dist/features/homepage/schemas/firestore.d.ts +15 -14
- package/dist/features/layout/AppLayoutShell.js +3 -18
- package/dist/features/layout/background-seed-defaults.d.ts +19 -0
- package/dist/features/layout/background-seed-defaults.js +14 -0
- package/dist/features/lottery/schemas/firestore.d.ts +2 -2
- package/dist/features/messages/hooks/useConversation.js +5 -13
- package/dist/features/messages/hooks/useConversations.js +0 -1
- package/dist/features/messages/schemas/firestore.d.ts +2 -2
- package/dist/features/orders/components/RefundHistoryTable.js +1 -3
- package/dist/features/orders/schemas/firestore.d.ts +2 -1
- package/dist/features/orders/schemas/index.d.ts +11 -7
- package/dist/features/orders/schemas/index.js +1 -1
- package/dist/features/payments/repository/saved-payment-methods.repository.d.ts +48 -0
- package/dist/features/payments/repository/saved-payment-methods.repository.js +236 -0
- package/dist/features/payments/schemas/index.d.ts +1 -0
- package/dist/features/payments/schemas/index.js +1 -0
- package/dist/features/payments/schemas/saved-methods-firestore.d.ts +63 -0
- package/dist/features/payments/schemas/saved-methods-firestore.js +60 -0
- package/dist/features/payments/server.d.ts +2 -0
- package/dist/features/payments/server.js +2 -0
- package/dist/features/products/actions/product-actions.d.ts +2 -1
- package/dist/features/products/api/[id]/route.js +1 -1
- package/dist/features/products/api/route.js +1 -1
- package/dist/features/products/components/CompareOverlay.d.ts +18 -1
- package/dist/features/products/components/CompareOverlay.js +0 -16
- package/dist/features/products/components/ProductDetailActions.js +2 -1
- package/dist/features/products/components/ShowGroupSection.js +1 -3
- package/dist/features/products/repository/product-features.repository.js +1 -1
- package/dist/features/products/schemas/firestore.d.ts +3 -6
- package/dist/features/promotions/actions/coupon-actions.d.ts +2 -1
- package/dist/features/promotions/hooks/useCouponValidate.d.ts +2 -1
- package/dist/features/promotions/repository/coupons.repository.d.ts +2 -1
- package/dist/features/promotions/schemas/firestore.d.ts +4 -4
- package/dist/features/promotions/schemas/index.d.ts +6 -6
- package/dist/features/reviews/repository/reviews.repository.d.ts +12 -0
- package/dist/features/reviews/repository/reviews.repository.js +24 -0
- package/dist/features/reviews/schemas/firestore.d.ts +5 -1
- package/dist/features/reviews/schemas/firestore.js +2 -0
- package/dist/features/reviews/schemas/index.d.ts +6 -6
- package/dist/features/scams/components/ScamRegistryView.js +2 -2
- package/dist/features/scams/schemas/firestore.d.ts +6 -6
- package/dist/features/search/types/index.d.ts +3 -2
- package/dist/features/search/types/index.js +0 -1
- package/dist/features/seller/components/FulfillmentView.js +2 -3
- package/dist/features/seller/components/PrintCenterView.js +1 -3
- package/dist/features/seller/components/SellerAddressesView.js +7 -4
- package/dist/features/seller/components/SellerOffersPanel.js +0 -1
- package/dist/features/seller/components/SellerReviewsView.js +0 -1
- package/dist/features/seller/schemas/firestore.d.ts +2 -4
- package/dist/features/server-errors/schemas/firestore.d.ts +2 -2
- package/dist/features/store-extensions/schemas/firestore.d.ts +2 -2
- package/dist/features/stores/components/StoreScopedSearch.js +2 -4
- package/dist/features/stores/repository/store.repository.js +3 -1
- package/dist/features/stores/types/index.d.ts +2 -1
- package/dist/features/stores/types/index.js +0 -1
- package/dist/features/support/schemas/firestore.d.ts +2 -2
- package/dist/features/support/schemas/index.d.ts +8 -8
- package/dist/features/wishlist/schemas/index.d.ts +2 -2
- package/dist/features/wishlist/types/index.d.ts +2 -1
- package/dist/index.d.ts +9 -0
- package/dist/index.js +10 -0
- package/dist/next/routing/route-map.d.ts +8 -0
- package/dist/next/routing/route-map.js +4 -0
- package/dist/providers/auth-firebase/provider.js +3 -1
- package/dist/providers/auth-firebase/session.js +3 -1
- package/dist/repositories/index.d.ts +1 -0
- package/dist/repositories/index.js +1 -0
- package/dist/schemas/registry.d.ts +66 -66
- package/dist/schemas/webhooks/razorpay.d.ts +50 -50
- package/dist/security/index.d.ts +1 -1
- package/dist/security/index.js +1 -1
- package/dist/security/pii-schemas.d.ts +2 -0
- package/dist/security/pii-schemas.js +2 -0
- package/dist/seo/json-ld.d.ts +2 -1
- package/dist/styles.css +1 -1
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/Form.d.ts +3 -1
- package/dist/ui/components/Form.js +2 -1
- package/dist/ui/components/Radio.d.ts +7 -0
- package/dist/ui/components/Radio.js +7 -0
- package/dist/ui/components/surface-tokens.d.ts +4 -0
- package/dist/ui/components/surface-tokens.js +24 -0
- package/dist/ui/index.d.ts +2 -2
- package/dist/ui/index.js +1 -1
- package/dist/ui/rich-text/RichText.js +0 -1
- package/package.json +5 -2
|
@@ -231,7 +231,7 @@ export declare function meetsMinPriority(priority: NotificationPriority, minPrio
|
|
|
231
231
|
* `defaultLightThemeId` and `defaultDarkThemeId` decide which record is
|
|
232
232
|
* applied when the user's mode preference resolves to that mode.
|
|
233
233
|
*/
|
|
234
|
-
export
|
|
234
|
+
export type SiteSettingsThemeRecord = {
|
|
235
235
|
id: string;
|
|
236
236
|
name: string;
|
|
237
237
|
mode: "light" | "dark";
|
|
@@ -241,7 +241,7 @@ export interface SiteSettingsThemeRecord {
|
|
|
241
241
|
tokens: Record<string, string>;
|
|
242
242
|
/** Full gradient palette; values are complete CSS gradient strings. */
|
|
243
243
|
gradients: Record<string, string>;
|
|
244
|
-
}
|
|
244
|
+
};
|
|
245
245
|
export interface SiteSettingsTheme {
|
|
246
246
|
/** All available theme records — built-ins + admin-created. */
|
|
247
247
|
themes?: SiteSettingsThemeRecord[];
|
|
@@ -250,7 +250,7 @@ export interface SiteSettingsTheme {
|
|
|
250
250
|
/** Theme id applied when the user's resolved mode is `"dark"`. */
|
|
251
251
|
defaultDarkThemeId?: string;
|
|
252
252
|
}
|
|
253
|
-
export interface SiteSettingsDocument {
|
|
253
|
+
export interface SiteSettingsDocument extends BaseDocument {
|
|
254
254
|
id: "global";
|
|
255
255
|
siteName: string;
|
|
256
256
|
motto: string;
|
|
@@ -479,8 +479,6 @@ export interface SiteSettingsDocument {
|
|
|
479
479
|
* opt-in and require the corresponding credentials to be set.
|
|
480
480
|
*/
|
|
481
481
|
notificationChannels?: NotificationChannelConfig;
|
|
482
|
-
createdAt: Date;
|
|
483
|
-
updatedAt: Date;
|
|
484
482
|
}
|
|
485
483
|
export type FeatureFlagKey = keyof SiteSettingsDocument["featureFlags"];
|
|
486
484
|
export interface FeatureFlagMeta {
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* These types extend/complement the core ProductItem with admin-only fields
|
|
5
5
|
* for managing auctions, pre-orders, stock, pricing, and lifecycle.
|
|
6
6
|
*/
|
|
7
|
+
import type { ListingType } from "../../products/types";
|
|
7
8
|
export type AdminProductStatus = "draft" | "published" | "in_review" | "archived";
|
|
8
9
|
export type AdminProductDrawerMode = "create" | "edit" | "delete" | null;
|
|
9
10
|
/**
|
|
@@ -48,7 +49,7 @@ export interface AdminProduct {
|
|
|
48
49
|
insuranceCost?: number;
|
|
49
50
|
shippingPaidBy?: "seller" | "buyer";
|
|
50
51
|
/** Canonical listing-kind discriminator (SB1-G Phase 4 — booleans removed). */
|
|
51
|
-
listingType?:
|
|
52
|
+
listingType?: ListingType;
|
|
52
53
|
auctionEndDate?: string;
|
|
53
54
|
startingBid?: number;
|
|
54
55
|
currentBid?: number;
|
|
@@ -27,5 +27,7 @@ export declare function getPublicUserProfile(userId: string): Promise<Pick<UserD
|
|
|
27
27
|
/** Fetch approved reviews for a store. storeId === storeSlug in this project. */
|
|
28
28
|
export declare function getSellerReviews(storeId: string): Promise<Review[]>;
|
|
29
29
|
export declare function getProfileStoreProducts(storeId: string): Promise<import("../../products").ProductDocument[]>;
|
|
30
|
-
/** Approved reviews
|
|
30
|
+
/** Approved product reviews written by this user as a buyer. Excludes seller→buyer ratings. */
|
|
31
31
|
export declare function getReviewsAuthoredBy(userId: string): Promise<Review[]>;
|
|
32
|
+
/** Approved seller→buyer reviews received by this buyer. */
|
|
33
|
+
export declare function getReviewsReceivedBy(userId: string): Promise<Review[]>;
|
|
@@ -70,9 +70,36 @@ export async function getProfileStoreProducts(storeId) {
|
|
|
70
70
|
const products = await productRepository.findByStore(storeId);
|
|
71
71
|
return products.filter((p) => p.status === ProductStatusValues.PUBLISHED);
|
|
72
72
|
}
|
|
73
|
-
/** Approved reviews
|
|
73
|
+
/** Approved product reviews written by this user as a buyer. Excludes seller→buyer ratings. */
|
|
74
74
|
export async function getReviewsAuthoredBy(userId) {
|
|
75
|
-
const
|
|
75
|
+
const all = await reviewRepository.findApprovedByUser(userId).catch(() => []);
|
|
76
|
+
// reviewerRole:"seller" means this user rated a buyer — not a product review
|
|
77
|
+
const snapshot = all.filter((r) => r.reviewerRole !== "seller");
|
|
78
|
+
return snapshot.map((r) => ({
|
|
79
|
+
id: r.id,
|
|
80
|
+
productId: r.productId,
|
|
81
|
+
productTitle: r.productTitle,
|
|
82
|
+
userId: r.userId,
|
|
83
|
+
userName: maskPublicReview(r).userName,
|
|
84
|
+
userAvatar: r.userAvatar,
|
|
85
|
+
rating: r.rating,
|
|
86
|
+
title: r.title,
|
|
87
|
+
comment: r.comment,
|
|
88
|
+
images: r.images?.map((url) => ({ url })),
|
|
89
|
+
status: r.status,
|
|
90
|
+
helpfulCount: r.helpfulCount,
|
|
91
|
+
reportCount: r.reportCount,
|
|
92
|
+
verified: r.verified,
|
|
93
|
+
featured: r.featured,
|
|
94
|
+
createdAt: r.createdAt instanceof Date ? r.createdAt.toISOString() : undefined,
|
|
95
|
+
updatedAt: r.updatedAt instanceof Date ? r.updatedAt.toISOString() : undefined,
|
|
96
|
+
storeSlug: r.storeId,
|
|
97
|
+
storeName: r.storeName,
|
|
98
|
+
}));
|
|
99
|
+
}
|
|
100
|
+
/** Approved seller→buyer reviews received by this buyer. */
|
|
101
|
+
export async function getReviewsReceivedBy(userId) {
|
|
102
|
+
const snapshot = await reviewRepository.findByReviewee(userId).catch(() => []);
|
|
76
103
|
return snapshot.map((r) => ({
|
|
77
104
|
id: r.id,
|
|
78
105
|
productId: r.productId,
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import type { UserRole } from "../types";
|
|
8
8
|
import type { BannedAction, EmployeeGroup, Permission } from "../permissions/constants";
|
|
9
9
|
import { type GenerateUserIdInput } from "../../../utils/id-generators";
|
|
10
|
+
import type { BaseDocument } from "../../../_internal/shared/types/base-document";
|
|
10
11
|
/**
|
|
11
12
|
* Generic schema defaults for user-related data.
|
|
12
13
|
* Consumer-specific values (admin email, currency) belong in SiteConfig, not here.
|
|
@@ -31,8 +32,7 @@ export interface AvatarMetadata {
|
|
|
31
32
|
};
|
|
32
33
|
zoom: number;
|
|
33
34
|
}
|
|
34
|
-
export interface UserDocument {
|
|
35
|
-
id?: string;
|
|
35
|
+
export interface UserDocument extends BaseDocument {
|
|
36
36
|
uid: string;
|
|
37
37
|
email: string | null;
|
|
38
38
|
phoneNumber: string | null;
|
|
@@ -44,8 +44,6 @@ export interface UserDocument {
|
|
|
44
44
|
passwordHash?: string;
|
|
45
45
|
emailVerified: boolean;
|
|
46
46
|
disabled: boolean;
|
|
47
|
-
createdAt: Date;
|
|
48
|
-
updatedAt: Date;
|
|
49
47
|
storeId?: string;
|
|
50
48
|
storeSlug?: string;
|
|
51
49
|
storeStatus?: "pending" | "approved" | "rejected";
|
|
@@ -242,15 +240,15 @@ export declare const USER_FIELDS: {
|
|
|
242
240
|
readonly LOGIN_COUNT: "metadata.loginCount";
|
|
243
241
|
};
|
|
244
242
|
};
|
|
245
|
-
export
|
|
243
|
+
export type EmailVerificationTokenDocument = {
|
|
246
244
|
id?: string;
|
|
247
245
|
userId: string;
|
|
248
246
|
email: string;
|
|
249
247
|
token: string;
|
|
250
248
|
expiresAt: Date;
|
|
251
249
|
createdAt: Date;
|
|
252
|
-
}
|
|
253
|
-
export
|
|
250
|
+
};
|
|
251
|
+
export type PasswordResetTokenDocument = {
|
|
254
252
|
id?: string;
|
|
255
253
|
userId: string;
|
|
256
254
|
email: string;
|
|
@@ -259,7 +257,7 @@ export interface PasswordResetTokenDocument {
|
|
|
259
257
|
createdAt: Date;
|
|
260
258
|
used: boolean;
|
|
261
259
|
usedAt?: Date;
|
|
262
|
-
}
|
|
260
|
+
};
|
|
263
261
|
export declare const EMAIL_VERIFICATION_COLLECTION: "emailVerificationTokens";
|
|
264
262
|
export declare const PASSWORD_RESET_COLLECTION: "passwordResetTokens";
|
|
265
263
|
export declare const TOKEN_INDEXED_FIELDS: readonly ["userId", "email", "expiresAt", "used"];
|
|
@@ -287,7 +285,7 @@ export declare const TOKEN_FIELDS: {
|
|
|
287
285
|
readonly USED: "used";
|
|
288
286
|
readonly USED_AT: "usedAt";
|
|
289
287
|
};
|
|
290
|
-
export
|
|
288
|
+
export type SessionDocument = {
|
|
291
289
|
id: string;
|
|
292
290
|
userId: string;
|
|
293
291
|
deviceInfo?: {
|
|
@@ -307,7 +305,7 @@ export interface SessionDocument {
|
|
|
307
305
|
isActive: boolean;
|
|
308
306
|
revokedAt?: Date;
|
|
309
307
|
revokedBy?: string;
|
|
310
|
-
}
|
|
308
|
+
};
|
|
311
309
|
export declare const SESSION_COLLECTION: "sessions";
|
|
312
310
|
export declare const SESSION_INDEXED_FIELDS: readonly ["userId", "isActive", "expiresAt", "createdAt", "lastActivity"];
|
|
313
311
|
export declare const DEFAULT_SESSION_DATA: Partial<SessionDocument>;
|
|
@@ -66,8 +66,8 @@ export declare const cartItemSchema: z.ZodObject<{
|
|
|
66
66
|
slug?: string | undefined;
|
|
67
67
|
attributes?: Record<string, string> | undefined;
|
|
68
68
|
} | undefined;
|
|
69
|
-
sessionId?: string | undefined;
|
|
70
69
|
userId?: string | undefined;
|
|
70
|
+
sessionId?: string | undefined;
|
|
71
71
|
addedAt?: string | undefined;
|
|
72
72
|
}, {
|
|
73
73
|
id: string;
|
|
@@ -79,9 +79,9 @@ export declare const cartItemSchema: z.ZodObject<{
|
|
|
79
79
|
slug?: string | undefined;
|
|
80
80
|
attributes?: Record<string, string> | undefined;
|
|
81
81
|
} | undefined;
|
|
82
|
+
userId?: string | undefined;
|
|
82
83
|
sessionId?: string | undefined;
|
|
83
84
|
quantity?: number | undefined;
|
|
84
|
-
userId?: string | undefined;
|
|
85
85
|
addedAt?: string | undefined;
|
|
86
86
|
}>;
|
|
87
87
|
export declare const cartSummarySchema: z.ZodObject<{
|
|
@@ -122,8 +122,8 @@ export declare const cartSummarySchema: z.ZodObject<{
|
|
|
122
122
|
slug?: string | undefined;
|
|
123
123
|
attributes?: Record<string, string> | undefined;
|
|
124
124
|
} | undefined;
|
|
125
|
-
sessionId?: string | undefined;
|
|
126
125
|
userId?: string | undefined;
|
|
126
|
+
sessionId?: string | undefined;
|
|
127
127
|
addedAt?: string | undefined;
|
|
128
128
|
}, {
|
|
129
129
|
id: string;
|
|
@@ -135,9 +135,9 @@ export declare const cartSummarySchema: z.ZodObject<{
|
|
|
135
135
|
slug?: string | undefined;
|
|
136
136
|
attributes?: Record<string, string> | undefined;
|
|
137
137
|
} | undefined;
|
|
138
|
+
userId?: string | undefined;
|
|
138
139
|
sessionId?: string | undefined;
|
|
139
140
|
quantity?: number | undefined;
|
|
140
|
-
userId?: string | undefined;
|
|
141
141
|
addedAt?: string | undefined;
|
|
142
142
|
}>, "many">;
|
|
143
143
|
subtotal: z.ZodNumber;
|
|
@@ -156,8 +156,8 @@ export declare const cartSummarySchema: z.ZodObject<{
|
|
|
156
156
|
slug?: string | undefined;
|
|
157
157
|
attributes?: Record<string, string> | undefined;
|
|
158
158
|
} | undefined;
|
|
159
|
-
sessionId?: string | undefined;
|
|
160
159
|
userId?: string | undefined;
|
|
160
|
+
sessionId?: string | undefined;
|
|
161
161
|
addedAt?: string | undefined;
|
|
162
162
|
}[];
|
|
163
163
|
subtotal: number;
|
|
@@ -173,9 +173,9 @@ export declare const cartSummarySchema: z.ZodObject<{
|
|
|
173
173
|
slug?: string | undefined;
|
|
174
174
|
attributes?: Record<string, string> | undefined;
|
|
175
175
|
} | undefined;
|
|
176
|
+
userId?: string | undefined;
|
|
176
177
|
sessionId?: string | undefined;
|
|
177
178
|
quantity?: number | undefined;
|
|
178
|
-
userId?: string | undefined;
|
|
179
179
|
addedAt?: string | undefined;
|
|
180
180
|
}[];
|
|
181
181
|
subtotal: number;
|
|
@@ -87,7 +87,5 @@ export function CategoryBundlesListing({ initialBundles, brandName, onBuyNow, })
|
|
|
87
87
|
return (_jsx(Div, { className: "border-dashed text-center", padding: "y-4xl", rounded: "2xl", border: "default", children: _jsxs(Text, { color: "muted", children: ["No bundles available", brandName ? ` for ${brandName}` : "", " yet."] }) }));
|
|
88
88
|
}
|
|
89
89
|
return (_jsxs(Div, { className: "min-h-[40vh]", children: [_jsx(ListingToolbar, { filterCount: filterActiveCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search bundles...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, onSearchKeyDown: (e) => { if (e.key === "Enter")
|
|
90
|
-
commitSearch(); }, sortValue: sort, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, onResetAll: resetAll, hasActiveState: hasActiveState }), totalPages > 1 && (_jsx(Row, { border: "bottom", className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 backdrop-blur-sm", surface: "default", padding: "toolbar", justify: "center", children: _jsx(Pagination, { currentPage: safePage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsx(Div, { padding: "y-lg", children: pageItems.length === 0 ? (_jsxs(Text, { paddingY: "4xl", color: "muted", size: "sm", align: "center", children: ["No bundles match your search", brandName ? ` for ${brandName}` : "", "."] })) : (_jsx(Div, { layout: "grid", gap: "3", className: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3", children: pageItems.map((bundle) => (_jsxs(Stack, { children: [_jsx(MarketplaceBundleCard, { bundle: bundle }), onBuyNow && (
|
|
91
|
-
// audit-inline-style-ok: asymmetric border-radius (bottom corners only); no appkit rounded= variant covers partial corners
|
|
92
|
-
_jsx(Div, { border: "default", surface: "default", className: "-mt-px border-t border-t-0 border-[var(--appkit-color-border)] pt-[0.5rem] pb-[0.75rem]", padding: "x-sm", style: { borderBottomLeftRadius: "0.75rem", borderBottomRightRadius: "0.75rem" }, children: _jsx(BundleBuyNowCta, { bundleSlug: bundle.slug, outOfStock: bundle.bundleStockStatus === "out_of_stock", onBuyNow: onBuyNow, compact: true }) }))] }, bundle.id))) })) }), _jsx(FilterDrawer, { open: filterOpen, onOpen: openFilters, onClose: () => setFilterOpen(false), onApply: applyFilters, onReset: () => setPendingShowOutOfStock(false), activeCount: filterActiveCount, hideTrigger: true, children: _jsx(Stack, { className: `${__P.p4}`, gap: "md", children: _jsxs(Label, { layout: "flex", gap: "lg", className: "justify-[space-between] cursor-pointer", children: [_jsx(Span, { size: "sm", weight: "medium", color: "muted", children: "Show out-of-stock bundles" }), _jsx("button", { type: "button", role: "switch", "aria-checked": pendingShowOutOfStock, onClick: () => setPendingShowOutOfStock((v) => !v), className: `relative inline-flex h-5 w-9 flex-shrink-0 items-center rounded-full transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-1 ${pendingShowOutOfStock ? "bg-primary" : "bg-zinc-300 dark:bg-slate-600"}`, children: _jsx(Span, { className: `inline-block h-3.5 w-3.5 transform transition-transform duration-200 ${pendingShowOutOfStock ? "translate-x-[19px]" : "translate-x-[3px]"}`, rounded: "full", surface: "default", shadow: "sm" }) })] }) }) })] }));
|
|
90
|
+
commitSearch(); }, sortValue: sort, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, onResetAll: resetAll, hasActiveState: hasActiveState }), totalPages > 1 && (_jsx(Row, { border: "bottom", className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 backdrop-blur-sm", surface: "default", padding: "toolbar", justify: "center", children: _jsx(Pagination, { currentPage: safePage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsx(Div, { padding: "y-lg", children: pageItems.length === 0 ? (_jsxs(Text, { paddingY: "4xl", color: "muted", size: "sm", align: "center", children: ["No bundles match your search", brandName ? ` for ${brandName}` : "", "."] })) : (_jsx(Div, { layout: "grid", gap: "3", className: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3", children: pageItems.map((bundle) => (_jsxs(Stack, { children: [_jsx(MarketplaceBundleCard, { bundle: bundle }), onBuyNow && (_jsx(Div, { border: "default", surface: "default", className: "-mt-px border-t border-t-0 border-[var(--appkit-color-border)] pt-[0.5rem] pb-[0.75rem]", padding: "x-sm", roundedBottom: "lg", children: _jsx(BundleBuyNowCta, { bundleSlug: bundle.slug, outOfStock: bundle.bundleStockStatus === "out_of_stock", onBuyNow: onBuyNow, compact: true }) }))] }, bundle.id))) })) }), _jsx(FilterDrawer, { open: filterOpen, onOpen: openFilters, onClose: () => setFilterOpen(false), onApply: applyFilters, onReset: () => setPendingShowOutOfStock(false), activeCount: filterActiveCount, hideTrigger: true, children: _jsx(Stack, { className: `${__P.p4}`, gap: "md", children: _jsxs(Label, { layout: "flex", gap: "lg", className: "justify-[space-between] cursor-pointer", children: [_jsx(Span, { size: "sm", weight: "medium", color: "muted", children: "Show out-of-stock bundles" }), _jsx("button", { type: "button", role: "switch", "aria-checked": pendingShowOutOfStock, onClick: () => setPendingShowOutOfStock((v) => !v), className: `relative inline-flex h-5 w-9 flex-shrink-0 items-center rounded-full transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-1 ${pendingShowOutOfStock ? "bg-primary" : "bg-zinc-300 dark:bg-slate-600"}`, children: _jsx(Span, { className: `inline-block h-3.5 w-3.5 transform transition-transform duration-200 ${pendingShowOutOfStock ? "translate-x-[19px]" : "translate-x-[3px]"}`, rounded: "full", surface: "default", shadow: "sm" }) })] }) }) })] }));
|
|
93
91
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
export type FailedCheckoutReason = "otp_not_verified" | "consent_expired" | "stock_failed" | "address_not_found" | "cart_empty" | "validation_error" | "unknown";
|
|
6
6
|
export type FailedPaymentReason = "signature_mismatch" | "payment_cancelled" | "amount_mismatch" | "otp_not_verified" | "consent_expired" | "product_unavailable" | "stock_insufficient" | "unknown";
|
|
7
|
-
export
|
|
7
|
+
export type FailedCheckoutDocument = {
|
|
8
8
|
id: string;
|
|
9
9
|
uid: string;
|
|
10
10
|
addressId?: string;
|
|
@@ -15,8 +15,8 @@ export interface FailedCheckoutDocument {
|
|
|
15
15
|
/** Raw error message for ops — not shown to buyers. */
|
|
16
16
|
detail?: string;
|
|
17
17
|
createdAt: Date;
|
|
18
|
-
}
|
|
19
|
-
export
|
|
18
|
+
};
|
|
19
|
+
export type FailedPaymentDocument = {
|
|
20
20
|
id: string;
|
|
21
21
|
uid: string;
|
|
22
22
|
gatewayOrderId?: string;
|
|
@@ -28,6 +28,6 @@ export interface FailedPaymentDocument {
|
|
|
28
28
|
/** Raw error message for ops. */
|
|
29
29
|
detail?: string;
|
|
30
30
|
createdAt: Date;
|
|
31
|
-
}
|
|
31
|
+
};
|
|
32
32
|
export declare const FAILED_CHECKOUTS_COLLECTION: "failedCheckouts";
|
|
33
33
|
export declare const FAILED_PAYMENTS_COLLECTION: "failedPayments";
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect, useRef, useState } from "react";
|
|
4
4
|
import { useQuery } from "@tanstack/react-query";
|
|
5
|
-
import { Alert, Button, Div, Heading, Input, Row, Span, Stack, StackedViewShell, Text } from "../../../ui";
|
|
5
|
+
import { Alert, Button, Div, Form, Heading, Input, Row, Span, Stack, StackedViewShell, Text } from "../../../ui";
|
|
6
6
|
import { apiClient } from "../../../http";
|
|
7
7
|
import { useCopilotChat } from "../hooks/useCopilotChat";
|
|
8
8
|
const __P = {
|
|
@@ -20,11 +20,11 @@ function MessageBubble({ msg }) {
|
|
|
20
20
|
}
|
|
21
21
|
function renderCopilotChatPanel(props) {
|
|
22
22
|
const { messages, conversationId, isLoading, error, input, setInput, labels, messagesEndRef, handleSubmit } = props;
|
|
23
|
-
return (_jsxs(Stack, { className: "border border-neutral-200 h-[calc(100vh-300px)]", surface: "default", rounded: "xl", children: [_jsx(Div, { border: "bottom", className: __P.p3, children: _jsxs(Text, { className: "text-neutral-500 dark:text-neutral-400", size: "xs", weight: "medium", children: [labels?.conversationId ?? LBL_CONVERSATION_ID, ": ", conversationId] }) }), _jsxs(Stack, { className: `flex-1 ${__O.yAuto} ${__P.p4}`, gap: "md", children: [messages.length === 0 ? (_jsxs(Stack, { justify: "center", className: "h-full text-center", align: "center", children: [_jsx(Text, { weight: "medium", children: labels?.noMessages ?? "No messages yet" }), _jsx(Text, { variant: "secondary", className: "mt-1", size: "sm", children: labels?.noMessagesDesc ?? "Ask anything to get started" })] })) : null, messages.map((msg) => _jsx(MessageBubble, { msg: msg }, msg.id)), isLoading ? (_jsx(Row, { justify: "start", children: _jsx(Div, { paddingY: "y-xs-tall", surface: "muted", className: "", padding: "x-md", style: { borderRadius: "0.125rem 1.5rem 1.5rem 1.5rem" }, children: _jsx(Span, { size: "sm", className: "opacity-60 animate-pulse", children: "Thinking..." }) }) })) : null, error ? _jsx(Text, { className: CLS_ERROR_TEXT, children: labels?.errorLabel ?? "An error occurred. Please try again." }) : null, _jsx(Div, { ref: messagesEndRef })] }), _jsx(Div, { border: "top", className: __P.p3, children: _jsxs(
|
|
23
|
+
return (_jsxs(Stack, { className: "border border-neutral-200 h-[calc(100vh-300px)]", surface: "default", rounded: "xl", children: [_jsx(Div, { border: "bottom", className: __P.p3, children: _jsxs(Text, { className: "text-neutral-500 dark:text-neutral-400", size: "xs", weight: "medium", children: [labels?.conversationId ?? LBL_CONVERSATION_ID, ": ", conversationId] }) }), _jsxs(Stack, { className: `flex-1 ${__O.yAuto} ${__P.p4}`, gap: "md", children: [messages.length === 0 ? (_jsxs(Stack, { justify: "center", className: "h-full text-center", align: "center", children: [_jsx(Text, { weight: "medium", children: labels?.noMessages ?? "No messages yet" }), _jsx(Text, { variant: "secondary", className: "mt-1", size: "sm", children: labels?.noMessagesDesc ?? "Ask anything to get started" })] })) : null, messages.map((msg) => _jsx(MessageBubble, { msg: msg }, msg.id)), isLoading ? (_jsx(Row, { justify: "start", children: _jsx(Div, { paddingY: "y-xs-tall", surface: "muted", className: "", padding: "x-md", style: { borderRadius: "0.125rem 1.5rem 1.5rem 1.5rem" }, children: _jsx(Span, { size: "sm", className: "opacity-60 animate-pulse", children: "Thinking..." }) }) })) : null, error ? _jsx(Text, { className: CLS_ERROR_TEXT, children: labels?.errorLabel ?? "An error occurred. Please try again." }) : null, _jsx(Div, { ref: messagesEndRef })] }), _jsx(Div, { border: "top", className: __P.p3, children: _jsxs(Form, { onSubmit: handleSubmit, gap: "xs", className: "flex", children: [_jsx(Input, { type: "text", bare: true, value: input, onChange: (e) => setInput(e.target.value), placeholder: labels?.inputPlaceholder ?? "Ask anything...", disabled: isLoading, className: "flex-1 h-10" }), _jsx(Button, { type: "submit", variant: "primary", disabled: isLoading || !input.trim(), className: "shrink-0", children: isLoading ? labels?.sendingButton ?? "Sending..." : labels?.sendButton ?? "Send" })] }) })] }));
|
|
24
24
|
}
|
|
25
25
|
function renderCopilotHistoryPanel(props) {
|
|
26
26
|
const { historyQuery, conversationInput, setConversationInput, loadConversation, labels } = props;
|
|
27
|
-
return (_jsxs(Stack, { surface: "card", padding: "sm", gap: "3", children: [_jsx(Heading, { level: 3, size: "sm", weight: "semibold", children: labels?.historyTitle ?? "Conversation History" }), _jsxs(
|
|
27
|
+
return (_jsxs(Stack, { surface: "card", padding: "sm", gap: "3", children: [_jsx(Heading, { level: 3, size: "sm", weight: "semibold", children: labels?.historyTitle ?? "Conversation History" }), _jsxs(Form, { spacing: "sm", onSubmit: (e) => { e.preventDefault(); if (!conversationInput.trim())
|
|
28
28
|
return; loadConversation(conversationInput.trim()); }, children: [_jsx(Input, { label: labels?.conversationId ?? LBL_CONVERSATION_ID, value: conversationInput, onChange: (e) => setConversationInput(e.target.value), placeholder: "conv_..." }), _jsx(Button, { type: "submit", variant: "secondary", size: "sm", className: "w-full", children: labels?.loadConversation ?? "Load conversation" })] }), historyQuery.error ? (_jsx(Alert, { variant: "warning", title: "History unavailable", children: historyQuery.error instanceof Error ? historyQuery.error.message : "Could not load history" })) : null, _jsxs(Stack, { className: `max-h-72 ${__O.yAuto}`, gap: "sm", children: [(historyQuery.data?.messages ?? []).map((log, index) => (_jsxs(Div, { className: "border border-neutral-200", rounded: "lg", padding: "xs", children: [_jsx(Text, { className: "text-neutral-500 dark:text-neutral-400", size: "xs", weight: "medium", children: new Date(log.createdAt).toLocaleString() }), _jsxs(Text, { className: `mt-1`, truncate: 3, size: "sm", children: ["Q: ", log.prompt] }), _jsxs(Text, { className: `mt-1`, color: "muted", truncate: 3, size: "sm", children: ["A: ", log.response] })] }, `${log.createdAt}-${index}`))), !historyQuery.isLoading && (historyQuery.data?.messages?.length ?? 0) === 0 ? (_jsx(Text, { variant: "secondary", size: "sm", children: "No server history found for this conversation yet." })) : null] })] }));
|
|
29
29
|
}
|
|
30
30
|
export function AdminCopilotView({ endpoint, historyEndpoint = "/api/copilot/history", renderHeader, labels = {}, className = "", }) {
|
|
@@ -49,7 +49,7 @@ export function AdminCopilotView({ endpoint, historyEndpoint = "/api/copilot/his
|
|
|
49
49
|
};
|
|
50
50
|
return (_jsx(StackedViewShell, { portal: "admin", title: labels.title ?? "AI Copilot", className: className, sections: [
|
|
51
51
|
renderHeader ? (renderHeader(startNewConversation)) : (_jsxs(Row, { align: "center", justify: "between", gap: "3", children: [_jsx(Heading, { level: 2, children: labels.title ?? "AI Copilot" }), _jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: startNewConversation, children: labels.newConversation ?? "New Conversation" })] })),
|
|
52
|
-
_jsxs(Div, { layout: "grid", gap: "4", className: "grid-cols-1 lg:grid-cols-[2fr,1fr]", children: [_jsxs(Stack, { className: "border border-neutral-200 h-[calc(100vh-300px)]", surface: "default", rounded: "xl", children: [_jsx(Div, { border: "bottom", className: __P.p3, children: _jsxs(Text, { className: "text-neutral-500 dark:text-neutral-400", size: "xs", weight: "medium", children: [labels.conversationId ?? LBL_CONVERSATION_ID, ": ", conversationId] }) }), _jsxs(Stack, { className: `flex-1 ${__O.yAuto} ${__P.p4}`, gap: "md", children: [messages.length === 0 ? (_jsxs(Stack, { justify: "center", className: "h-full text-center", align: "center", children: [_jsx(Text, { weight: "medium", children: labels.noMessages ?? "No messages yet" }), _jsx(Text, { variant: "secondary", className: "mt-1", size: "sm", children: labels.noMessagesDesc ?? "Ask anything to get started" })] })) : null, messages.map((msg) => (_jsx(MessageBubble, { msg: msg }, msg.id))), isLoading ? (_jsx(Row, { justify: "start", children: _jsx(Div, { paddingY: "y-xs-tall", surface: "muted", className: "", padding: "x-md", style: { borderRadius: "0.125rem 1.5rem 1.5rem 1.5rem" }, children: _jsx(Span, { size: "sm", className: "opacity-60 animate-pulse", children: "Thinking..." }) }) })) : null, error ? (_jsx(Text, { className: CLS_ERROR_TEXT, children: labels.errorLabel ?? "An error occurred. Please try again." })) : null, _jsx(Div, { ref: messagesEndRef })] }), _jsx(Div, { border: "top", className: __P.p3, children: _jsxs(
|
|
52
|
+
_jsxs(Div, { layout: "grid", gap: "4", className: "grid-cols-1 lg:grid-cols-[2fr,1fr]", children: [_jsxs(Stack, { className: "border border-neutral-200 h-[calc(100vh-300px)]", surface: "default", rounded: "xl", children: [_jsx(Div, { border: "bottom", className: __P.p3, children: _jsxs(Text, { className: "text-neutral-500 dark:text-neutral-400", size: "xs", weight: "medium", children: [labels.conversationId ?? LBL_CONVERSATION_ID, ": ", conversationId] }) }), _jsxs(Stack, { className: `flex-1 ${__O.yAuto} ${__P.p4}`, gap: "md", children: [messages.length === 0 ? (_jsxs(Stack, { justify: "center", className: "h-full text-center", align: "center", children: [_jsx(Text, { weight: "medium", children: labels.noMessages ?? "No messages yet" }), _jsx(Text, { variant: "secondary", className: "mt-1", size: "sm", children: labels.noMessagesDesc ?? "Ask anything to get started" })] })) : null, messages.map((msg) => (_jsx(MessageBubble, { msg: msg }, msg.id))), isLoading ? (_jsx(Row, { justify: "start", children: _jsx(Div, { paddingY: "y-xs-tall", surface: "muted", className: "", padding: "x-md", style: { borderRadius: "0.125rem 1.5rem 1.5rem 1.5rem" }, children: _jsx(Span, { size: "sm", className: "opacity-60 animate-pulse", children: "Thinking..." }) }) })) : null, error ? (_jsx(Text, { className: CLS_ERROR_TEXT, children: labels.errorLabel ?? "An error occurred. Please try again." })) : null, _jsx(Div, { ref: messagesEndRef })] }), _jsx(Div, { border: "top", className: __P.p3, children: _jsxs(Form, { onSubmit: handleSubmit, gap: "xs", className: "flex", children: [_jsx(Input, { type: "text", bare: true, value: input, onChange: (event) => setInput(event.target.value), placeholder: labels.inputPlaceholder ?? "Ask anything...", disabled: isLoading, className: "flex-1 h-10" }), _jsx(Button, { type: "submit", variant: "primary", disabled: isLoading || !input.trim(), className: "shrink-0", children: isLoading ? labels.sendingButton ?? "Sending..." : labels.sendButton ?? "Send" })] }) })] }), _jsxs(Stack, { surface: "card", padding: "sm", gap: "3", children: [_jsx(Heading, { level: 3, size: "sm", weight: "semibold", children: labels.historyTitle ?? "Conversation History" }), _jsxs(Form, { spacing: "sm", onSubmit: (event) => {
|
|
53
53
|
event.preventDefault();
|
|
54
54
|
if (!conversationInput.trim())
|
|
55
55
|
return;
|
|
@@ -52,7 +52,7 @@ export interface EventDocument extends BaseDocument {
|
|
|
52
52
|
};
|
|
53
53
|
createdBy: string;
|
|
54
54
|
}
|
|
55
|
-
export
|
|
55
|
+
export type EventEntryDocument = {
|
|
56
56
|
id: string;
|
|
57
57
|
eventId: string;
|
|
58
58
|
userId?: string;
|
|
@@ -73,7 +73,7 @@ export interface EventEntryDocument {
|
|
|
73
73
|
spinWonAt?: Date;
|
|
74
74
|
ipAddress?: string;
|
|
75
75
|
submittedAt: Date;
|
|
76
|
-
}
|
|
76
|
+
};
|
|
77
77
|
export declare const EVENT_INDEXED_FIELDS: readonly ["type", "status", "startsAt", "endsAt", "createdAt"];
|
|
78
78
|
export declare const EVENT_ENTRY_INDEXED_FIELDS: readonly ["eventId", "userId", "reviewStatus", "submittedAt", "points"];
|
|
79
79
|
export declare const EVENT_FIELDS: {
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* active members drops below `minActiveMembers`, `visibilityStatus` flips
|
|
12
12
|
* to "hidden" (recomputed by `onProductStockChange`).
|
|
13
13
|
*/
|
|
14
|
+
import type { BaseDocument } from "../../../_internal/shared/types/base-document";
|
|
14
15
|
export type GroupTheme =
|
|
15
16
|
/** "Other anime figures you might like" */
|
|
16
17
|
"related"
|
|
@@ -23,8 +24,7 @@ export type GroupTheme =
|
|
|
23
24
|
/** Default / unspecified theme */
|
|
24
25
|
| "generic";
|
|
25
26
|
export type GroupVisibility = "visible" | "hidden";
|
|
26
|
-
export interface GroupedListingDocument {
|
|
27
|
-
id: string;
|
|
27
|
+
export interface GroupedListingDocument extends BaseDocument {
|
|
28
28
|
slug: string;
|
|
29
29
|
title: string;
|
|
30
30
|
description?: string;
|
|
@@ -44,8 +44,6 @@ export interface GroupedListingDocument {
|
|
|
44
44
|
brandSlug?: string;
|
|
45
45
|
categorySlug?: string;
|
|
46
46
|
createdBy: string;
|
|
47
|
-
createdAt: Date;
|
|
48
|
-
updatedAt: Date;
|
|
49
47
|
}
|
|
50
48
|
export declare const GROUPED_LISTINGS_COLLECTION: "groupedListings";
|
|
51
49
|
export declare const GROUPED_LISTINGS_INDEXED_FIELDS: readonly ["storeId", "brandSlug", "categorySlug", "isActive", "isFeatured", "groupTheme", "visibilityStatus", "createdAt"];
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
13
13
|
import { useToast } from "../../../ui";
|
|
14
14
|
import { ACCOUNT_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
15
|
+
import { normalizeError } from "../../../errors/normalize";
|
|
15
16
|
import { clearGuestHistory, getGuestHistory, removeGuestHistoryItem, trackGuestHistory, } from "../utils/guest-history";
|
|
16
17
|
const TRACK_DEBOUNCE_MS = 1500;
|
|
17
18
|
const sessionTracked = new Set();
|
|
@@ -77,8 +78,8 @@ export function useHistory(userId) {
|
|
|
77
78
|
setItems(getGuestHistory());
|
|
78
79
|
}
|
|
79
80
|
}
|
|
80
|
-
catch {
|
|
81
|
-
|
|
81
|
+
catch (err) {
|
|
82
|
+
void normalizeError(err);
|
|
82
83
|
}
|
|
83
84
|
}, [isAuth]);
|
|
84
85
|
useEffect(() => {
|
|
@@ -5,30 +5,30 @@ export declare const createSectionSchema: z.ZodObject<{
|
|
|
5
5
|
type: z.ZodString;
|
|
6
6
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
7
7
|
order: z.ZodDefault<z.ZodNumber>;
|
|
8
|
-
config: z.ZodOptional<z.
|
|
8
|
+
config: z.ZodOptional<z.ZodObject<{}, "passthrough", z.ZodTypeAny, z.objectOutputType<{}, z.ZodTypeAny, "passthrough">, z.objectInputType<{}, z.ZodTypeAny, "passthrough">>>;
|
|
9
9
|
}, "strip", z.ZodTypeAny, {
|
|
10
10
|
type: string;
|
|
11
11
|
order: number;
|
|
12
12
|
enabled: boolean;
|
|
13
|
-
config?:
|
|
13
|
+
config?: z.objectOutputType<{}, z.ZodTypeAny, "passthrough"> | undefined;
|
|
14
14
|
}, {
|
|
15
15
|
type: string;
|
|
16
16
|
order?: number | undefined;
|
|
17
17
|
enabled?: boolean | undefined;
|
|
18
|
-
config?:
|
|
18
|
+
config?: z.objectInputType<{}, z.ZodTypeAny, "passthrough"> | undefined;
|
|
19
19
|
}>;
|
|
20
20
|
export declare const updateSectionSchema: z.ZodObject<{
|
|
21
21
|
order: z.ZodOptional<z.ZodNumber>;
|
|
22
22
|
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
23
|
-
config: z.ZodOptional<z.
|
|
23
|
+
config: z.ZodOptional<z.ZodObject<{}, "passthrough", z.ZodTypeAny, z.objectOutputType<{}, z.ZodTypeAny, "passthrough">, z.objectInputType<{}, z.ZodTypeAny, "passthrough">>>;
|
|
24
24
|
}, "strip", z.ZodTypeAny, {
|
|
25
25
|
order?: number | undefined;
|
|
26
26
|
enabled?: boolean | undefined;
|
|
27
|
-
config?:
|
|
27
|
+
config?: z.objectOutputType<{}, z.ZodTypeAny, "passthrough"> | undefined;
|
|
28
28
|
}, {
|
|
29
29
|
order?: number | undefined;
|
|
30
30
|
enabled?: boolean | undefined;
|
|
31
|
-
config?:
|
|
31
|
+
config?: z.objectInputType<{}, z.ZodTypeAny, "passthrough"> | undefined;
|
|
32
32
|
}>;
|
|
33
33
|
export type CreateHomepageSectionInput = z.infer<typeof createSectionSchema>;
|
|
34
34
|
export type UpdateHomepageSectionInput = z.infer<typeof updateSectionSchema>;
|
|
@@ -6,12 +6,12 @@ export const createSectionSchema = z.object({
|
|
|
6
6
|
type: z.string().min(1),
|
|
7
7
|
enabled: z.boolean().default(true),
|
|
8
8
|
order: z.number().int().default(0),
|
|
9
|
-
config: z.
|
|
9
|
+
config: z.object({}).passthrough().optional(),
|
|
10
10
|
});
|
|
11
11
|
export const updateSectionSchema = z.object({
|
|
12
12
|
order: z.number().int().optional(),
|
|
13
13
|
enabled: z.boolean().optional(),
|
|
14
|
-
config: z.
|
|
14
|
+
config: z.object({}).passthrough().optional(),
|
|
15
15
|
});
|
|
16
16
|
// --- Actions --------------------------------------------------------------
|
|
17
17
|
export async function createHomepageSection(input, createdBy) {
|
|
@@ -10,5 +10,5 @@ export function PromoGrid({ banners, eyebrow = "LIMITED TIME", heading = "HOT DE
|
|
|
10
10
|
return null;
|
|
11
11
|
return (_jsx(Section, { paddingY: "y-4xl", className: "bg-[var(--dark-section-alt)] [border-top:1px_solid_rgba(255,255,255,0.06)] [border-bottom:1px_solid_rgba(255,255,255,0.06)]", children: _jsxs(Div, { className: "mx-auto max-w-7xl", padding: "x-md", children: [_jsx(Row, { wrap: true, align: "end", justify: "between", gap: "sm", className: "mb-7 gap-y-2", children: _jsxs(Div, { children: [_jsx(Text, { className: "mb-1 font-black uppercase tracking-widest text-[var(--color-red)] tracking-[0.18em]", size: "xs", children: eyebrow }), _jsx(Heading, { level: 2, className: "[font-family:var(--font-bangers,Bangers,cursive)] text-[clamp(1.6rem,4vw,2.4rem)] tracking-[0.08em] text-white leading-none", children: heading })] }) }), _jsx(Div, { layout: "grid", gap: "3", className: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3", children: banners.slice(0, 4).map((banner, i) => (_jsxs(Link, { href: banner.ctaUrl, className: `group relative block overflow-hidden bg-[var(--dark-section-card)] ${i === 0 ? "sm:row-span-2 min-h-[clamp(220px,32vh,480px)]" : "min-h-[clamp(130px,16vh,240px)]"}`, children: [_jsx(Image, { src: banner.image, alt: banner.title, fill: true, className: "object-cover transition-transform duration-500 group-hover:scale-105", sizes: i === 0
|
|
12
12
|
? "(max-width: 640px) 100vw, 40vw"
|
|
13
|
-
: "(max-width: 640px) 50vw, 30vw" }), _jsx(Div, { className: `absolute inset-0 ${i === 0 ? "[background:linear-gradient(to_top,rgba(0,0,0,0.9)_0%,rgba(0,0,0,0.3)_50%,transparent_100%)]" : "[background:linear-gradient(to_top,rgba(0,0,0,0.85)_0%,rgba(0,0,0,0.15)_100%)]"}` }), _jsx(Div, { className: "absolute inset-0 opacity-0 transition-opacity duration-300 group-hover:opacity-100
|
|
13
|
+
: "(max-width: 640px) 50vw, 30vw" }), _jsx(Div, { className: `absolute inset-0 ${i === 0 ? "[background:linear-gradient(to_top,rgba(0,0,0,0.9)_0%,rgba(0,0,0,0.3)_50%,transparent_100%)]" : "[background:linear-gradient(to_top,rgba(0,0,0,0.85)_0%,rgba(0,0,0,0.15)_100%)]"}` }), _jsx(Div, { className: "absolute inset-0 opacity-0 transition-opacity duration-300 group-hover:opacity-100 ring-2 ring-[var(--color-yellow)] ring-inset" }), _jsxs(Div, { className: `absolute bottom-0 left-0 right-0 ${__P.p4}`, children: [_jsx(Text, { className: `leading-tight [font-family:var(--font-bangers,Bangers,cursive)] tracking-[0.06em] text-white ${i === 0 ? "text-[1.35rem]" : "text-[1rem]"}`, children: banner.title }), _jsxs(Span, { layout: "inline-flex", gap: "xs", className: "mt-1 font-black uppercase text-[var(--color-yellow)]", size: "xs", children: [banner.ctaLabel, " \u2192"] })] })] }, banner.id))) })] }) }));
|
|
14
14
|
}
|
|
@@ -29,7 +29,7 @@ export interface CarouselBackground {
|
|
|
29
29
|
export type CarouselSlideHeight = "viewport" | "tall" | "medium";
|
|
30
30
|
export type CarouselHoverEffect = "scale" | "color" | "glow" | "none";
|
|
31
31
|
/** A content card placed in one of 6 grid zones (2 rows × 3 cols). */
|
|
32
|
-
export
|
|
32
|
+
export type CarouselCard = {
|
|
33
33
|
id: string;
|
|
34
34
|
/** Zone 1–6: row 1 = zones 1–3, row 2 = zones 4–6 */
|
|
35
35
|
zone: 1 | 2 | 3 | 4 | 5 | 6;
|
|
@@ -61,7 +61,7 @@ export interface CarouselCard {
|
|
|
61
61
|
gridRow?: 1 | 2;
|
|
62
62
|
/** Legacy — kept for backward compat; use `zone` instead */
|
|
63
63
|
gridCol?: 1 | 2 | 3;
|
|
64
|
-
}
|
|
64
|
+
};
|
|
65
65
|
export type GridCardCreateInput = Omit<CarouselCard, "id">;
|
|
66
66
|
/** A named carousel that holds up to MAX_SLIDES_PER_CAROUSEL slide references. */
|
|
67
67
|
export interface CarouselDocument extends BaseDocument {
|
|
@@ -174,14 +174,15 @@ export interface WelcomeSectionConfig {
|
|
|
174
174
|
ctaText?: string;
|
|
175
175
|
ctaLink?: string;
|
|
176
176
|
}
|
|
177
|
+
export type TrustIndicator = {
|
|
178
|
+
id: string;
|
|
179
|
+
icon: string;
|
|
180
|
+
title: string;
|
|
181
|
+
description: string;
|
|
182
|
+
};
|
|
177
183
|
export interface TrustIndicatorsSectionConfig {
|
|
178
184
|
title: string;
|
|
179
|
-
indicators:
|
|
180
|
-
id: string;
|
|
181
|
-
icon: string;
|
|
182
|
-
title: string;
|
|
183
|
-
description: string;
|
|
184
|
-
}>;
|
|
185
|
+
indicators: TrustIndicator[];
|
|
185
186
|
}
|
|
186
187
|
export interface SectionCTA {
|
|
187
188
|
label: string;
|
|
@@ -381,7 +382,7 @@ export type SocialFeedLayout = "grid" | "masonry" | "carousel";
|
|
|
381
382
|
* videoId is stored; thumbnail URL is computed at render time from the video ID.
|
|
382
383
|
* Do NOT store raw YouTube CDN thumbnail URLs in Firestore (they may expire).
|
|
383
384
|
*/
|
|
384
|
-
export
|
|
385
|
+
export type StaticSocialPost = {
|
|
385
386
|
id: string;
|
|
386
387
|
platform: SocialPlatform;
|
|
387
388
|
/** YouTube video ID (e.g. "dQw4w9WgXcQ"). Required when platform is "youtube". */
|
|
@@ -389,7 +390,7 @@ export interface StaticSocialPost {
|
|
|
389
390
|
/** YouTube channel display name shown as a chip. */
|
|
390
391
|
channelName?: string;
|
|
391
392
|
caption?: string;
|
|
392
|
-
}
|
|
393
|
+
};
|
|
393
394
|
export interface SocialFeedSectionConfig {
|
|
394
395
|
title: string;
|
|
395
396
|
subtitle?: string;
|
|
@@ -409,7 +410,7 @@ export interface SocialFeedSectionConfig {
|
|
|
409
410
|
posts?: StaticSocialPost[];
|
|
410
411
|
}
|
|
411
412
|
/** Normalised social post returned by /api/social-feed */
|
|
412
|
-
export
|
|
413
|
+
export type SocialPost = {
|
|
413
414
|
id: string;
|
|
414
415
|
platform: SocialPlatform;
|
|
415
416
|
imageUrl?: string;
|
|
@@ -427,7 +428,7 @@ export interface SocialPost {
|
|
|
427
428
|
comments?: number;
|
|
428
429
|
};
|
|
429
430
|
publishedAt?: string;
|
|
430
|
-
}
|
|
431
|
+
};
|
|
431
432
|
export interface PreOrdersSectionConfig {
|
|
432
433
|
title: string;
|
|
433
434
|
subtitle?: string;
|
|
@@ -461,7 +462,7 @@ export interface BannerSectionConfig {
|
|
|
461
462
|
clickable: boolean;
|
|
462
463
|
clickLink?: string;
|
|
463
464
|
}
|
|
464
|
-
export
|
|
465
|
+
export type CustomCardsCard = {
|
|
465
466
|
id: string;
|
|
466
467
|
image?: string;
|
|
467
468
|
imageAlt?: string;
|
|
@@ -479,7 +480,7 @@ export interface CustomCardsCard {
|
|
|
479
480
|
textColor?: string;
|
|
480
481
|
borderRadius?: "none" | "sm" | "md" | "lg" | "xl" | "full";
|
|
481
482
|
shadowLevel?: "none" | "sm" | "md" | "lg";
|
|
482
|
-
}
|
|
483
|
+
};
|
|
483
484
|
export interface CustomCardsSectionConfig {
|
|
484
485
|
title?: string;
|
|
485
486
|
layout: "grid" | "row" | "masonry";
|
|
@@ -27,23 +27,10 @@ import { TitleBar } from "./TitleBar";
|
|
|
27
27
|
import { BackToTop } from "./BackToTop";
|
|
28
28
|
import { useDashboardNav } from "./DashboardNavContext";
|
|
29
29
|
import { usePathname } from "next/navigation";
|
|
30
|
-
|
|
31
|
-
// written into siteSettings on first deploy and may be overridden at runtime.
|
|
32
|
-
// audit-hex-tokens-ok: seed defaults for siteSettings.theme.background
|
|
33
|
-
const DEFAULT_LIGHT_BG = {
|
|
34
|
-
type: "color",
|
|
35
|
-
value: "#f9fafb", // audit-hex-tokens-ok: zinc-50 seed default
|
|
36
|
-
overlay: { enabled: false, color: "#000000", opacity: 0 }, // audit-hex-tokens-ok: overlay seed default
|
|
37
|
-
};
|
|
30
|
+
import { OVERLAY_FALLBACK_COLOR, SEED_DARK_BG as DEFAULT_DARK_BG, SEED_LIGHT_BG as DEFAULT_LIGHT_BG } from "./background-seed-defaults";
|
|
38
31
|
const CLS_STAT_BOX = "flex flex-col items-center gap-1 p-2 bg-zinc-100 dark:bg-zinc-800 rounded-lg text-center";
|
|
39
32
|
const CLS_STAT_LABEL = "text-xs text-zinc-500 dark:text-zinc-400";
|
|
40
33
|
const CLS_LOGOUT_BTN = "flex w-full items-center gap-3 rounded-lg px-3 py-2 text-sm text-error transition-colors hover:bg-error-surface hover:text-error dark:text-error dark:hover:bg-error-surface dark:hover:text-error";
|
|
41
|
-
// audit-hex-tokens-ok: dark theme background seed default
|
|
42
|
-
const DEFAULT_DARK_BG = {
|
|
43
|
-
type: "color",
|
|
44
|
-
value: "#030712", // audit-hex-tokens-ok: gray-950 seed default
|
|
45
|
-
overlay: { enabled: false, color: "#000000", opacity: 0 }, // audit-hex-tokens-ok: overlay seed default
|
|
46
|
-
};
|
|
47
34
|
/** Collapsible accordion section for the public sidebar. */
|
|
48
35
|
function CollapsibleNavGroup({ title, children, }) {
|
|
49
36
|
const [open, setOpen] = useState(false);
|
|
@@ -152,8 +139,7 @@ export function AppLayoutShell({ children, navItems, sidebarItems = [], sidebarS
|
|
|
152
139
|
value: lightBackground.value,
|
|
153
140
|
overlay: {
|
|
154
141
|
enabled: lightBackground.overlay?.enabled ?? false,
|
|
155
|
-
|
|
156
|
-
color: lightBackground.overlay?.color ?? "#000000",
|
|
142
|
+
color: lightBackground.overlay?.color ?? OVERLAY_FALLBACK_COLOR,
|
|
157
143
|
opacity: lightBackground.overlay?.opacity ?? 0,
|
|
158
144
|
},
|
|
159
145
|
};
|
|
@@ -162,8 +148,7 @@ export function AppLayoutShell({ children, navItems, sidebarItems = [], sidebarS
|
|
|
162
148
|
value: darkBackground.value,
|
|
163
149
|
overlay: {
|
|
164
150
|
enabled: darkBackground.overlay?.enabled ?? false,
|
|
165
|
-
|
|
166
|
-
color: darkBackground.overlay?.color ?? "#000000",
|
|
151
|
+
color: darkBackground.overlay?.color ?? OVERLAY_FALLBACK_COLOR,
|
|
167
152
|
opacity: darkBackground.overlay?.opacity ?? 0,
|
|
168
153
|
},
|
|
169
154
|
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export declare const SEED_LIGHT_BG: {
|
|
2
|
+
type: "color";
|
|
3
|
+
value: string;
|
|
4
|
+
overlay: {
|
|
5
|
+
enabled: boolean;
|
|
6
|
+
color: string;
|
|
7
|
+
opacity: number;
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
export declare const SEED_DARK_BG: {
|
|
11
|
+
type: "color";
|
|
12
|
+
value: string;
|
|
13
|
+
overlay: {
|
|
14
|
+
enabled: boolean;
|
|
15
|
+
color: string;
|
|
16
|
+
opacity: number;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
export declare const OVERLAY_FALLBACK_COLOR = "#000000";
|