@mohasinac/appkit 2.8.6 → 2.8.8
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/server/features/checkout/actions.js +9 -9
- package/dist/_internal/server/jobs/core/cleanupRtdbEvents.js +1 -1
- package/dist/_internal/shared/listing-types/action-tracker.js +1 -1
- package/dist/client.d.ts +2 -0
- package/dist/client.js +1 -0
- package/dist/constants/api-endpoints.d.ts +3 -0
- package/dist/constants/api-endpoints.js +1 -0
- package/dist/core/hooks/useSyncManager.js +4 -4
- package/dist/features/account/components/NotificationPreferencesPanel.js +1 -1
- package/dist/features/admin/components/AdminSupportTicketDetailView.js +4 -4
- package/dist/features/blog/api/[slug]/route.js +1 -1
- package/dist/features/categories/components/CategoryDetailPageView.js +3 -1
- package/dist/features/layout/TitleBarLayout.js +2 -4
- package/dist/features/media/MediaImage.js +7 -5
- package/dist/features/media/MediaVideo.js +5 -2
- package/dist/features/media/upload/MediaUploadField.js +3 -1
- package/dist/features/pre-orders/components/PreOrderDetailPageView.js +3 -1
- package/dist/features/products/components/ShareButton.js +1 -1
- package/dist/features/products/components/ShowGroupSection.js +1 -1
- package/dist/features/products/components/SublistingCarouselSection.js +1 -1
- package/dist/features/promotions/components/CouponCard.js +1 -1
- package/dist/features/scams/actions/scam-actions.js +1 -1
- package/dist/features/seller/components/PhysicalLocationModal.js +5 -1
- package/dist/features/seller/components/SellerFeaturesView.js +2 -2
- package/dist/features/seller/components/SellerGoogleReviewsView.js +1 -1
- package/dist/features/seller/components/SellerPayoutRequestView.js +1 -1
- package/dist/features/seller/components/SellerPayoutSettingsView.js +1 -1
- package/dist/features/seller/components/SellerProductShell.js +1 -1
- package/dist/features/seller/components/SellerShippingView.js +1 -1
- package/dist/features/wishlist/hooks/useWishlistCount.js +1 -1
- package/dist/monitoring/server-logger.js +3 -3
- package/dist/providers/db-firebase/browser-stub.d.ts +1 -0
- package/dist/providers/db-firebase/browser-stub.js +1 -0
- package/dist/providers/db-firebase/rtdb-paths.d.ts +1 -0
- package/dist/providers/db-firebase/rtdb-paths.js +1 -0
- package/dist/seed/_helpers/media.d.ts +1 -0
- package/dist/seed/_helpers/media.js +26 -0
- package/dist/seed/blog-posts-seed-data.js +12 -11
- package/dist/seed/carousel-slides-seed-data.js +7 -6
- package/dist/seed/cart-seed-data.js +13 -12
- package/dist/seed/categories-seed-data.js +95 -94
- package/dist/seed/events-seed-data.js +9 -8
- package/dist/seed/factories/product.factory.js +4 -3
- package/dist/seed/factories/store.factory.js +3 -2
- package/dist/seed/factories/user.factory.js +2 -1
- package/dist/seed/grouped-listings-seed-data.js +4 -3
- package/dist/seed/homepage-sections-seed-data.js +2 -1
- package/dist/seed/products-auctions-seed-data.js +57 -56
- package/dist/seed/products-classifieds-seed-data.js +12 -11
- package/dist/seed/products-digital-codes-seed-data.js +11 -10
- package/dist/seed/products-live-items-seed-data.js +9 -8
- package/dist/seed/products-preorders-seed-data.js +6 -5
- package/dist/seed/products-prize-draws-seed-data.js +25 -24
- package/dist/seed/products-standard-seed-data.js +125 -124
- package/dist/seed/scammers-seed-data.js +2 -1
- package/dist/seed/site-settings-seed-data.js +6 -5
- package/dist/seed/store-extensions-seed-data.js +10 -9
- package/dist/seed/stores-seed-data.js +17 -16
- package/dist/seed/users-seed-data.js +22 -21
- package/dist/ui/components/BackgroundRenderer.js +6 -4
- package/dist/ui/rich-text/RichText.js +1 -1
- package/package.json +1 -1
|
@@ -246,7 +246,7 @@ export async function createCheckoutOrderAction(input) {
|
|
|
246
246
|
if (!addressId) {
|
|
247
247
|
failedCheckoutRepository
|
|
248
248
|
.logCheckout(uid, "address_not_found", "Address required for physical cart", { addressId: "", paymentMethod })
|
|
249
|
-
.catch(() => { });
|
|
249
|
+
.catch(() => { }); // audit-silent-catch-ok: fire-and-forget side effect must not abort checkout
|
|
250
250
|
throw new NotFoundError(ERROR_MESSAGES.CHECKOUT.ADDRESS_REQUIRED);
|
|
251
251
|
}
|
|
252
252
|
const addressDoc = await unitOfWork.addresses.findById(addressId);
|
|
@@ -257,7 +257,7 @@ export async function createCheckoutOrderAction(input) {
|
|
|
257
257
|
if (!resolvedAddress) {
|
|
258
258
|
failedCheckoutRepository
|
|
259
259
|
.logCheckout(uid, "address_not_found", "Address not found", { addressId, paymentMethod })
|
|
260
|
-
.catch(() => { });
|
|
260
|
+
.catch(() => { }); // audit-silent-catch-ok: fire-and-forget side effect must not abort checkout
|
|
261
261
|
throw new NotFoundError(ERROR_MESSAGES.CHECKOUT.ADDRESS_REQUIRED);
|
|
262
262
|
}
|
|
263
263
|
shippingAddress = formatShippingAddress(resolvedAddress);
|
|
@@ -395,7 +395,7 @@ export async function createCheckoutOrderAction(input) {
|
|
|
395
395
|
addressId,
|
|
396
396
|
paymentMethod,
|
|
397
397
|
})
|
|
398
|
-
.catch(() => { });
|
|
398
|
+
.catch(() => { }); // audit-silent-catch-ok: fire-and-forget side effect must not abort checkout
|
|
399
399
|
throw err;
|
|
400
400
|
}
|
|
401
401
|
const { available, unavailable, emailOtpUsed } = stockResult;
|
|
@@ -406,7 +406,7 @@ export async function createCheckoutOrderAction(input) {
|
|
|
406
406
|
paymentMethod,
|
|
407
407
|
cartItemCount: cartItems.length,
|
|
408
408
|
})
|
|
409
|
-
.catch(() => { });
|
|
409
|
+
.catch(() => { }); // audit-silent-catch-ok: fire-and-forget side effect must not abort checkout
|
|
410
410
|
throw new ValidationError(ERROR_MESSAGES.CHECKOUT.INSUFFICIENT_STOCK);
|
|
411
411
|
}
|
|
412
412
|
const appliedCoupons = cart.appliedCoupons ?? [];
|
|
@@ -650,7 +650,7 @@ export async function verifyAndPlaceRazorpayOrderAction(input) {
|
|
|
650
650
|
gatewayPaymentId: razorpay_payment_id,
|
|
651
651
|
addressId,
|
|
652
652
|
})
|
|
653
|
-
.catch(() => { });
|
|
653
|
+
.catch(() => { }); // audit-silent-catch-ok: fire-and-forget side effect must not abort checkout
|
|
654
654
|
throw new ValidationError(ERROR_MESSAGES.CHECKOUT.PAYMENT_FAILED);
|
|
655
655
|
}
|
|
656
656
|
const cart = await unitOfWork.carts.getOrCreate(uid);
|
|
@@ -692,7 +692,7 @@ export async function verifyAndPlaceRazorpayOrderAction(input) {
|
|
|
692
692
|
gatewayPaymentId: razorpay_payment_id,
|
|
693
693
|
addressId,
|
|
694
694
|
})
|
|
695
|
-
.catch(() => { });
|
|
695
|
+
.catch(() => { }); // audit-silent-catch-ok: fire-and-forget side effect must not abort checkout
|
|
696
696
|
throw new ApiError(403, "Order verification required. Please complete OTP verification and retry.");
|
|
697
697
|
}
|
|
698
698
|
}
|
|
@@ -743,7 +743,7 @@ export async function verifyAndPlaceRazorpayOrderAction(input) {
|
|
|
743
743
|
gatewayPaymentId: razorpay_payment_id,
|
|
744
744
|
addressId,
|
|
745
745
|
})
|
|
746
|
-
.catch(() => { });
|
|
746
|
+
.catch(() => { }); // audit-silent-catch-ok: fire-and-forget side effect must not abort checkout
|
|
747
747
|
throw new ValidationError(exists
|
|
748
748
|
? ERROR_MESSAGES.CHECKOUT.INSUFFICIENT_STOCK
|
|
749
749
|
: ERROR_MESSAGES.CHECKOUT.PRODUCT_UNAVAILABLE);
|
|
@@ -771,7 +771,7 @@ export async function verifyAndPlaceRazorpayOrderAction(input) {
|
|
|
771
771
|
amountRs: paidAmountRs,
|
|
772
772
|
addressId,
|
|
773
773
|
})
|
|
774
|
-
.catch(() => { });
|
|
774
|
+
.catch(() => { }); // audit-silent-catch-ok: fire-and-forget side effect must not abort checkout
|
|
775
775
|
throw new ValidationError(ERROR_MESSAGES.CHECKOUT.PAYMENT_FAILED);
|
|
776
776
|
}
|
|
777
777
|
}
|
|
@@ -977,7 +977,7 @@ export async function verifyAndPlaceRazorpayOrderAction(input) {
|
|
|
977
977
|
});
|
|
978
978
|
if (!isDigitalCartRazorpay && addressId) {
|
|
979
979
|
const otpRefForDelete = consentOtpRef(db, uid, addressId);
|
|
980
|
-
otpRefForDelete.delete().catch(() => { });
|
|
980
|
+
otpRefForDelete.delete().catch(() => { }); // audit-silent-catch-ok: fire-and-forget side effect must not abort checkout
|
|
981
981
|
}
|
|
982
982
|
if (emailsToSend.length > 0) {
|
|
983
983
|
Promise.all(emailsToSend.map((e) => sendOrderConfirmationEmail(e))).catch((err) => serverLogger.error("Order confirmation email error:", err));
|
|
@@ -16,7 +16,7 @@ export async function runCleanupRtdbEvents(ctx) {
|
|
|
16
16
|
if (staleAuthIds.length > 0) {
|
|
17
17
|
await Promise.all(staleAuthIds.flatMap((id) => [
|
|
18
18
|
rtdb.ref(`auth_events/${id}`).remove(),
|
|
19
|
-
auth.deleteUser(`auth_event_${id}`).catch(() => { }),
|
|
19
|
+
auth.deleteUser(`auth_event_${id}`).catch(() => { }), // audit-silent-catch-ok: idempotent cleanup; absent records are fine
|
|
20
20
|
]));
|
|
21
21
|
ctx.logger.info("Auth events removed", { count: staleAuthIds.length });
|
|
22
22
|
}
|
|
@@ -50,7 +50,7 @@ export const actionTracker = {
|
|
|
50
50
|
const result = currentSink(event);
|
|
51
51
|
// Swallow promise rejections so the caller path is never affected.
|
|
52
52
|
if (result && typeof result.catch === "function") {
|
|
53
|
-
result.catch(() => { });
|
|
53
|
+
result.catch(() => { }); // audit-silent-catch-ok: telemetry sink must not break callers
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
catch {
|
package/dist/client.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ export { registerClientRealtimeProvider, getClientRealtimeProvider, type IClient
|
|
|
3
3
|
export { registerClientSessionAdapter, getClientSessionAdapter, type IClientSessionAdapter, type AdapterAuthUser, type AuthUnsubscribe, } from "./contracts/client-session";
|
|
4
4
|
export { ConfirmDeleteModal } from "./ui/components/ConfirmDeleteModal";
|
|
5
5
|
export { EmptyState } from "./ui/components/EmptyState";
|
|
6
|
+
export { Skeleton } from "./ui/components/Skeleton";
|
|
7
|
+
export type { SkeletonProps } from "./ui/components/Skeleton";
|
|
6
8
|
export { Divider } from "./ui/components/Divider";
|
|
7
9
|
export { TabStrip } from "./ui/components/TabStrip";
|
|
8
10
|
export { SellerProductsView } from "./features/seller/components/SellerProductsView";
|
package/dist/client.js
CHANGED
|
@@ -11,6 +11,7 @@ export { registerClientSessionAdapter, getClientSessionAdapter, } from "./contra
|
|
|
11
11
|
export { ConfirmDeleteModal } from "./ui/components/ConfirmDeleteModal";
|
|
12
12
|
// S-STORE — pure-UI primitives + Seller view consumed by new dashboard pages
|
|
13
13
|
export { EmptyState } from "./ui/components/EmptyState";
|
|
14
|
+
export { Skeleton } from "./ui/components/Skeleton";
|
|
14
15
|
export { Divider } from "./ui/components/Divider";
|
|
15
16
|
export { TabStrip } from "./ui/components/TabStrip";
|
|
16
17
|
export { SellerProductsView } from "./features/seller/components/SellerProductsView";
|
|
@@ -321,6 +321,7 @@ export declare const BEFORE_AFTER_ENDPOINTS: {
|
|
|
321
321
|
};
|
|
322
322
|
export declare const DEMO_ENDPOINTS: {
|
|
323
323
|
readonly SEED: "/api/demo/seed";
|
|
324
|
+
readonly SEED_EVENT_INIT: "/api/demo/seed/event/init";
|
|
324
325
|
};
|
|
325
326
|
export declare const WHATSAPP_SELLER_ENDPOINTS: {
|
|
326
327
|
readonly SETTINGS: "/api/store/whatsapp-settings";
|
|
@@ -633,6 +634,7 @@ export declare const API_ENDPOINTS: {
|
|
|
633
634
|
};
|
|
634
635
|
readonly DEMO: {
|
|
635
636
|
readonly SEED: "/api/demo/seed";
|
|
637
|
+
readonly SEED_EVENT_INIT: "/api/demo/seed/event/init";
|
|
636
638
|
};
|
|
637
639
|
readonly WHATSAPP_SELLER: {
|
|
638
640
|
readonly SETTINGS: "/api/store/whatsapp-settings";
|
|
@@ -957,6 +959,7 @@ export declare const API_ROUTES: {
|
|
|
957
959
|
};
|
|
958
960
|
readonly DEMO: {
|
|
959
961
|
readonly SEED: "/api/demo/seed";
|
|
962
|
+
readonly SEED_EVENT_INIT: "/api/demo/seed/event/init";
|
|
960
963
|
};
|
|
961
964
|
readonly WHATSAPP_SELLER: {
|
|
962
965
|
readonly SETTINGS: "/api/store/whatsapp-settings";
|
|
@@ -437,6 +437,7 @@ export const BEFORE_AFTER_ENDPOINTS = {
|
|
|
437
437
|
// ---------------------------------------------------------------------------
|
|
438
438
|
export const DEMO_ENDPOINTS = {
|
|
439
439
|
SEED: "/api/demo/seed",
|
|
440
|
+
SEED_EVENT_INIT: "/api/demo/seed/event/init",
|
|
440
441
|
};
|
|
441
442
|
// ---------------------------------------------------------------------------
|
|
442
443
|
// WhatsApp Business (store-level settings + catalog sync)
|
|
@@ -30,12 +30,12 @@ async function replayCartOps() {
|
|
|
30
30
|
productId: op.productId,
|
|
31
31
|
quantity: op.quantity ?? 1,
|
|
32
32
|
})
|
|
33
|
-
.catch(() => { });
|
|
33
|
+
.catch(() => { }); // audit-silent-catch-ok: offline-replay; failed ops stay queued for next sync tick
|
|
34
34
|
}
|
|
35
35
|
else if (op.op === "remove") {
|
|
36
36
|
await apiClient
|
|
37
37
|
.delete(`/api/cart/${op.productId}`)
|
|
38
|
-
.catch(() => { });
|
|
38
|
+
.catch(() => { }); // audit-silent-catch-ok: offline-replay; failed ops stay queued for next sync tick
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
clearCartOps();
|
|
@@ -50,12 +50,12 @@ async function replayWishlistOps() {
|
|
|
50
50
|
if (op.op === "add") {
|
|
51
51
|
await apiClient
|
|
52
52
|
.post("/api/user/wishlist", { productId: op.itemId })
|
|
53
|
-
.catch(() => { });
|
|
53
|
+
.catch(() => { }); // audit-silent-catch-ok: offline-replay; failed ops stay queued for next sync tick
|
|
54
54
|
}
|
|
55
55
|
else if (op.op === "remove") {
|
|
56
56
|
await apiClient
|
|
57
57
|
.delete(`/api/user/wishlist/${op.itemId}`)
|
|
58
|
-
.catch(() => { });
|
|
58
|
+
.catch(() => { }); // audit-silent-catch-ok: offline-replay; failed ops stay queued for next sync tick
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
clearWishlistOps();
|
|
@@ -55,7 +55,7 @@ export function NotificationPreferencesPanel({ fetchUrl = "/api/user/notificatio
|
|
|
55
55
|
if (prefs?.types)
|
|
56
56
|
setTypes((prev) => ({ ...prev, ...prefs.types }));
|
|
57
57
|
})
|
|
58
|
-
.catch(() => { })
|
|
58
|
+
.catch(() => { }) // audit-silent-catch-ok: defaults render if fetch fails; user can still edit and save
|
|
59
59
|
.finally(() => setLoading(false));
|
|
60
60
|
}, [fetchUrl]);
|
|
61
61
|
const setChannel = useCallback((key, val) => {
|
|
@@ -126,7 +126,7 @@ export function AdminSupportTicketDetailView({ open, onClose, ticketId, subject,
|
|
|
126
126
|
internalNotes: (notes ? notes + "\n" : "") +
|
|
127
127
|
`[${new Date().toISOString()}] Applied store change to ${linkedStoreId}: status=${storeStatus}, verified=${storeIsVerified}, featured=${storeIsFeatured}`,
|
|
128
128
|
})
|
|
129
|
-
.catch(() => { });
|
|
129
|
+
.catch(() => { }); // audit-silent-catch-ok: ticket-note metadata; primary admin action already succeeded
|
|
130
130
|
invalidate();
|
|
131
131
|
},
|
|
132
132
|
onError: (err) => {
|
|
@@ -175,7 +175,7 @@ export function AdminSupportTicketDetailView({ open, onClose, ticketId, subject,
|
|
|
175
175
|
`[${new Date().toISOString()}] Lifted hard ban for ${linkedUserId}`,
|
|
176
176
|
status: "resolved",
|
|
177
177
|
})
|
|
178
|
-
.catch(() => { });
|
|
178
|
+
.catch(() => { }); // audit-silent-catch-ok: ticket-note metadata; primary admin action already succeeded
|
|
179
179
|
invalidate();
|
|
180
180
|
},
|
|
181
181
|
onError: (err) => {
|
|
@@ -196,7 +196,7 @@ export function AdminSupportTicketDetailView({ open, onClose, ticketId, subject,
|
|
|
196
196
|
`[${new Date().toISOString()}] Lifted create_support_tickets soft ban for ${linkedUserId}`,
|
|
197
197
|
status: "resolved",
|
|
198
198
|
})
|
|
199
|
-
.catch(() => { });
|
|
199
|
+
.catch(() => { }); // audit-silent-catch-ok: ticket-note metadata; primary admin action already succeeded
|
|
200
200
|
invalidate();
|
|
201
201
|
},
|
|
202
202
|
onError: (err) => {
|
|
@@ -232,7 +232,7 @@ export function AdminSupportTicketDetailView({ open, onClose, ticketId, subject,
|
|
|
232
232
|
.map((it) => `${it.productId}x${it.quantity}`)
|
|
233
233
|
.join(", ")}`,
|
|
234
234
|
})
|
|
235
|
-
.catch(() => { });
|
|
235
|
+
.catch(() => { }); // audit-silent-catch-ok: ticket-note metadata; primary admin action already succeeded
|
|
236
236
|
invalidate();
|
|
237
237
|
setOrderItemsOpen(false);
|
|
238
238
|
},
|
|
@@ -27,7 +27,7 @@ export async function GET(_request, context) {
|
|
|
27
27
|
return NextResponse.json({ success: false, error: "Blog post not found" }, { status: 404 });
|
|
28
28
|
}
|
|
29
29
|
// Increment view count fire-and-forget — must not block response
|
|
30
|
-
blogRepository.incrementViews(post.id).catch(() => { });
|
|
30
|
+
blogRepository.incrementViews(post.id).catch(() => { }); // audit-silent-catch-ok: view-count is best-effort analytics
|
|
31
31
|
// Related posts: same category, latest 3, excluding current
|
|
32
32
|
const related = (await blogRepository.findRelated(post.category, post.id, 3)).map(toBlogPost);
|
|
33
33
|
const body = { post: toBlogPost(post), related };
|
|
@@ -114,7 +114,9 @@ export async function CategoryDetailPageView({ slug }) {
|
|
|
114
114
|
const hasCover = Boolean(coverImage);
|
|
115
115
|
return (_jsxs(Main, { children: [_jsxs(Section, { className: `relative ${__O.hidden} ${hasCover ? "min-h-[220px] md:min-h-[280px]" : "bg-zinc-50 dark:bg-zinc-900"}`, children: [hasCover && (_jsxs(_Fragment, { children: [_jsx("div", { className: "absolute inset-0 bg-center bg-cover",
|
|
116
116
|
// audit-inline-style-ok: dynamic image URL
|
|
117
|
-
style: { backgroundImage: `url(${coverImage})` } }), _jsx(Div, { className: "absolute inset-0 bg-black/55" })] })), _jsxs(Div, { className: `relative z-10 max-w-7xl mx-auto px-4 ${hasCover ? "py-12" : "py-8"}`, children: [_jsxs(Nav, { className: "flex items-center gap-1.5 text-sm mb-4", "aria-label": "Breadcrumb", children: [_jsx(Link, { href: String(ROUTES.HOME), className: hasCover ? "text-white/70 hover:text-white transition-colors" : "text-zinc-500 dark:text-zinc-400 hover:text-primary-600 transition-colors", children: "Home" }), _jsx(Span, { className: hasCover ? "text-white/40" : "text-zinc-400", children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORIES), className: hasCover ? "text-white/70 hover:text-white transition-colors" : "text-zinc-500 dark:text-zinc-400 hover:text-primary-600 transition-colors", children: "Categories" }), _jsx(Span, { className: hasCover ? "text-white/40" : "text-zinc-400", children: "/" }), _jsx(Span, { weight: "medium", className: hasCover ? "text-white" : "text-zinc-900 dark:text-zinc-100", children: category?.name ?? slug })] }), _jsx(Heading, { level: 1, className: `text-3xl md:text-4xl font-bold mb-2 ${hasCover ? "text-white" : "text-zinc-900 dark:text-zinc-50"}`, children: category?.name ?? slug }), category?.description && typeof category.description === "string" && !category.description.startsWith("{") && (_jsx(Text, { className: `text-base max-w-2xl mb-4 ${hasCover ? "text-white/80" : "text-zinc-600 dark:text-zinc-400"}`, children: category.description })), _jsxs(Div, { className: "flex flex-wrap gap-2", children: [productCount > 0 && (_jsxs(Span, { size: "xs", weight: "medium", className: `inline-flex items-center gap-1 px-2.5 py-1 rounded-full ${hasCover ? "bg-white/20 text-white backdrop-blur-sm" : "bg-primary/10 text-primary-700 dark:text-primary-400"}`, children: [productCount.toLocaleString(), " ", productCount === 1 ? "product" : "products"] })), auctionCount > 0 && (_jsxs(Span, { size: "xs", weight: "medium", className: `inline-flex items-center gap-1 px-2.5 py-1 rounded-full ${hasCover ? "bg-white/20 text-white backdrop-blur-sm" : "bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400"}`, children: [auctionCount.toLocaleString(), " ", auctionCount === 1 ? "auction" : "auctions"] })), preOrderCount > 0 && (_jsxs(Span, { size: "xs", weight: "medium", className: `inline-flex items-center gap-1 px-2.5 py-1 rounded-full ${hasCover ? "bg-white/20 text-white backdrop-blur-sm" : "bg-violet-100 text-violet-700 dark:bg-violet-900/30 dark:text-violet-400"}`, children: [preOrderCount.toLocaleString(), " ", preOrderCount === 1 ? "pre-order" : "pre-orders"] })), storeCount > 0 && (_jsxs(Span, { size: "xs", weight: "medium", className: `inline-flex items-center gap-1 px-2.5 py-1 rounded-full ${hasCover ? "bg-white/20 text-white backdrop-blur-sm" : "bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400"}`, children: [storeCount.toLocaleString(), " ", storeCount === 1 ? "store" : "stores"] }))] })] })] }), childCategories.length > 0 && (_jsx(Section, { surface: "default", className: "border-b border-zinc-100 dark:border-zinc-800", children:
|
|
117
|
+
style: { backgroundImage: `url(${coverImage})` } }), _jsx(Div, { className: "absolute inset-0 bg-black/55" })] })), _jsxs(Div, { className: `relative z-10 max-w-7xl mx-auto px-4 ${hasCover ? "py-12" : "py-8"}`, children: [_jsxs(Nav, { className: "flex items-center gap-1.5 text-sm mb-4", "aria-label": "Breadcrumb", children: [_jsx(Link, { href: String(ROUTES.HOME), className: hasCover ? "text-white/70 hover:text-white transition-colors" : "text-zinc-500 dark:text-zinc-400 hover:text-primary-600 transition-colors", children: "Home" }), _jsx(Span, { className: hasCover ? "text-white/40" : "text-zinc-400", children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORIES), className: hasCover ? "text-white/70 hover:text-white transition-colors" : "text-zinc-500 dark:text-zinc-400 hover:text-primary-600 transition-colors", children: "Categories" }), _jsx(Span, { className: hasCover ? "text-white/40" : "text-zinc-400", children: "/" }), _jsx(Span, { weight: "medium", className: hasCover ? "text-white" : "text-zinc-900 dark:text-zinc-100", children: category?.name ?? slug })] }), _jsx(Heading, { level: 1, className: `text-3xl md:text-4xl font-bold mb-2 ${hasCover ? "text-white" : "text-zinc-900 dark:text-zinc-50"}`, children: category?.name ?? slug }), category?.description && typeof category.description === "string" && !category.description.startsWith("{") && (_jsx(Text, { className: `text-base max-w-2xl mb-4 ${hasCover ? "text-white/80" : "text-zinc-600 dark:text-zinc-400"}`, children: category.description })), _jsxs(Div, { className: "flex flex-wrap gap-2", children: [productCount > 0 && (_jsxs(Span, { size: "xs", weight: "medium", className: `inline-flex items-center gap-1 px-2.5 py-1 rounded-full ${hasCover ? "bg-white/20 text-white backdrop-blur-sm" : "bg-primary/10 text-primary-700 dark:text-primary-400"}`, children: [productCount.toLocaleString(), " ", productCount === 1 ? "product" : "products"] })), auctionCount > 0 && (_jsxs(Span, { size: "xs", weight: "medium", className: `inline-flex items-center gap-1 px-2.5 py-1 rounded-full ${hasCover ? "bg-white/20 text-white backdrop-blur-sm" : "bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400"}`, children: [auctionCount.toLocaleString(), " ", auctionCount === 1 ? "auction" : "auctions"] })), preOrderCount > 0 && (_jsxs(Span, { size: "xs", weight: "medium", className: `inline-flex items-center gap-1 px-2.5 py-1 rounded-full ${hasCover ? "bg-white/20 text-white backdrop-blur-sm" : "bg-violet-100 text-violet-700 dark:bg-violet-900/30 dark:text-violet-400"}`, children: [preOrderCount.toLocaleString(), " ", preOrderCount === 1 ? "pre-order" : "pre-orders"] })), storeCount > 0 && (_jsxs(Span, { size: "xs", weight: "medium", className: `inline-flex items-center gap-1 px-2.5 py-1 rounded-full ${hasCover ? "bg-white/20 text-white backdrop-blur-sm" : "bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400"}`, children: [storeCount.toLocaleString(), " ", storeCount === 1 ? "store" : "stores"] }))] })] })] }), childCategories.length > 0 && (_jsx(Section, { surface: "default", className: "border-b border-zinc-100 dark:border-zinc-800", children: _jsx(Div, { className: "max-w-7xl mx-auto px-4 py-3", children: _jsx(Div, { className: `flex gap-2.5 ${__O.xAuto} pb-1`,
|
|
118
|
+
// audit-inline-style-ok: dynamic CSS
|
|
119
|
+
style: { scrollbarWidth: "none" }, children: childCategories.map((child) => (_jsxs(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(child.slug)), className: "flex-shrink-0 flex items-center gap-1.5 rounded-full border border-zinc-200 dark:border-zinc-700 bg-zinc-50 dark:bg-zinc-800 px-4 py-1.5 text-sm font-medium text-zinc-700 dark:text-zinc-200 hover:border-primary hover:text-primary transition-colors whitespace-nowrap", children: [child.display?.icon && (_jsx(Span, { className: "leading-none", children: child.display.icon })), child.name, (child.metrics?.productCount ?? 0) > 0 && (_jsx(Span, { size: "xs", className: "text-zinc-400 dark:text-zinc-400", children: (child.metrics?.productCount ?? 0).toLocaleString() }))] }, child.id))) }) }) })), _jsx(Section, { className: "py-6", children: _jsx(Container, { size: "xl", children: _jsx(CategoryDetailTabs, { categorySlug: slug, categoryId: category?.id, initialProductsData: productsResult ?? undefined, initialBundles: bundlesResult ?? [], initialStores: categoryStores, counts: {
|
|
118
120
|
products: productCount,
|
|
119
121
|
auctions: auctionCount,
|
|
120
122
|
preOrders: preOrderCount,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import Link from "next/link";
|
|
3
|
-
import { BlockHeader, Button, Div, Row, SiteLogo, Span } from "../../ui";
|
|
3
|
+
import { Avatar, BlockHeader, Button, Div, Row, SiteLogo, Span } from "../../ui";
|
|
4
4
|
/** Shared icon-button class for all action buttons in the title bar. */
|
|
5
5
|
const iconBtn = "flex items-center justify-center w-9 h-9 rounded-lg text-zinc-600 dark:text-zinc-400 hover:bg-primary-50 hover:text-primary-700 dark:hover:bg-slate-800 dark:hover:text-secondary-400 transition-colors";
|
|
6
6
|
/** Badge counter class for wishlist/cart counts. */
|
|
@@ -28,9 +28,7 @@ export function TitleBarLayout({ onToggleSidebar, sidebarOpen, onSearchToggle, s
|
|
|
28
28
|
const notificationsEl = notificationsHref ? (_jsxs(Link, { href: notificationsHref, "aria-label": `Notifications${unreadNotificationCount > 0 ? `, ${unreadNotificationCount} unread` : ""}`, className: `relative ${iconBtn}`, children: [_jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M14.857 17.082a23.848 23.848 0 0 0 5.454-1.31A8.967 8.967 0 0 1 18 9.75V9A6 6 0 0 0 6 9v.75a8.967 8.967 0 0 1-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 0 1-5.714 0m5.714 0a3 3 0 1 1-5.714 0" }) }), unreadNotificationCount > 0 && (_jsx(Span, { className: countBadge, children: unreadNotificationCount > 99 ? "99+" : unreadNotificationCount }))] })) : null;
|
|
29
29
|
const wishlistEl = wishlistHref ? (_jsxs(Link, { href: wishlistHref, "aria-label": `Wishlist${wishlistCount > 0 ? `, ${wishlistCount} items` : ""}`, className: `relative ${iconBtn}`, children: [_jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4.318 6.318a4.5 4.5 0 0 0 0 6.364L12 20.364l7.682-7.682a4.5 4.5 0 0 0-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 0 0-6.364 0z" }) }), wishlistCount > 0 && (_jsx(Span, { className: countBadge, children: wishlistCount > 9 ? "9+" : wishlistCount }))] })) : null;
|
|
30
30
|
const cartEl = cartHref ? (_jsxs(Link, { href: cartHref, "aria-label": `Cart${cartCount > 0 ? `, ${cartCount} items` : ""}`, className: `relative ${iconBtn}`, children: [_jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M2.25 3h1.386c.51 0 .955.343 1.087.835l.383 1.437M7.5 14.25a3 3 0 0 0-3 3h15.75m-12.75-3h11.218c1.121-2.3 2.1-4.684 2.924-7.138a60.114 60.114 0 0 0-16.536-1.84M7.5 14.25 5.106 5.272M6 20.25a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0zm12.75 0a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0z" }) }), cartCount > 0 && (_jsx(Span, { className: countBadge, children: cartCount > 9 ? "9+" : cartCount }))] })) : null;
|
|
31
|
-
const profileEl = profileHref ? (_jsx(Link, { href: profileHref, "aria-label": user ? `Profile — ${user.displayName ?? user.email}` : "Sign in", className: `relative ${iconBtn}`, children: user?.photoURL
|
|
32
|
-
// eslint-disable-next-line @next/next/no-img-element
|
|
33
|
-
_jsx("img", { src: user.photoURL, alt: user.displayName ?? "Profile", width: 28, height: 28, loading: "lazy", className: "w-7 h-7 rounded-full object-cover" })) : (_jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M15.75 6a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0zM4.501 20.118a7.5 7.5 0 0 1 14.998 0A17.933 17.933 0 0 1 12 21.75c-2.676 0-5.216-.584-7.499-1.632z" }) })) })) : null;
|
|
31
|
+
const profileEl = profileHref ? (_jsx(Link, { href: profileHref, "aria-label": user ? `Profile — ${user.displayName ?? user.email}` : "Sign in", className: `relative ${iconBtn}`, children: _jsx(Avatar, { src: user?.photoURL ?? undefined, name: user?.displayName ?? user?.email ?? undefined, alt: user?.displayName ?? "Profile", size: "sm" }) })) : null;
|
|
34
32
|
const authButtonsEl = !user && (loginHref || registerHref) ? (_jsxs(Row, { gap: "xs", className: "hidden lg:flex", children: [loginHref && (_jsx(Link, { href: loginHref, className: "px-3 py-1.5 text-sm font-medium text-zinc-700 dark:text-zinc-300 hover:text-primary-700 dark:hover:text-secondary-400 transition-colors rounded-lg hover:bg-primary-50 dark:hover:bg-slate-800", children: "Sign in" })), registerHref && (_jsx(Link, { href: registerHref, className: "px-3 py-1.5 text-sm font-semibold rounded-lg bg-primary text-white hover:bg-primary/90 transition-colors btn-glow shadow-sm", children: "Register" }))] })) : null;
|
|
35
33
|
const hasTb2 = !!(notificationsEl || wishlistEl || cartEl || profileEl);
|
|
36
34
|
// ── Render ───────────────────────────────────────────────────────────────────
|
|
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import Image from "next/image";
|
|
4
4
|
import { useState } from "react";
|
|
5
5
|
import { Div, Span } from "../../ui";
|
|
6
|
+
import { resolveMediaUrl } from "../../utils/media-url";
|
|
6
7
|
const SIZE_HINTS = {
|
|
7
8
|
thumbnail: "(max-width: 640px) 80px, (max-width: 1024px) 96px, 112px",
|
|
8
9
|
card: "(max-width: 640px) 50vw, (max-width: 1024px) 33vw, 25vw",
|
|
@@ -28,12 +29,13 @@ export function MediaImage({ src, alt, size = "card", priority = false, loading,
|
|
|
28
29
|
const [isLoaded, setIsLoaded] = useState(false);
|
|
29
30
|
const icon = fallback ?? FALLBACK_ICONS[size];
|
|
30
31
|
const fitClass = objectFit === "contain" ? "object-contain" : "object-cover";
|
|
31
|
-
|
|
32
|
+
const resolvedSrc = resolveMediaUrl(src);
|
|
33
|
+
if (!resolvedSrc || hasError) {
|
|
32
34
|
return (_jsx(Div, { className: `relative w-full h-full overflow-hidden flex items-center justify-center bg-zinc-100 dark:bg-slate-800 text-zinc-400 text-4xl${className ? ` ${className}` : ""}`, role: "img", "aria-label": alt, children: _jsx(Span, { "aria-hidden": "true", children: icon }) }));
|
|
33
35
|
}
|
|
34
|
-
const isSvg =
|
|
35
|
-
|
|
36
|
-
/[./]svg(\?|$)/i.test(
|
|
37
|
-
return (_jsxs(Div, { className: `relative w-full h-full overflow-hidden${className ? ` ${className}` : ""}`, children: [!isLoaded && (_jsx(Div, { className: "absolute inset-0 bg-zinc-200 dark:bg-slate-700 animate-pulse", "aria-hidden": "true" })), _jsx(Image, { src:
|
|
36
|
+
const isSvg = resolvedSrc.toLowerCase().endsWith(".svg") ||
|
|
37
|
+
resolvedSrc.includes("image/svg") ||
|
|
38
|
+
/[./]svg(\?|$)/i.test(resolvedSrc);
|
|
39
|
+
return (_jsxs(Div, { className: `relative w-full h-full overflow-hidden${className ? ` ${className}` : ""}`, children: [!isLoaded && (_jsx(Div, { className: "absolute inset-0 bg-zinc-200 dark:bg-slate-700 animate-pulse", "aria-hidden": "true" })), _jsx(Image, { src: resolvedSrc, alt: alt, fill: true, priority: priority, loading: loading ?? (priority ? "eager" : "lazy"), className: fitClass, sizes: SIZE_HINTS[size], onLoad: () => setIsLoaded(true), onError: () => setHasError(true), unoptimized: isSvg })] }));
|
|
38
40
|
}
|
|
39
41
|
export default MediaImage;
|
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { useRef, useEffect } from "react";
|
|
4
4
|
import { Div, Span } from "../../ui";
|
|
5
|
+
import { resolveMediaUrl } from "../../utils/media-url";
|
|
5
6
|
export function MediaVideo({ src, thumbnailUrl, alt = "Video", controls = true, autoPlayMuted = false, loop = false, trimStart, trimEnd, objectFit = "cover", }) {
|
|
6
7
|
const videoRef = useRef(null);
|
|
7
8
|
const fitClass = objectFit === "contain" ? "object-contain" : "object-cover";
|
|
9
|
+
const resolvedSrc = resolveMediaUrl(src);
|
|
10
|
+
const resolvedPoster = resolveMediaUrl(thumbnailUrl);
|
|
8
11
|
// Apply trimStart on load
|
|
9
12
|
useEffect(() => {
|
|
10
13
|
const el = videoRef.current;
|
|
@@ -30,9 +33,9 @@ export function MediaVideo({ src, thumbnailUrl, alt = "Video", controls = true,
|
|
|
30
33
|
el.addEventListener("timeupdate", onTimeUpdate);
|
|
31
34
|
return () => el.removeEventListener("timeupdate", onTimeUpdate);
|
|
32
35
|
}, [trimEnd, trimStart]);
|
|
33
|
-
if (!
|
|
36
|
+
if (!resolvedSrc) {
|
|
34
37
|
return (_jsx(Div, { className: "absolute inset-0 flex items-center justify-center bg-zinc-100 dark:bg-slate-800 text-zinc-400 text-4xl", role: "img", "aria-label": alt, children: _jsx(Span, { "aria-hidden": "true", children: "\uD83C\uDFAC" }) }));
|
|
35
38
|
}
|
|
36
|
-
return (_jsx("video", { ref: videoRef, src:
|
|
39
|
+
return (_jsx("video", { ref: videoRef, src: resolvedSrc, poster: resolvedPoster, controls: controls, autoPlay: autoPlayMuted, muted: autoPlayMuted, loop: loop, playsInline: true, "aria-label": alt, className: `absolute inset-0 w-full h-full ${fitClass}` }));
|
|
37
40
|
}
|
|
38
41
|
export default MediaVideo;
|
|
@@ -81,7 +81,9 @@ function ExternalUrlTabPanel({ extInput, setExtInput, extError, setExtError, onA
|
|
|
81
81
|
return (_jsxs(Div, { className: "space-y-2", children: [_jsxs(Row, { gap: "sm", children: [_jsx("input", { type: "url", value: extInput, onChange: (e) => { setExtInput(e.target.value); setExtError(""); }, placeholder: "https://example.com/image.jpg", className: "appkit-input flex-1" }), _jsx(Button, { type: "button", variant: "primary", size: "sm", onClick: onApply, children: "Apply" })] }), extError && _jsx(Alert, { variant: "error", children: extError }), _jsx(Text, { size: "xs", variant: "secondary", children: "External URLs are stored as-is and are not watermarked." })] }));
|
|
82
82
|
}
|
|
83
83
|
function MediaPreviewPanel({ value, label, disabled, enableTrim, enableThumbnail, onEditVideo, onRemove, tUpload, tMediaEditor, }) {
|
|
84
|
-
return (_jsxs(Div, { className: `rounded-lg border border-zinc-200 dark:border-zinc-700 bg-zinc-50 dark:bg-zinc-900 ${__P.p3}`, children: [isVideo(value) ? (_jsxs(Div, { className: "space-y-2", children: [_jsx(Div, { className: `relative aspect-video ${__O.hidden} rounded-lg`, children: _jsx(MediaVideo, { src: value, alt: label, controls: true, objectFit: "contain" }) }), _jsx(Text, { size: "xs", variant: "secondary", children: "Video uploaded successfully. Large videos may take longer to process." })] })) : isImage(value) ? (_jsx(Div, { className: `relative aspect-video ${__O.hidden} rounded-lg`, children: _jsx(MediaImage, { src: value, alt: label, size: "card", objectFit: "contain" }) })) : isPdf(value) ? (_jsxs(Div, { className: "space-y-2", children: [
|
|
84
|
+
return (_jsxs(Div, { className: `rounded-lg border border-zinc-200 dark:border-zinc-700 bg-zinc-50 dark:bg-zinc-900 ${__P.p3}`, children: [isVideo(value) ? (_jsxs(Div, { className: "space-y-2", children: [_jsx(Div, { className: `relative aspect-video ${__O.hidden} rounded-lg`, children: _jsx(MediaVideo, { src: value, alt: label, controls: true, objectFit: "contain" }) }), _jsx(Text, { size: "xs", variant: "secondary", children: "Video uploaded successfully. Large videos may take longer to process." })] })) : isImage(value) ? (_jsx(Div, { className: `relative aspect-video ${__O.hidden} rounded-lg`, children: _jsx(MediaImage, { src: value, alt: label, size: "card", objectFit: "contain" }) })) : isPdf(value) ? (_jsxs(Div, { className: "space-y-2", children: [_jsx(Div, { className: `relative w-full ${__O.hidden} rounded-lg border border-zinc-200 dark:border-zinc-700`,
|
|
85
|
+
// audit-inline-style-ok: dynamic CSS
|
|
86
|
+
style: { height: 280 }, children: _jsx("iframe", { src: value, title: filenameFromUrl(value), className: "w-full h-full border-0" }) }), _jsxs(Row, { gap: "md", align: "center", children: [_jsx(Div, { "aria-hidden": true, className: CLS_PDF_BADGE, children: _jsx(Span, { size: "xs", weight: "bold", children: "PDF" }) }), _jsx("a", { href: value, target: "_blank", rel: "noopener noreferrer", className: CLS_PDF_NAME, children: filenameFromUrl(value) })] })] })) : (_jsx("a", { href: value, target: "_blank", rel: "noopener noreferrer", className: CLS_PDF_LINK, children: filenameFromUrl(value) })), !disabled && (_jsxs(Row, { wrap: true, gap: "sm", className: "mt-2", children: [isVideo(value) && (enableTrim || enableThumbnail) && (_jsx(Button, { type: "button", onClick: () => onEditVideo(value), variant: "secondary", size: "sm", children: tMediaEditor("editVideo") })), _jsx(Button, { type: "button", onClick: onRemove, variant: "danger", size: "sm", children: tUpload("remove") })] }))] }));
|
|
85
87
|
}
|
|
86
88
|
export function MediaUploadField({ label, value, onChange, onChangeField, onUpload,
|
|
87
89
|
// SB-UNI-Z5 2026-05-13 — `kind` derives accept/maxSizeMB when not set
|
|
@@ -58,7 +58,9 @@ function PreOrderInfoSection({ title, productionStatus, maxPerUser, deliveryDate
|
|
|
58
58
|
} }), (categoryName || category || brand) && (_jsxs(Row, { gap: "sm", wrap: true, children: [category && (_jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(category)), className: "inline-flex items-center rounded-full border border-zinc-200 dark:border-zinc-700 bg-zinc-50 dark:bg-zinc-800/50 px-2.5 py-1 text-xs font-medium text-zinc-600 dark:text-zinc-300 transition-colors hover:border-primary-300 hover:bg-primary-50 hover:text-primary-700 dark:hover:border-primary-700/60 dark:hover:bg-primary-900/20 dark:hover:text-primary-400", children: categoryName || category })), !category && categoryName && (_jsx(Span, { size: "xs", weight: "medium", className: "inline-flex items-center rounded-full border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-800/50 px-2.5 py-1 text-zinc-500 dark:text-zinc-400", children: categoryName })), brand && brandSlug && (_jsx(Link, { href: String(ROUTES.PUBLIC.BRAND_DETAIL(brandSlug)), className: "inline-flex items-center rounded-full border border-zinc-200 dark:border-zinc-700 bg-zinc-50 dark:bg-zinc-800/50 px-2.5 py-1 text-xs font-medium text-zinc-600 dark:text-zinc-300 transition-colors hover:border-primary-300 hover:bg-primary-50 hover:text-primary-700 dark:hover:border-primary-700/60 dark:hover:bg-primary-900/20 dark:hover:text-primary-400", children: brand })), brand && !brandSlug && (_jsx(Span, { size: "xs", weight: "medium", className: "inline-flex items-center rounded-full border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-800/50 px-2.5 py-1 text-zinc-500 dark:text-zinc-400", children: brand }))] })), productFeatures && features.length > 0 && (_jsx(FeatureBadgeList, { productFeatureIds: features, features: productFeatures })), !productFeatures && features.length > 0 && (_jsxs(Div, { className: "rounded-xl border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/60 px-4 py-3", children: [_jsx(Text, { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-zinc-500 dark:text-zinc-400", children: "About this product" }), _jsx(Ul, { className: "space-y-1.5", children: features.map((f, i) => (_jsxs(Li, { className: "flex items-start gap-2 text-sm text-zinc-700 dark:text-zinc-300", children: [_jsx(Span, { className: "mt-0.5 flex-shrink-0 text-primary-500", children: "\u2022" }), f] }, i))) })] })), descriptionHtml && (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "text-sm leading-relaxed text-zinc-600 dark:text-zinc-400 line-clamp-4" })), safeSeller && (_jsx(Div, { className: `rounded-xl border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/60 ${__P.p3}`, children: _jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Div, { children: [_jsx(Text, { className: "text-[10px] uppercase tracking-wide text-zinc-400 dark:text-zinc-400 mb-0.5", children: "Sold by" }), _jsx(Text, { className: "text-sm font-semibold text-zinc-800 dark:text-zinc-200", children: safeSeller })] }), storeHref && (_jsx(Link, { href: storeHref, className: "shrink-0 rounded-lg bg-primary/10 dark:bg-primary/20 px-3 py-1.5 text-xs font-semibold text-primary-700 dark:text-primary-300 hover:bg-primary/20 dark:hover:bg-primary/30 transition-colors", children: "Visit Store \u2192" }))] }) }))] }));
|
|
59
59
|
}
|
|
60
60
|
function PreOrderBuyBarPanel({ reserveTarget, reservedCount, progressPct, productId, price, currency, depositAmount, depositPercent, isCancellable, tags, onReserveNow, }) {
|
|
61
|
-
return (_jsxs(Div, { id: "pre-order-buy-bar", className: `rounded-xl border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/60 ${__P.p5} space-y-4`, children: [reserveTarget > 0 && (_jsxs(Div, { className: "space-y-2", children: [_jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400", children: [reservedCount, " of ", reserveTarget, " reserved"] }), _jsxs(Span, { size: "xs", weight: "semibold", className: "text-primary-600 dark:text-primary-400", children: [progressPct, "%"] })] }),
|
|
61
|
+
return (_jsxs(Div, { id: "pre-order-buy-bar", className: `rounded-xl border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/60 ${__P.p5} space-y-4`, children: [reserveTarget > 0 && (_jsxs(Div, { className: "space-y-2", children: [_jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400", children: [reservedCount, " of ", reserveTarget, " reserved"] }), _jsxs(Span, { size: "xs", weight: "semibold", className: "text-primary-600 dark:text-primary-400", children: [progressPct, "%"] })] }), _jsx(Div, { className: `h-2 w-full ${__O.hidden} rounded-full bg-zinc-200 dark:bg-zinc-700`, children: _jsx(Div, { className: "h-full rounded-full bg-primary transition-all",
|
|
62
|
+
// audit-inline-style-ok: computed percentage
|
|
63
|
+
style: { width: `${progressPct}%` } }) })] })), onReserveNow ? (_jsx(PreOrderActionsClient, { productId: productId, price: price, currency: currency, depositAmount: depositAmount, depositPercent: depositPercent, isCancellable: isCancellable, tags: tags, onReserveNow: onReserveNow })) : (_jsxs(_Fragment, { children: [price !== null && (_jsxs(Div, { children: [_jsx(Text, { className: "text-2xl font-bold text-zinc-900 dark:text-zinc-50", children: formatCurrency(price, currency) }), depositAmount !== null && (_jsxs(Text, { className: "mt-0.5 text-xs text-zinc-500 dark:text-zinc-400", children: ["Reserve with ", formatCurrency(depositAmount, currency), depositPercent !== null ? ` (${depositPercent}% deposit)` : ""] }))] })), _jsxs(Stack, { gap: "sm", children: [_jsx(Button, { variant: "primary", size: "md", className: "w-full", children: "Reserve Now" }), isCancellable && (_jsx(Text, { className: "text-center text-xs text-zinc-500 dark:text-zinc-400", children: "\u2713 Free cancellation before production" }))] }), tags.length > 0 && (_jsx(Div, { className: "border-t border-zinc-200 dark:border-zinc-700 pt-4", children: _jsx(Row, { wrap: true, gap: "xs", children: tags.map((tag) => (_jsx(Span, { size: "xs", className: "rounded-full bg-zinc-100 dark:bg-zinc-800 px-2.5 py-1 text-zinc-600 dark:text-zinc-300", children: tag }, tag))) }) })), _jsx(Div, { className: "border-t border-zinc-200 dark:border-zinc-700 pt-4", children: _jsx(Row, { wrap: true, gap: "sm", justify: "center", className: "text-center", children: [
|
|
62
64
|
{ icon: "🔒", label: "Secure\nPayment" },
|
|
63
65
|
{ icon: "📅", label: "Guaranteed\nDelivery" },
|
|
64
66
|
{ icon: "↩", label: "Free\nCancellation" },
|
|
@@ -14,7 +14,7 @@ export function ShareButton({ title, text, className = "" }) {
|
|
|
14
14
|
// user cancelled or browser blocked — fall through to clipboard
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
|
-
navigator.clipboard.writeText(url).catch(() => { });
|
|
17
|
+
navigator.clipboard.writeText(url).catch(() => { }); // audit-silent-catch-ok: clipboard denial is non-fatal; "Copied" toast simply won't show
|
|
18
18
|
setCopied(true);
|
|
19
19
|
setTimeout(() => setCopied(false), 2000);
|
|
20
20
|
};
|
|
@@ -46,7 +46,7 @@ export function ShowGroupSection({ groupId, currentSlug, isParent, groupTitle })
|
|
|
46
46
|
fetch(`/api/products/group/${encodeURIComponent(groupId)}`)
|
|
47
47
|
.then((r) => r.json())
|
|
48
48
|
.then((res) => setMembers(res.data?.items ?? []))
|
|
49
|
-
.catch(() => { })
|
|
49
|
+
.catch(() => { }) // audit-silent-catch-ok: group section is supplementary; main PDP renders without it
|
|
50
50
|
.finally(() => setLoading(false));
|
|
51
51
|
}, [groupId]);
|
|
52
52
|
if (!groupId || loading || members.length <= 1)
|
|
@@ -43,7 +43,7 @@ export function SublistingCarouselSection({ sublistingCategoryId, currentListing
|
|
|
43
43
|
setCategory(res.data?.category ?? null);
|
|
44
44
|
setListings(res.data?.listings ?? []);
|
|
45
45
|
})
|
|
46
|
-
.catch(() => { })
|
|
46
|
+
.catch(() => { }) // audit-silent-catch-ok: sublisting carousel is supplementary; main PDP renders without it
|
|
47
47
|
.finally(() => setLoading(false));
|
|
48
48
|
}, [sublistingCategoryId]);
|
|
49
49
|
if (!sublistingCategoryId || loading || (!category && !loading))
|
|
@@ -104,7 +104,7 @@ export function CouponCard({ coupon, labels: labelsProp, onCopy, className = "",
|
|
|
104
104
|
const handleCopy = () => {
|
|
105
105
|
if (!n.code)
|
|
106
106
|
return;
|
|
107
|
-
navigator.clipboard.writeText(n.code).catch(() => { });
|
|
107
|
+
navigator.clipboard.writeText(n.code).catch(() => { }); // audit-silent-catch-ok: clipboard denial is non-fatal; "Copied" UI simply won't toggle
|
|
108
108
|
setCopied(true);
|
|
109
109
|
onCopy?.(n.code);
|
|
110
110
|
setTimeout(() => setCopied(false), 2000);
|
|
@@ -43,7 +43,7 @@ export async function getPublicScammerById(id) {
|
|
|
43
43
|
if (!doc || doc.status !== "verified")
|
|
44
44
|
return null;
|
|
45
45
|
// Increment views — non-blocking.
|
|
46
|
-
scammerRepository.incrementViews(doc.id).catch(() => { });
|
|
46
|
+
scammerRepository.incrementViews(doc.id).catch(() => { }); // audit-silent-catch-ok: view-count is best-effort analytics
|
|
47
47
|
return doc;
|
|
48
48
|
}
|
|
49
49
|
/**
|
|
@@ -27,5 +27,9 @@ export function PhysicalLocationModal({ count, onSave, onClose }) {
|
|
|
27
27
|
setSaving(false);
|
|
28
28
|
}
|
|
29
29
|
};
|
|
30
|
-
return (
|
|
30
|
+
return (_jsx(Modal, { isOpen: true, onClose: onClose, title: `Set Location — ${count} item${count !== 1 ? "s" : ""}`, size: "sm", actions: _jsxs(Row, { gap: "sm", justify: "end", children: [_jsx(Button, { variant: "ghost", onClick: onClose, disabled: saving, children: "Cancel" }), _jsx(Button, { variant: "primary", onClick: handleSave, disabled: saving, children: saving ? "Saving..." : "Save" })] }), children: _jsxs(Div
|
|
31
|
+
// audit-inline-style-ok: dynamic CSS
|
|
32
|
+
, {
|
|
33
|
+
// audit-inline-style-ok: dynamic CSS
|
|
34
|
+
style: { display: "flex", flexDirection: "column", gap: "12px" }, children: [_jsx(Text, { variant: "muted", children: "Assign a physical storage location to the selected items. All fields are optional." }), _jsx(Input, { label: "Zone (e.g. A, B, Storage-1)", value: zone, onChange: e => setZone(e.target.value), placeholder: "A" }), _jsx(Input, { label: "Shelf (e.g. 3, Top, Middle)", value: shelf, onChange: e => setShelf(e.target.value), placeholder: "3" }), _jsx(Input, { label: "Bin (e.g. Blue, Box-12)", value: bin, onChange: e => setBin(e.target.value), placeholder: "Blue" }), error && _jsx(Text, { variant: "error", children: error })] }) }));
|
|
31
35
|
}
|
|
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import React from "react";
|
|
4
4
|
import { Plus } from "lucide-react";
|
|
5
5
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
|
6
|
-
import { Button, Div, Heading, Row, SideDrawer, Stack, Text, Toggle, useToast, } from "../../../ui";
|
|
6
|
+
import { Button, Div, Heading, Row, SideDrawer, Stack, Text, Toggle, useToast, Skeleton, } from "../../../ui";
|
|
7
7
|
import { apiClient } from "../../../http";
|
|
8
8
|
import { ERROR_MESSAGES } from "../../../errors/messages";
|
|
9
9
|
import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
@@ -54,7 +54,7 @@ export function SellerFeaturesView() {
|
|
|
54
54
|
ERROR_MESSAGES.PRODUCT_FEATURES.UPDATE_FAILED, "error");
|
|
55
55
|
}
|
|
56
56
|
};
|
|
57
|
-
return (_jsxs(Stack, { gap: "md", className: "min-h-screen px-3 sm:px-4 py-4", children: [_jsxs(Row, { align: "center", justify: "between", children: [_jsxs(Div, { children: [_jsx(Heading, { level: 1, className: "text-lg font-semibold text-zinc-900 dark:text-zinc-50", children: "Custom feature badges" }), _jsxs(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400 mt-0.5", children: [features.length, " of ", limit, " used. Platform features are always available on top of your custom ones."] })] }), _jsxs(Button, { size: "sm", onClick: openCreatePanel, disabled: isFull, className: "flex items-center gap-1.5", children: [_jsx(Plus, { className: "h-4 w-4" }), "Add Feature"] })] }), isFull && (_jsxs(Text, { as: "div", className: CAP_BANNER_CLASS, children: ["You have reached the ", limit, "-feature limit for your store. Delete an existing feature before adding a new one."] })), featuresQuery.isLoading ? (
|
|
57
|
+
return (_jsxs(Stack, { gap: "md", className: "min-h-screen px-3 sm:px-4 py-4", children: [_jsxs(Row, { align: "center", justify: "between", children: [_jsxs(Div, { children: [_jsx(Heading, { level: 1, className: "text-lg font-semibold text-zinc-900 dark:text-zinc-50", children: "Custom feature badges" }), _jsxs(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400 mt-0.5", children: [features.length, " of ", limit, " used. Platform features are always available on top of your custom ones."] })] }), _jsxs(Button, { size: "sm", onClick: openCreatePanel, disabled: isFull, className: "flex items-center gap-1.5", children: [_jsx(Plus, { className: "h-4 w-4" }), "Add Feature"] })] }), isFull && (_jsxs(Text, { as: "div", className: CAP_BANNER_CLASS, children: ["You have reached the ", limit, "-feature limit for your store. Delete an existing feature before adding a new one."] })), featuresQuery.isLoading ? (_jsxs(Stack, { gap: "sm", children: [_jsx(Skeleton, { variant: "rectangular", height: "48px" }), _jsx(Skeleton, { variant: "rectangular", height: "48px" }), _jsx(Skeleton, { variant: "rectangular", height: "48px" })] })) : features.length === 0 ? (_jsx(Div, { className: EMPTY_STATE_CLASS, children: "You haven't created any custom features yet." })) : (_jsx(Stack, { as: "ul", gap: "none", className: LIST_CLASS, children: features.map((f) => (_jsxs(Row, { as: "li", className: ROW_CLASS, gap: "sm", children: [_jsxs("button", { type: "button", onClick: () => openEditPanel(f.id), className: "flex-1 text-left", children: [_jsx(Text, { as: "div", className: "text-sm font-medium text-zinc-900 dark:text-zinc-100", children: f.label }), _jsxs(Text, { as: "div", className: "text-xs text-zinc-500 dark:text-zinc-400 mt-0.5", children: [f.category, " \u00B7 ", f.productTypes.join(" · ")] })] }), _jsx(Toggle, { checked: f.isActive, onChange: (checked) => toggleActive(f, checked), "aria-label": "Active" })] }, f.id))) })), _jsx(SideDrawer, { isOpen: isCreateOpen || isEditOpen, onClose: closePanel, title: isCreateOpen ? "Add Feature" : "Edit Feature", mode: isCreateOpen ? "create" : "edit", children: (isCreateOpen || isEditOpen) && (_jsx(AdminFeatureEditorView, { featureId: editId ?? undefined, fixedScope: "store", embedded: true, endpointOverride: {
|
|
58
58
|
create: SELLER_ENDPOINTS.FEATURES,
|
|
59
59
|
byId: SELLER_ENDPOINTS.FEATURE_BY_ID,
|
|
60
60
|
}, onSaved: onSaved, onDeleted: onSaved })) })] }));
|
|
@@ -31,7 +31,7 @@ export function SellerGoogleReviewsView({ onSave, onSync, }) {
|
|
|
31
31
|
lastSyncedAt: data.lastSyncedAt ? String(data.lastSyncedAt) : undefined,
|
|
32
32
|
});
|
|
33
33
|
})
|
|
34
|
-
.catch(() => { })
|
|
34
|
+
.catch(() => { }) // audit-silent-catch-ok: empty form is safe fallback; user can still connect
|
|
35
35
|
.finally(() => setLoading(false));
|
|
36
36
|
}, []);
|
|
37
37
|
const handleSave = useCallback(async () => {
|
|
@@ -27,7 +27,7 @@ export function SellerPayoutRequestView({ payoutsApiBase = "/api/store/payouts",
|
|
|
27
27
|
setSummary(payoutsJson?.data?.summary ?? null);
|
|
28
28
|
setPayoutDetails(settingsJson?.data?.payoutDetails ?? null);
|
|
29
29
|
})
|
|
30
|
-
.catch(() => { })
|
|
30
|
+
.catch(() => { }) // audit-silent-catch-ok: empty state is safe fallback; UI shows "no payout history yet"
|
|
31
31
|
.finally(() => setLoading(false));
|
|
32
32
|
}, [payoutsApiBase, payoutSettingsApiBase]);
|
|
33
33
|
const handleRequest = async () => {
|
|
@@ -52,7 +52,7 @@ export function SellerPayoutSettingsView({ apiBase = "/api/store/payout-settings
|
|
|
52
52
|
: "",
|
|
53
53
|
});
|
|
54
54
|
})
|
|
55
|
-
.catch(() => { })
|
|
55
|
+
.catch(() => { }) // audit-silent-catch-ok: blank form is safe fallback; save round-trip will surface persistent errors
|
|
56
56
|
.finally(() => setLoading(false));
|
|
57
57
|
}, [apiBase]);
|
|
58
58
|
const update = useCallback((partial) => {
|
|
@@ -165,7 +165,7 @@ export function SellerProductShell({ mode, listingType = "standard", initialValu
|
|
|
165
165
|
autoSaveTimer.current = setTimeout(() => {
|
|
166
166
|
const result = onSaveRef.current(draftRef.current);
|
|
167
167
|
if (result && typeof result.catch === "function")
|
|
168
|
-
result.catch(() => { });
|
|
168
|
+
result.catch(() => { }); // audit-silent-catch-ok: autosave is best-effort; manual Save shows real errors
|
|
169
169
|
}, 2000);
|
|
170
170
|
return () => { if (autoSaveTimer.current)
|
|
171
171
|
clearTimeout(autoSaveTimer.current); };
|
|
@@ -44,7 +44,7 @@ export function SellerShippingView({ apiBase = "/api/store/shipping" }) {
|
|
|
44
44
|
: "",
|
|
45
45
|
});
|
|
46
46
|
})
|
|
47
|
-
.catch(() => { })
|
|
47
|
+
.catch(() => { }) // audit-silent-catch-ok: blank form is safe fallback; save round-trip will surface persistent errors
|
|
48
48
|
.finally(() => setLoading(false));
|
|
49
49
|
}, [apiBase]);
|
|
50
50
|
const update = useCallback((partial) => {
|
|
@@ -77,7 +77,7 @@ export function useWishlistCount(userId) {
|
|
|
77
77
|
});
|
|
78
78
|
}
|
|
79
79
|
})
|
|
80
|
-
.catch(() => { })
|
|
80
|
+
.catch(() => { }) // audit-silent-catch-ok: badge count refetches on next route change
|
|
81
81
|
.finally(finish);
|
|
82
82
|
}, [userId, queryClient]);
|
|
83
83
|
// Refetch when the user lands on /wishlist or /cart so the badge matches
|
|
@@ -152,7 +152,7 @@ export const serverLogger = {
|
|
|
152
152
|
};
|
|
153
153
|
console.info(`[INFO] ${message}`, sanitized);
|
|
154
154
|
if (isFileLoggingEnabled)
|
|
155
|
-
writeLog(entry).catch(() => { });
|
|
155
|
+
writeLog(entry).catch(() => { }); // audit-silent-catch-ok: logger must never throw
|
|
156
156
|
},
|
|
157
157
|
warn(message, data) {
|
|
158
158
|
const sanitized = data ? redactPii(normalizeLogData(data)) : undefined;
|
|
@@ -164,7 +164,7 @@ export const serverLogger = {
|
|
|
164
164
|
};
|
|
165
165
|
console.warn(`[WARN] ${message}`, sanitized);
|
|
166
166
|
if (isFileLoggingEnabled)
|
|
167
|
-
writeLog(entry).catch(() => { });
|
|
167
|
+
writeLog(entry).catch(() => { }); // audit-silent-catch-ok: logger must never throw
|
|
168
168
|
},
|
|
169
169
|
error(message, data) {
|
|
170
170
|
const sanitized = data ? redactPii(normalizeLogData(data)) : undefined;
|
|
@@ -176,7 +176,7 @@ export const serverLogger = {
|
|
|
176
176
|
};
|
|
177
177
|
console.error(`[ERROR] ${message}`, sanitized);
|
|
178
178
|
if (isFileLoggingEnabled)
|
|
179
|
-
writeLog(entry).catch(() => { });
|
|
179
|
+
writeLog(entry).catch(() => { }); // audit-silent-catch-ok: logger must never throw
|
|
180
180
|
},
|
|
181
181
|
};
|
|
182
182
|
/** Pull the commonly needed headers from a NextRequest in one call. */
|
|
@@ -32,6 +32,7 @@ export declare const RTDB_PATHS: {
|
|
|
32
32
|
readonly AUTH_EVENTS: "auth_events";
|
|
33
33
|
readonly PAYMENT_EVENTS: "payment_events";
|
|
34
34
|
readonly BULK_EVENTS: "bulk_events";
|
|
35
|
+
readonly SEED_EVENTS: "seed_events";
|
|
35
36
|
};
|
|
36
37
|
export { buildFirebaseClientConfig, normalizeFirebaseConfigValue } from './client-config';
|
|
37
38
|
export type { FirebaseClientConfig } from './client-config';
|