@mohasinac/appkit 2.8.4 → 2.8.6
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 +2 -0
- package/dist/_internal/server/features/checkout/actions.js +2 -1
- package/dist/_internal/server/features/grouped/data.js +9 -8
- package/dist/_internal/server/features/products/data.js +2 -1
- package/dist/_internal/server/features/refunds/actions.js +2 -1
- package/dist/_internal/server/features/seo/sitemap.js +2 -1
- package/dist/_internal/server/jobs/core/countersReconcile.js +4 -4
- package/dist/_internal/shared/features/blog/schema.d.ts +4 -4
- package/dist/_internal/shared/features/categories/bundle-schemas.d.ts +8 -8
- package/dist/_internal/shared/listing-types/action-tracker.js +0 -2
- package/dist/constants/field-names.d.ts +3 -0
- package/dist/constants/field-names.js +3 -0
- package/dist/core/unit-of-work.js +0 -1
- package/dist/features/account/components/NotificationBell.js +1 -0
- package/dist/features/account/components/UserOffersPanel.js +1 -0
- package/dist/features/account/components/UserSidebar.js +3 -1
- package/dist/features/admin/components/AdminCarouselEditorView.js +3 -1
- package/dist/features/admin/components/AdminSidebar.js +3 -1
- package/dist/features/admin/components/AdminSiteSettingsView.js +3 -1
- package/dist/features/admin/components/AdminSupportTicketDetailView.js +0 -3
- package/dist/features/admin/components/DashboardStats.js +5 -1
- package/dist/features/admin/components/analytics/AdminAnalyticsCharts.js +3 -1
- package/dist/features/admin/components/analytics/AdminStatCard.js +5 -1
- package/dist/features/admin/hooks/useChat.js +1 -0
- package/dist/features/admin/repository/chat.repository.js +7 -6
- package/dist/features/auctions/components/AuctionBidsTable.js +5 -1
- package/dist/features/auctions/components/AuctionBottomActions.js +3 -4
- package/dist/features/auctions/repository/bid.repository.js +12 -12
- package/dist/features/auth/hooks/useAuth.js +1 -0
- package/dist/features/blog/actions/blog-actions.d.ts +2 -2
- package/dist/features/blog/components/BlogFeaturedCard.js +3 -1
- package/dist/features/blog/components/BlogListView.js +5 -1
- package/dist/features/blog/components/BlogPostView.js +3 -1
- package/dist/features/blog/schemas/index.d.ts +1 -1
- package/dist/features/cart/components/CartDrawer.js +3 -1
- package/dist/features/cart/repository/cart.repository.js +2 -1
- package/dist/features/categories/components/BrandDetailPageView.js +5 -1
- package/dist/features/categories/components/CategoryDetailPageView.js +3 -1
- package/dist/features/categories/components/CategoryGrid.js +5 -1
- package/dist/features/categories/components/CategoryTableColumns.js +6 -2
- package/dist/features/categories/components/CategoryTree.js +3 -1
- package/dist/features/categories/components/ConcernCard.js +3 -1
- package/dist/features/categories/repository/categories.repository.js +26 -25
- package/dist/features/categories/schemas/firestore.d.ts +1 -0
- package/dist/features/categories/schemas/firestore.js +1 -0
- package/dist/features/categories/schemas/index.d.ts +10 -10
- package/dist/features/collections/components/CollectionCard.js +3 -1
- package/dist/features/events/components/EventCard.js +3 -1
- package/dist/features/events/components/EventParticipateView.js +2 -1
- package/dist/features/events/schemas/index.d.ts +3 -3
- package/dist/features/faq/actions/faq-actions.d.ts +6 -6
- package/dist/features/faq/repository/faqs.repository.js +21 -20
- package/dist/features/faq/schemas/index.d.ts +2 -2
- package/dist/features/forms/Slider.js +3 -1
- package/dist/features/history/hooks/useHistory.js +1 -0
- package/dist/features/homepage/components/AdSlot.js +6 -2
- package/dist/features/homepage/components/AdvertisementBanner.js +3 -1
- package/dist/features/homepage/components/BeforeAfterCard.js +5 -1
- package/dist/features/homepage/components/CustomCardsSection.js +5 -1
- package/dist/features/homepage/components/HowItWorksSection.js +3 -1
- package/dist/features/homepage/components/NewsletterBanner.js +6 -2
- package/dist/features/homepage/components/SecurityHighlightsSection.js +3 -1
- package/dist/features/homepage/components/StatsCounterSection.js +3 -1
- package/dist/features/homepage/components/TrustFeaturesSection.js +3 -1
- package/dist/features/homepage/repository/carousel.repository.js +8 -7
- package/dist/features/homepage/repository/carousels.repository.js +2 -1
- package/dist/features/homepage/repository/homepage-sections.repository.js +6 -5
- package/dist/features/layout/AppLayoutShell.js +3 -1
- package/dist/features/layout/BottomActionsContext.js +1 -3
- package/dist/features/layout/BottomNavbar.js +1 -1
- package/dist/features/media/MediaLightbox.js +9 -3
- package/dist/features/media/modals/ImageEditor.js +6 -2
- package/dist/features/media/upload/MediaUploadField.js +1 -1
- package/dist/features/messages/hooks/useConversation.js +2 -0
- package/dist/features/messages/hooks/useConversations.js +1 -0
- package/dist/features/messages/repository/conversations.repository.js +5 -4
- package/dist/features/orders/components/OrdersList.js +3 -1
- package/dist/features/orders/repository/orders.repository.js +13 -13
- package/dist/features/pre-orders/components/PreOrderDetailPageView.js +1 -1
- package/dist/features/products/components/CompareOverlay.js +5 -1
- package/dist/features/products/components/FeatureBadge.js +3 -1
- package/dist/features/products/components/MarketplacePrizeDrawCard.js +1 -1
- package/dist/features/products/components/ProductGrid.js +3 -1
- package/dist/features/products/repository/product-features.repository.js +7 -6
- package/dist/features/products/repository/product-templates.repository.js +4 -3
- package/dist/features/products/repository/products.repository.js +4 -4
- package/dist/features/promotions/repository/claimed-coupons.repository.js +2 -1
- package/dist/features/promotions/repository/coupons.repository.js +4 -4
- package/dist/features/reviews/components/ReviewDetailShell.js +8 -2
- package/dist/features/reviews/components/ReviewModal.js +5 -1
- package/dist/features/reviews/components/ReviewSummary.js +3 -1
- package/dist/features/reviews/components/ReviewsList.js +5 -1
- package/dist/features/reviews/repository/reviews.repository.js +2 -2
- package/dist/features/scams/repository/scammer.repository.js +3 -3
- package/dist/features/search/components/Search.js +14 -1
- package/dist/features/seller/components/PhysicalLocationModal.js +1 -1
- package/dist/features/seller/components/SellerAddressesView.js +2 -1
- package/dist/features/seller/components/SellerBidsView.js +3 -1
- package/dist/features/seller/components/SellerCouponEditorView.js +3 -1
- package/dist/features/seller/components/SellerOffersPanel.js +1 -0
- package/dist/features/seller/components/SellerPayoutMethodsView.js +1 -0
- package/dist/features/seller/components/SellerProductsView.js +5 -1
- package/dist/features/seller/components/SellerReviewsView.js +1 -0
- package/dist/features/seller/components/SellerShippingConfigsView.js +1 -0
- package/dist/features/seller/components/SellerSidebar.js +12 -4
- package/dist/features/seller/components/SellerStoreCategoriesView.js +1 -0
- package/dist/features/seller/components/SellerTemplatesView.js +1 -0
- package/dist/features/shell/FormShell.js +10 -2
- package/dist/features/shell/QuickFormDrawer.js +5 -2
- package/dist/features/stores/components/StoreHeader.js +5 -1
- package/dist/features/support/repository/support.repository.js +8 -7
- package/dist/features/wishlist/components/WishlistPage.js +3 -1
- package/dist/providers/db-firebase/admin.js +0 -1
- package/dist/seed/products-auctions-seed-data.js +107 -0
- package/dist/ui/DataTable.js +6 -2
- package/dist/ui/components/AvatarDisplay.js +3 -1
- package/dist/ui/components/BackgroundRenderer.js +4 -2
- package/dist/ui/components/Card.js +6 -2
- package/dist/ui/components/ImageGallery.js +3 -1
- package/dist/ui/components/ImageLightbox.js +3 -1
- package/dist/ui/components/PasswordStrengthIndicator.js +3 -1
- package/dist/ui/components/Progress.js +6 -2
- package/dist/ui/components/RowActionMenu.js +3 -1
- package/dist/ui/components/SiteLogo.js +7 -1
- package/dist/ui/components/Skeleton.js +3 -1
- package/dist/ui/components/Slider.js +3 -1
- package/dist/ui/components/TabStrip.js +3 -1
- package/package.json +1 -1
|
@@ -14,6 +14,7 @@ export function useChat(chatId, options) {
|
|
|
14
14
|
const tokenExpiresAtRef = useRef(0);
|
|
15
15
|
const unsubscribeRef = useRef(null);
|
|
16
16
|
const connectAndSubscribe = useCallback(async () => {
|
|
17
|
+
// toast-intentionally-silent — error stored in setError() for UI inline render
|
|
17
18
|
if (!chatId)
|
|
18
19
|
return;
|
|
19
20
|
setIsLoading(true);
|
|
@@ -22,6 +22,7 @@ import { getAdminRealtimeDb } from "../../../providers/db-firebase";
|
|
|
22
22
|
import { BaseRepository } from "../../../providers/db-firebase";
|
|
23
23
|
import { prepareForFirestore } from "../../../providers/db-firebase";
|
|
24
24
|
import { DatabaseError } from "../../../errors";
|
|
25
|
+
import { CONVERSATION_FIELDS, STORE_FIELDS } from "../../../constants/field-names";
|
|
25
26
|
import { CHAT_ROOM_COLLECTION } from "../schemas";
|
|
26
27
|
import { encryptPiiFields, decryptPiiFields, CHAT_PII_FIELDS, } from "../../../security";
|
|
27
28
|
export { CHAT_ROOM_COLLECTION } from "../schemas";
|
|
@@ -72,11 +73,11 @@ class ChatRepository extends BaseRepository {
|
|
|
72
73
|
try {
|
|
73
74
|
const [asBuyer, asSeller] = await Promise.all([
|
|
74
75
|
this.getCollection()
|
|
75
|
-
.where(
|
|
76
|
+
.where(CONVERSATION_FIELDS.BUYER_ID, "==", userId)
|
|
76
77
|
.where("adminDeleted", "==", false)
|
|
77
78
|
.get(),
|
|
78
79
|
this.getCollection()
|
|
79
|
-
.where(
|
|
80
|
+
.where(STORE_FIELDS.OWNER_ID, "==", userId)
|
|
80
81
|
.where("adminDeleted", "==", false)
|
|
81
82
|
.get(),
|
|
82
83
|
]);
|
|
@@ -101,14 +102,14 @@ class ChatRepository extends BaseRepository {
|
|
|
101
102
|
try {
|
|
102
103
|
const [asBuyer, asSeller] = await Promise.all([
|
|
103
104
|
this.getCollection()
|
|
104
|
-
.where(
|
|
105
|
+
.where(CONVERSATION_FIELDS.BUYER_ID, "==", userId)
|
|
105
106
|
.where("adminDeleted", "==", false)
|
|
106
|
-
.orderBy(
|
|
107
|
+
.orderBy(CONVERSATION_FIELDS.UPDATED_AT, "desc")
|
|
107
108
|
.get(),
|
|
108
109
|
this.getCollection()
|
|
109
|
-
.where(
|
|
110
|
+
.where(STORE_FIELDS.OWNER_ID, "==", userId)
|
|
110
111
|
.where("adminDeleted", "==", false)
|
|
111
|
-
.orderBy(
|
|
112
|
+
.orderBy(CONVERSATION_FIELDS.UPDATED_AT, "desc")
|
|
112
113
|
.get(),
|
|
113
114
|
]);
|
|
114
115
|
const seen = new Set();
|
|
@@ -54,7 +54,11 @@ function AuctionRow({ auction, portal, }) {
|
|
|
54
54
|
const sorted = useMemo(() => [...auction.bids].sort((a, b) => b.bidAmount - a.bidAmount), [auction.bids]);
|
|
55
55
|
const highest = sorted[0]?.bidAmount ?? 0;
|
|
56
56
|
const isWinning = auction.bids.some((b) => b.isWinning);
|
|
57
|
-
return (_jsxs(Div, { className: `border border-[var(--appkit-color-border)] rounded-xl ${__O.hidden} bg-[var(--appkit-color-surface)] shadow-sm`, children: [_jsxs("button", { type: "button", onClick: () => setExpanded((v) => !v), className: "w-full flex items-center gap-3 px-4 py-3 hover:bg-[var(--appkit-color-border-subtle)] transition-colors text-left", "aria-expanded": expanded, children: [_jsx(ChevronRight, { className: `shrink-0 text-[var(--appkit-color-text-muted)] transition-transform ${expanded ? "rotate-90" : ""}`, size: 16 }), _jsx(Div, { className: "flex-1 min-w-0", children: _jsx(Text, { className: "text-sm font-semibold text-[var(--appkit-color-text)] line-clamp-1", children: auction.productTitle }) }), _jsxs(Row, { gap: "sm", className: "shrink-0", children: [isWinning && (_jsx(Badge, { variant: "active", children: "Winning" })), _jsxs(Text, { variant: "secondary", className: "text-xs", children: [auction.bids.length, " bid", auction.bids.length !== 1 ? "s" : ""] }), _jsx(Text, { className: "text-sm font-semibold text-[var(--appkit-color-text)]", children: paise(highest) })] })] }), expanded && (_jsxs(Div, { className: "border-t border-[var(--appkit-color-border)]", children: [_jsxs("div", { className: "grid text-xs font-medium text-[var(--appkit-color-text-muted)] uppercase tracking-wide px-4 py-2 border-b border-[var(--appkit-color-border-subtle)]",
|
|
57
|
+
return (_jsxs(Div, { className: `border border-[var(--appkit-color-border)] rounded-xl ${__O.hidden} bg-[var(--appkit-color-surface)] shadow-sm`, children: [_jsxs("button", { type: "button", onClick: () => setExpanded((v) => !v), className: "w-full flex items-center gap-3 px-4 py-3 hover:bg-[var(--appkit-color-border-subtle)] transition-colors text-left", "aria-expanded": expanded, children: [_jsx(ChevronRight, { className: `shrink-0 text-[var(--appkit-color-text-muted)] transition-transform ${expanded ? "rotate-90" : ""}`, size: 16 }), _jsx(Div, { className: "flex-1 min-w-0", children: _jsx(Text, { className: "text-sm font-semibold text-[var(--appkit-color-text)] line-clamp-1", children: auction.productTitle }) }), _jsxs(Row, { gap: "sm", className: "shrink-0", children: [isWinning && (_jsx(Badge, { variant: "active", children: "Winning" })), _jsxs(Text, { variant: "secondary", className: "text-xs", children: [auction.bids.length, " bid", auction.bids.length !== 1 ? "s" : ""] }), _jsx(Text, { className: "text-sm font-semibold text-[var(--appkit-color-text)]", children: paise(highest) })] })] }), expanded && (_jsxs(Div, { className: "border-t border-[var(--appkit-color-border)]", children: [_jsxs("div", { className: "grid text-xs font-medium text-[var(--appkit-color-text-muted)] uppercase tracking-wide px-4 py-2 border-b border-[var(--appkit-color-border-subtle)]",
|
|
58
|
+
// audit-inline-style-ok: runtime grid template
|
|
59
|
+
style: { gridTemplateColumns: portal === "buyer" ? "1fr auto auto" : "1fr 1fr auto auto" }, children: [_jsx("span", { children: portal === "buyer" ? "Amount" : "Bidder" }), portal !== "buyer" && _jsx("span", { children: "Amount" }), _jsx("span", { children: "Status" }), _jsx("span", { className: "text-right", children: "Time" })] }), sorted.map((bid) => (_jsxs("div", { className: "grid items-center px-4 py-2.5 border-b border-[var(--appkit-color-border-subtle)] last:border-0 hover:bg-[var(--appkit-color-border-subtle)] transition-colors",
|
|
60
|
+
// audit-inline-style-ok: runtime grid template
|
|
61
|
+
style: { gridTemplateColumns: portal === "buyer" ? "1fr auto auto" : "1fr 1fr auto auto" }, children: [portal !== "buyer" && (_jsx(Text, { className: "text-sm text-[var(--appkit-color-text)] truncate pr-3", children: bid.userName || bid.userId })), _jsx(Text, { className: "text-sm font-medium text-[var(--appkit-color-text)]", children: paise(bid.bidAmount) }), _jsx(Badge, { variant: STATUS_VARIANT[bid.status] ?? "pending", className: "capitalize", children: bid.status }), _jsx(Text, { variant: "secondary", className: "text-xs text-right", children: relDate(bid.bidDate) })] }, bid.id)))] }))] }));
|
|
58
62
|
}
|
|
59
63
|
export function AuctionBidsTable({ bids, portal = "buyer", emptyLabel = "No bids found.", }) {
|
|
60
64
|
const auctions = useMemo(() => groupByAuction(bids), [bids]);
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { useBottomActions } from "../../layout/hooks/useBottomActions";
|
|
3
3
|
import { formatCurrency } from "../../../utils/number.formatter";
|
|
4
|
-
import { ACTION_ID } from "../../products/constants/action-defs";
|
|
4
|
+
import { ACTION_ID, ACTION_META } from "../../products/constants/action-defs";
|
|
5
5
|
export function AuctionBottomActions({ currentBid, currency, bidCount, isEnded, }) {
|
|
6
|
+
const placeBidMeta = ACTION_META[ACTION_ID.PLACE_BID];
|
|
6
7
|
useBottomActions(isEnded
|
|
7
8
|
? {}
|
|
8
9
|
: {
|
|
9
10
|
actions: [
|
|
10
11
|
{
|
|
11
|
-
|
|
12
|
-
label: "Place Bid",
|
|
13
|
-
variant: "primary",
|
|
12
|
+
...placeBidMeta,
|
|
14
13
|
onClick: () => {
|
|
15
14
|
document
|
|
16
15
|
.getElementById("auction-bid-form")
|
|
@@ -63,7 +63,7 @@ export class BidRepository extends BaseRepository {
|
|
|
63
63
|
let query = this.db
|
|
64
64
|
.collection(this.collection)
|
|
65
65
|
.where(BID_FIELDS.USER_ID, "==", userId)
|
|
66
|
-
.orderBy(
|
|
66
|
+
.orderBy(BID_FIELDS.CREATED_AT, "desc")
|
|
67
67
|
.limit(pageSize + 1);
|
|
68
68
|
if (startAfterDoc)
|
|
69
69
|
query = query.startAfter(startAfterDoc);
|
|
@@ -86,7 +86,7 @@ export class BidRepository extends BaseRepository {
|
|
|
86
86
|
.collection(this.collection)
|
|
87
87
|
.where(BID_FIELDS.PRODUCT_ID, "==", productId)
|
|
88
88
|
.where(BID_FIELDS.STATUS, "==", "active")
|
|
89
|
-
.orderBy(
|
|
89
|
+
.orderBy(BID_FIELDS.BID_AMOUNT, "desc")
|
|
90
90
|
.get();
|
|
91
91
|
return snapshot.docs.map((doc) => ({
|
|
92
92
|
id: doc.id,
|
|
@@ -169,9 +169,9 @@ export class BidRepository extends BaseRepository {
|
|
|
169
169
|
try {
|
|
170
170
|
const snapshot = await this.db
|
|
171
171
|
.collection(this.collection)
|
|
172
|
-
.where(
|
|
173
|
-
.where(
|
|
174
|
-
.orderBy(
|
|
172
|
+
.where(BID_FIELDS.PRODUCT_ID, "==", productId)
|
|
173
|
+
.where(BID_FIELDS.STATUS, "==", "active")
|
|
174
|
+
.orderBy(BID_FIELDS.BID_AMOUNT, "desc")
|
|
175
175
|
.limit(1)
|
|
176
176
|
.get();
|
|
177
177
|
if (snapshot.empty) {
|
|
@@ -192,8 +192,8 @@ export class BidRepository extends BaseRepository {
|
|
|
192
192
|
try {
|
|
193
193
|
const snapshot = await this.db
|
|
194
194
|
.collection(this.collection)
|
|
195
|
-
.where(
|
|
196
|
-
.orderBy(
|
|
195
|
+
.where(BID_FIELDS.PRODUCT_ID, "==", productId)
|
|
196
|
+
.orderBy(BID_FIELDS.BID_AMOUNT, "desc")
|
|
197
197
|
.get();
|
|
198
198
|
return snapshot.docs.map((doc) => this.mapDoc(doc));
|
|
199
199
|
}
|
|
@@ -230,7 +230,7 @@ export class BidRepository extends BaseRepository {
|
|
|
230
230
|
// Set all bids for this product to not winning
|
|
231
231
|
const allBidsSnapshot = await this.db
|
|
232
232
|
.collection(this.collection)
|
|
233
|
-
.where(
|
|
233
|
+
.where(BID_FIELDS.PRODUCT_ID, "==", productId)
|
|
234
234
|
.get();
|
|
235
235
|
allBidsSnapshot.docs.forEach((doc) => {
|
|
236
236
|
batch.update(doc.ref, {
|
|
@@ -260,7 +260,7 @@ export class BidRepository extends BaseRepository {
|
|
|
260
260
|
const batch = this.db.batch();
|
|
261
261
|
const bidsSnapshot = await this.db
|
|
262
262
|
.collection(this.collection)
|
|
263
|
-
.where(
|
|
263
|
+
.where(BID_FIELDS.PRODUCT_ID, "==", productId)
|
|
264
264
|
.get();
|
|
265
265
|
bidsSnapshot.docs.forEach((doc) => {
|
|
266
266
|
const bid = doc.data();
|
|
@@ -283,7 +283,7 @@ export class BidRepository extends BaseRepository {
|
|
|
283
283
|
const batch = this.db.batch();
|
|
284
284
|
const bidsSnapshot = await this.db
|
|
285
285
|
.collection(this.collection)
|
|
286
|
-
.where(
|
|
286
|
+
.where(BID_FIELDS.PRODUCT_ID, "==", productId)
|
|
287
287
|
.get();
|
|
288
288
|
bidsSnapshot.docs.forEach((doc) => {
|
|
289
289
|
batch.update(doc.ref, {
|
|
@@ -303,7 +303,7 @@ export class BidRepository extends BaseRepository {
|
|
|
303
303
|
*/
|
|
304
304
|
async countByProduct(productId) {
|
|
305
305
|
try {
|
|
306
|
-
return await getFirestoreCount(this.db.collection(this.collection).where(
|
|
306
|
+
return await getFirestoreCount(this.db.collection(this.collection).where(BID_FIELDS.PRODUCT_ID, "==", productId));
|
|
307
307
|
}
|
|
308
308
|
catch (error) {
|
|
309
309
|
throw new DatabaseError(`Failed to count bids for product: ${productId}`, error);
|
|
@@ -314,7 +314,7 @@ export class BidRepository extends BaseRepository {
|
|
|
314
314
|
*/
|
|
315
315
|
async countByUser(userId) {
|
|
316
316
|
try {
|
|
317
|
-
return await getFirestoreCount(this.db.collection(this.collection).where(
|
|
317
|
+
return await getFirestoreCount(this.db.collection(this.collection).where(BID_FIELDS.USER_ID, "==", userId));
|
|
318
318
|
}
|
|
319
319
|
catch (error) {
|
|
320
320
|
throw new DatabaseError(`Failed to count bids for user: ${userId}`, error);
|
|
@@ -119,6 +119,7 @@ export function useGoogleLogin(options) {
|
|
|
119
119
|
return () => window.removeEventListener("message", handleMessage);
|
|
120
120
|
}, []); // mount once — uses refs, no stale closure risk
|
|
121
121
|
const mutate = useCallback(async () => {
|
|
122
|
+
// toast-intentionally-silent — errors surfaced via onErrorRef.current() callback
|
|
122
123
|
calledRef.current = false; // reset for each new auth flow
|
|
123
124
|
const popup = window.open(`${window.location.origin}/auth.html`, "oauth_google", "width=500,height=660,left=400,top=100");
|
|
124
125
|
if (!popup) {
|
|
@@ -216,6 +216,7 @@ export declare const updateBlogPostSchema: z.ZodObject<{
|
|
|
216
216
|
content?: string | undefined;
|
|
217
217
|
isFeatured?: boolean | undefined;
|
|
218
218
|
publishedAt?: string | undefined;
|
|
219
|
+
excerpt?: string | undefined;
|
|
219
220
|
coverImage?: {
|
|
220
221
|
url: string;
|
|
221
222
|
type: "image" | "video" | "file";
|
|
@@ -225,7 +226,6 @@ export declare const updateBlogPostSchema: z.ZodObject<{
|
|
|
225
226
|
url: string;
|
|
226
227
|
type: "image";
|
|
227
228
|
} | null | undefined;
|
|
228
|
-
excerpt?: string | undefined;
|
|
229
229
|
authorId?: string | undefined;
|
|
230
230
|
authorName?: string | undefined;
|
|
231
231
|
readTimeMinutes?: number | undefined;
|
|
@@ -253,13 +253,13 @@ export declare const updateBlogPostSchema: z.ZodObject<{
|
|
|
253
253
|
content?: string | undefined;
|
|
254
254
|
isFeatured?: boolean | undefined;
|
|
255
255
|
publishedAt?: string | undefined;
|
|
256
|
+
excerpt?: string | undefined;
|
|
256
257
|
coverImage?: string | {
|
|
257
258
|
url: string;
|
|
258
259
|
type: "image" | "video" | "file";
|
|
259
260
|
alt?: string | undefined;
|
|
260
261
|
thumbnailUrl?: string | undefined;
|
|
261
262
|
} | null | undefined;
|
|
262
|
-
excerpt?: string | undefined;
|
|
263
263
|
authorId?: string | undefined;
|
|
264
264
|
authorName?: string | undefined;
|
|
265
265
|
readTimeMinutes?: number | undefined;
|
|
@@ -31,5 +31,7 @@ export function BlogFeaturedCard({ post, href, labels = {}, className = "", sele
|
|
|
31
31
|
day: "numeric",
|
|
32
32
|
})
|
|
33
33
|
: "";
|
|
34
|
-
return (_jsxs(Article, { className: `group relative flex h-full flex-col overflow-hidden rounded-xl border border-neutral-200 dark:border-slate-700 bg-white dark:bg-slate-900 shadow-sm hover:shadow-md transition-shadow duration-200 ${isSelected ? "ring-2 ring-primary outline outline-2 outline-primary" : ""} ${className}`, onMouseDown: onSelect && !isSelected ? longPress.onMouseDown : undefined, onMouseUp: onSelect && !isSelected ? longPress.onMouseUp : undefined, onMouseLeave: onSelect && !isSelected ? longPress.onMouseLeave : undefined, onTouchStart: onSelect && !isSelected ? longPress.onTouchStart : undefined, onTouchEnd: onSelect && !isSelected ? longPress.onTouchEnd : undefined, children: [onSelect && (_jsx(BaseListingCard.Checkbox, { selected: isSelected, onSelect: (e) => { e.preventDefault(); onSelect(post.id, !isSelected); }, label: isSelected ? "Deselect post" : "Select post", position: "top-2 left-2", className: selectable || isSelected ? "opacity-100" : "opacity-0 group-hover:opacity-100 transition-opacity" })), _jsxs(TextLink, { href: href, className: "flex h-full flex-col", children: [_jsx(Div, { className: `aspect-video ${__O.hidden} flex-shrink-0`, children: coverImageUrl ? (_jsx(Div, { role: "img", "aria-label": safeTitle, className: "h-full w-full bg-cover bg-center transition-transform duration-300 group-hover:scale-105",
|
|
34
|
+
return (_jsxs(Article, { className: `group relative flex h-full flex-col overflow-hidden rounded-xl border border-neutral-200 dark:border-slate-700 bg-white dark:bg-slate-900 shadow-sm hover:shadow-md transition-shadow duration-200 ${isSelected ? "ring-2 ring-primary outline outline-2 outline-primary" : ""} ${className}`, onMouseDown: onSelect && !isSelected ? longPress.onMouseDown : undefined, onMouseUp: onSelect && !isSelected ? longPress.onMouseUp : undefined, onMouseLeave: onSelect && !isSelected ? longPress.onMouseLeave : undefined, onTouchStart: onSelect && !isSelected ? longPress.onTouchStart : undefined, onTouchEnd: onSelect && !isSelected ? longPress.onTouchEnd : undefined, children: [onSelect && (_jsx(BaseListingCard.Checkbox, { selected: isSelected, onSelect: (e) => { e.preventDefault(); onSelect(post.id, !isSelected); }, label: isSelected ? "Deselect post" : "Select post", position: "top-2 left-2", className: selectable || isSelected ? "opacity-100" : "opacity-0 group-hover:opacity-100 transition-opacity" })), _jsxs(TextLink, { href: href, className: "flex h-full flex-col", children: [_jsx(Div, { className: `aspect-video ${__O.hidden} flex-shrink-0`, children: coverImageUrl ? (_jsx(Div, { role: "img", "aria-label": safeTitle, className: "h-full w-full bg-cover bg-center transition-transform duration-300 group-hover:scale-105",
|
|
35
|
+
// audit-inline-style-ok: dynamic image URL
|
|
36
|
+
style: { backgroundImage: `url(${coverImageUrl})` } })) : (_jsx(Div, { role: "img", "aria-label": safeTitle, className: "h-full w-full bg-gradient-to-br from-zinc-200 via-zinc-100 to-zinc-300 dark:from-slate-800 dark:via-slate-700 dark:to-slate-900" })) }), _jsxs(Div, { className: `flex flex-1 flex-col ${__P.p4}`, children: [_jsxs(Row, { className: "gap-1.5 mb-2 flex-wrap", children: [_jsx(Span, { size: "xs", weight: "medium", className: `inline-block px-2 py-0.5 rounded-full capitalize ${CATEGORY_BADGE[post.category] ?? "bg-zinc-100 text-zinc-700 dark:bg-zinc-800 dark:text-zinc-300"}`, children: post.category }), post.isFeatured && (_jsx(Span, { size: "xs", weight: "medium", className: CLS_FEATURED_BADGE, children: labels.featuredBadge ?? "Featured" }))] }), _jsx(Heading, { level: 3, className: `font-semibold text-neutral-900 dark:text-zinc-100 text-base leading-snug mb-1 ${THEME_CONSTANTS.utilities.textClamp2}`, children: safeTitle }), post.excerpt && (_jsx(Text, { className: `text-neutral-500 dark:text-zinc-400 text-sm ${THEME_CONSTANTS.utilities.textClamp2} mb-3`, children: post.excerpt })), _jsxs(Row, { className: "mt-auto gap-3 text-xs text-zinc-400 dark:text-zinc-400 flex-wrap", children: [post.authorName && (_jsx(Span, { children: safeDisplayName(post.authorName, "Author") })), post.readTimeMinutes != null && (_jsxs(Span, { children: [post.readTimeMinutes, " ", labels.readTime ?? "min read"] })), date && _jsx(Span, { children: date })] })] })] })] }));
|
|
35
37
|
}
|
|
@@ -26,7 +26,11 @@ export function BlogCard({ post, href, onClick, className = "" }) {
|
|
|
26
26
|
const isInteractive = !!(href || onClick);
|
|
27
27
|
const card = (_jsxs(Article, { role: onClick && !href ? "button" : undefined, tabIndex: onClick && !href ? 0 : undefined, onKeyDown: onClick && !href
|
|
28
28
|
? (e) => (e.key === "Enter" || e.key === " ") && onClick(post)
|
|
29
|
-
: undefined, onClick: onClick && !href ? () => onClick(post) : undefined, className: `group flex flex-col h-full overflow-hidden rounded-xl border border-neutral-200 dark:border-slate-700 bg-white dark:bg-slate-900 shadow-sm transition hover:shadow-md ${isInteractive ? "cursor-pointer" : ""} ${className}`, children: [_jsx(Div, { className: `aspect-video w-full ${__O.hidden} bg-neutral-100 dark:bg-slate-800 flex-shrink-0`, children: coverImageUrl ? (_jsx(Div, { role: "img", "aria-label": post.title, className: "h-full w-full bg-center bg-cover transition-transform duration-300 group-hover:scale-105",
|
|
29
|
+
: undefined, onClick: onClick && !href ? () => onClick(post) : undefined, className: `group flex flex-col h-full overflow-hidden rounded-xl border border-neutral-200 dark:border-slate-700 bg-white dark:bg-slate-900 shadow-sm transition hover:shadow-md ${isInteractive ? "cursor-pointer" : ""} ${className}`, children: [_jsx(Div, { className: `aspect-video w-full ${__O.hidden} bg-neutral-100 dark:bg-slate-800 flex-shrink-0`, children: coverImageUrl ? (_jsx(Div, { role: "img", "aria-label": post.title, className: "h-full w-full bg-center bg-cover transition-transform duration-300 group-hover:scale-105",
|
|
30
|
+
// audit-inline-style-ok: dynamic image URL
|
|
31
|
+
style: { backgroundImage: `url(${coverImageUrl})` } })) : (_jsx(Div, { className: "h-full w-full bg-gradient-to-br from-zinc-200 via-zinc-100 to-zinc-300 dark:from-slate-800 dark:via-slate-700 dark:to-slate-900 flex items-center justify-center", children: _jsx(Span, { className: "text-4xl opacity-30", "aria-hidden": "true", children: "\u270D\uFE0F" }) })) }), _jsxs(Div, { className: `flex flex-1 flex-col ${__P.p5}`, children: [_jsxs(Row, { className: "mb-2 gap-2 flex-wrap", children: [_jsx(Span, { size: "xs", weight: "medium", className: "rounded-full bg-primary/10 px-2 py-0.5 capitalize text-primary", children: post.category }), post.isFeatured && (_jsx(Span, { size: "xs", weight: "medium", className: CLS_FEATURED_BADGE, children: "Featured" })), post.readTimeMinutes && (_jsxs(Span, { size: "xs", className: "text-zinc-400 dark:text-zinc-400", children: [post.readTimeMinutes, " min read"] }))] }), _jsx(Heading, { level: 3, className: `${THEME_CONSTANTS.utilities.textClamp2} text-base font-semibold text-neutral-900 dark:text-zinc-100 group-hover:text-primary`, children: post.title }), post.excerpt && (_jsx(Text, { className: `mt-2 ${THEME_CONSTANTS.utilities.textClamp3} flex-1 text-sm text-neutral-500 dark:text-zinc-400`, children: post.excerpt })), _jsxs(Row, { className: "mt-auto pt-3 gap-3", children: [post.authorAvatar ? (_jsx(Div, { role: "img", "aria-label": post.authorName ?? "author", className: "h-7 w-7 flex-shrink-0 rounded-full bg-center bg-cover",
|
|
32
|
+
// audit-inline-style-ok: dynamic image URL
|
|
33
|
+
style: { backgroundImage: `url(${post.authorAvatar})` } })) : post.authorName ? (_jsx(Div, { className: "flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-full bg-primary/10 text-xs font-bold text-primary", children: post.authorName.charAt(0).toUpperCase() })) : null, _jsxs(Text, { className: "text-xs text-neutral-500 dark:text-zinc-400 min-w-0", children: [post.authorName && (_jsx(Span, { weight: "medium", className: "text-neutral-700 dark:text-zinc-300", children: safeDisplayName(post.authorName, "Author") })), date && _jsxs(Span, { className: "ml-1 text-neutral-400", children: ["\u00B7 ", date] })] })] })] })] }));
|
|
30
34
|
if (href) {
|
|
31
35
|
return (_jsx(Link, { href: href, className: "block h-full", children: card }));
|
|
32
36
|
}
|
|
@@ -53,5 +53,7 @@ export function BlogPostView({ slug, initialData, labels = {}, renderImage, rend
|
|
|
53
53
|
})
|
|
54
54
|
: "";
|
|
55
55
|
const coverImageUrl = getMediaUrl(post.coverImage);
|
|
56
|
-
return (_jsxs(Div, { className: `min-h-screen ${className}`, children: [coverImageUrl && (_jsxs(Div, { className: `relative h-72 md:h-96 ${__O.hidden}`, children: [renderImage ? (renderImage(post)) : (_jsx(Div, { role: "img", "aria-label": post.title, className: "h-full w-full bg-cover bg-center",
|
|
56
|
+
return (_jsxs(Div, { className: `min-h-screen ${className}`, children: [coverImageUrl && (_jsxs(Div, { className: `relative h-72 md:h-96 ${__O.hidden}`, children: [renderImage ? (renderImage(post)) : (_jsx(Div, { role: "img", "aria-label": post.title, className: "h-full w-full bg-cover bg-center",
|
|
57
|
+
// audit-inline-style-ok: dynamic image URL
|
|
58
|
+
style: { backgroundImage: `url(${coverImageUrl})` } })), _jsx(Div, { className: "absolute inset-0 bg-gradient-to-t from-black/50 to-transparent" })] })), _jsxs(Div, { className: "max-w-3xl mx-auto px-4 py-12", children: [renderBlogPostHeader(post, date, labels), post.tags && post.tags.length > 0 && (_jsx(Row, { wrap: true, gap: "sm", className: "mb-8", children: post.tags.map((tag) => (_jsxs(Link, { href: `${String(ROUTES.PUBLIC.BLOG)}?tags=${encodeURIComponent(tag)}`, className: "inline-block px-3 py-1 rounded-full bg-neutral-100 dark:bg-zinc-800 hover:bg-primary/10 text-zinc-600 dark:text-zinc-400 hover:text-primary text-xs font-medium transition-colors", children: ["#", tag] }, tag))) })), renderAuthorBio && _jsx(Div, { className: "mb-6", children: renderAuthorBio(post) }), _jsx(Div, { surface: "card", padding: "xl", className: "mb-12", children: renderContent ? renderContent(post) : (_jsx(RichText, { html: normalizeRichTextHtml(post.content ?? ""), proseClass: "prose max-w-none dark:prose-invert" })) }), renderBlogPostRelated(related, labels, renderRelatedCard), renderBackButton && _jsx(Div, { className: "mt-10 pt-8 border-t border-zinc-200 dark:border-zinc-700", children: renderBackButton() })] })] }));
|
|
57
59
|
}
|
|
@@ -180,6 +180,7 @@ export declare const blogPostSchema: z.ZodObject<{
|
|
|
180
180
|
content?: string | undefined;
|
|
181
181
|
isFeatured?: boolean | undefined;
|
|
182
182
|
publishedAt?: string | undefined;
|
|
183
|
+
excerpt?: string | undefined;
|
|
183
184
|
coverImage?: string | {
|
|
184
185
|
url: string;
|
|
185
186
|
type: "image" | "video" | "file";
|
|
@@ -188,7 +189,6 @@ export declare const blogPostSchema: z.ZodObject<{
|
|
|
188
189
|
source?: "upload" | "youtube" | "external" | undefined;
|
|
189
190
|
youtubeId?: string | undefined;
|
|
190
191
|
} | null | undefined;
|
|
191
|
-
excerpt?: string | undefined;
|
|
192
192
|
authorId?: string | undefined;
|
|
193
193
|
authorName?: string | undefined;
|
|
194
194
|
readTimeMinutes?: number | undefined;
|
|
@@ -12,7 +12,9 @@ const __O = {
|
|
|
12
12
|
};
|
|
13
13
|
const CLS_REMOVE_BTN = "self-start text-zinc-400 dark:text-zinc-400 transition hover:text-red-500";
|
|
14
14
|
export function CartItemRow({ item, onQtyChange, onRemove, href, isOutOfStock = false }) {
|
|
15
|
-
return (_jsxs(Div, { surface: "card", padding: "sm", className: `flex gap-4 transition-opacity ${isOutOfStock ? "opacity-60" : ""}`, children: [_jsx(Div, { className: `h-20 w-20 flex-shrink-0 ${__O.hidden} rounded-lg bg-neutral-100 dark:bg-slate-800`, children: item.meta.image && (_jsx(Div, { role: "img", "aria-label": item.meta.title, className: "h-full w-full bg-center bg-cover",
|
|
15
|
+
return (_jsxs(Div, { surface: "card", padding: "sm", className: `flex gap-4 transition-opacity ${isOutOfStock ? "opacity-60" : ""}`, children: [_jsx(Div, { className: `h-20 w-20 flex-shrink-0 ${__O.hidden} rounded-lg bg-neutral-100 dark:bg-slate-800`, children: item.meta.image && (_jsx(Div, { role: "img", "aria-label": item.meta.title, className: "h-full w-full bg-center bg-cover",
|
|
16
|
+
// audit-inline-style-ok: dynamic image URL
|
|
17
|
+
style: { backgroundImage: `url(${item.meta.image})` } })) }), _jsxs(Div, { className: "flex flex-1 flex-col justify-between", children: [_jsxs(Div, { className: "flex items-start gap-1.5", children: [href ? (_jsx("a", { href: href, target: "_blank", rel: "noopener noreferrer", className: `font-medium text-neutral-900 dark:text-zinc-100 hover:underline underline-offset-2 ${THEME_CONSTANTS.utilities.textClamp2}`, children: item.meta.title })) : (_jsx(Text, { className: `font-medium text-neutral-900 dark:text-zinc-100 ${THEME_CONSTANTS.utilities.textClamp2}`, children: item.meta.title })), isOutOfStock && (_jsx(Span, { weight: "semibold", className: "flex-shrink-0 rounded bg-error-surface px-1.5 py-0.5 text-[10px] uppercase tracking-wide text-error", children: "Out of Stock" }))] }), item.meta.attributes &&
|
|
16
18
|
Object.keys(item.meta.attributes).length > 0 && (_jsx(Text, { className: "text-xs text-neutral-500 dark:text-zinc-400", children: Object.entries(item.meta.attributes)
|
|
17
19
|
.map(([k, v]) => `${k}: ${v}`)
|
|
18
20
|
.join(", ") })), _jsxs(Row, { justify: "between", children: [_jsx(Text, { className: "font-semibold text-neutral-900 dark:text-zinc-100", children: formatCurrency(item.meta.price * item.quantity, item.meta.currency) }), onQtyChange && !isOutOfStock && (_jsxs(Row, { className: "gap-2", children: [_jsx(Button, { onClick: () => onQtyChange(item.id, item.quantity - 1), disabled: item.quantity <= 1, variant: "outline", size: "sm", className: "flex h-7 w-7 items-center justify-center rounded-full border border-neutral-200 dark:border-slate-600 text-sm disabled:opacity-40", children: "\u2212" }), _jsx(Span, { size: "sm", className: "min-w-[1.5rem] text-center", children: item.quantity }), _jsx(Button, { onClick: () => onQtyChange(item.id, item.quantity + 1), variant: "outline", size: "sm", className: "flex h-7 w-7 items-center justify-center rounded-full border border-neutral-200 dark:border-slate-600 text-sm", children: "+" })] })), isOutOfStock && (_jsxs(Span, { size: "xs", className: "text-zinc-400 dark:text-zinc-400", children: ["Qty: ", item.quantity] }))] })] }), onRemove && (_jsx(Button, { onClick: () => onRemove(item.id), variant: "ghost", size: "sm", "aria-label": ACTIONS.CART["remove-item"].ariaLabel, className: CLS_REMOVE_BTN, children: "\u2715" }))] }));
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from "crypto";
|
|
2
2
|
import { DatabaseError, NotFoundError, ValidationError } from "../../../errors";
|
|
3
|
+
import { CART_FIELDS } from "../../../constants/field-names";
|
|
3
4
|
const ERR_CART_ITEM_NOT_FOUND = "Cart item not found";
|
|
4
5
|
const ERR_CART_ITEM_LOCKED = "This item requires payment and cannot be removed or modified.";
|
|
5
6
|
import { BaseRepository, prepareForFirestore, } from "../../../providers/db-firebase";
|
|
@@ -277,7 +278,7 @@ export class CartRepository extends BaseRepository {
|
|
|
277
278
|
cutoff.setDate(cutoff.getDate() - ttlDays);
|
|
278
279
|
const snap = await this.db
|
|
279
280
|
.collection(this.collection)
|
|
280
|
-
.where(
|
|
281
|
+
.where(CART_FIELDS.UPDATED_AT, "<", cutoff)
|
|
281
282
|
.limit(500)
|
|
282
283
|
.get();
|
|
283
284
|
return snap.docs.map((d) => d.ref);
|
|
@@ -86,5 +86,9 @@ export async function BrandDetailPageView({ slug, initialBrand }) {
|
|
|
86
86
|
counts.preOrders +
|
|
87
87
|
counts.prizeDraws +
|
|
88
88
|
counts.bundles;
|
|
89
|
-
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",
|
|
89
|
+
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",
|
|
90
|
+
// audit-inline-style-ok: dynamic image URL
|
|
91
|
+
style: { backgroundImage: `url(${coverImage})` } }), _jsx(Div, { className: "absolute inset-0 bg-black/55" })] })), !hasCover && (_jsx("div", { className: "absolute inset-0 opacity-10",
|
|
92
|
+
// audit-inline-style-ok: runtime theme color
|
|
93
|
+
style: { backgroundColor: brandColor } })), _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.BRANDS ?? "/brands"), className: hasCover ? "text-white/70 hover:text-white transition-colors" : "text-zinc-500 dark:text-zinc-400 hover:text-primary-600 transition-colors", children: "Brands" }), _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: brand?.name ?? slug })] }), _jsxs(Div, { className: "flex items-center gap-4 mb-3", children: [brand?.display?.icon && (_jsx(Span, { className: "text-5xl leading-none", children: brand.display.icon })), _jsxs(_Fragment, { children: [_jsx(Heading, { level: 1, className: `text-3xl md:text-4xl font-bold ${hasCover ? "text-white" : "text-zinc-900 dark:text-zinc-50"}`, children: brand?.name ?? slug }), brand?.description && typeof brand.description === "string" && !brand.description.startsWith("{") && (_jsx(Text, { className: `text-base max-w-2xl mt-1 ${hasCover ? "text-white/80" : "text-zinc-600 dark:text-zinc-400"}`, children: brand.description }))] })] }), _jsxs(Div, { className: "flex flex-wrap gap-2 mt-3", children: [counts.products > 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: [counts.products.toLocaleString(), " ", counts.products === 1 ? "product" : "products"] })), counts.auctions > 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: [counts.auctions.toLocaleString(), " ", counts.auctions === 1 ? "auction" : "auctions"] })), counts.preOrders > 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: [counts.preOrders.toLocaleString(), " ", counts.preOrders === 1 ? "pre-order" : "pre-orders"] })), totalItems === 0 && (_jsx(Span, { size: "sm", className: hasCover ? "text-white/60" : "text-zinc-400", children: "No items listed yet" }))] })] })] }), _jsx(Section, { className: "py-6", children: _jsx(Container, { size: "xl", children: brandName ? (_jsx(BrandDetailTabs, { brandName: brandName, initialProductsData: productsResult ?? undefined, initialBundles: brandBundles, counts: counts })) : (_jsx(Text, { className: "py-12 text-center text-sm text-zinc-500 dark:text-zinc-400", children: "Brand not found." })) }) })] }));
|
|
90
94
|
}
|
|
@@ -112,7 +112,9 @@ export async function CategoryDetailPageView({ slug }) {
|
|
|
112
112
|
const totalCount = productCount + auctionCount + preOrderCount + prizeDrawCount + bundleCount + storeCount;
|
|
113
113
|
const coverImage = category?.display?.coverImage;
|
|
114
114
|
const hasCover = Boolean(coverImage);
|
|
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",
|
|
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
|
+
// 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: _jsxs(Div, { className: "max-w-7xl mx-auto px-4 py-3", children: ["// audit-inline-style-ok: dynamic CSS", _jsx(Div, { className: `flex gap-2.5 ${__O.xAuto} pb-1`, 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: {
|
|
116
118
|
products: productCount,
|
|
117
119
|
auctions: auctionCount,
|
|
118
120
|
preOrders: preOrderCount,
|
|
@@ -12,7 +12,11 @@ const __O = {
|
|
|
12
12
|
const CLS_FEATURED_DOT = "absolute left-2 top-2 rounded-full bg-amber-400 p-1 leading-none";
|
|
13
13
|
export function CategoryCard({ category, href, onClick, className = "", }) {
|
|
14
14
|
const productCount = category.metrics?.productCount ?? category.productCount ?? 0;
|
|
15
|
-
const inner = (_jsxs(Div, { className: "flex h-full flex-col", children: [_jsxs(Div, { className: `relative aspect-[4/3] w-full ${__O.hidden} bg-gradient-to-br from-neutral-100 to-neutral-200 dark:from-zinc-800 dark:to-zinc-700 flex-shrink-0`, children: [category.display?.coverImage ? (_jsx(Div, { role: "img", "aria-label": category.name, className: "h-full w-full bg-center bg-cover transition-transform duration-300 group-hover:scale-105",
|
|
15
|
+
const inner = (_jsxs(Div, { className: "flex h-full flex-col", children: [_jsxs(Div, { className: `relative aspect-[4/3] w-full ${__O.hidden} bg-gradient-to-br from-neutral-100 to-neutral-200 dark:from-zinc-800 dark:to-zinc-700 flex-shrink-0`, children: [category.display?.coverImage ? (_jsx(Div, { role: "img", "aria-label": category.name, className: "h-full w-full bg-center bg-cover transition-transform duration-300 group-hover:scale-105",
|
|
16
|
+
// audit-inline-style-ok: dynamic image URL
|
|
17
|
+
style: { backgroundImage: `url(${category.display.coverImage})` } })) : category.display?.color ? (_jsx(Div, { className: "h-full w-full opacity-80",
|
|
18
|
+
// audit-inline-style-ok: runtime theme color
|
|
19
|
+
style: { backgroundColor: category.display.color } })) : null, category.display?.icon && (_jsx(Div, { className: "absolute inset-0 flex items-center justify-center text-4xl", children: category.display.icon })), category.isFeatured && (_jsx(Span, { size: "xs", className: CLS_FEATURED_DOT, children: "\u2605" }))] }), _jsxs(Div, { className: `flex flex-1 flex-col ${__P.p3}.5`, children: [_jsx(Text, { className: `font-semibold text-sm leading-snug ${THEME_CONSTANTS.utilities.textClamp2} text-neutral-900 dark:text-white`, children: category.name }), category.description && (_jsx(Text, { className: `mt-1 text-xs ${THEME_CONSTANTS.utilities.textClamp2} text-neutral-500 dark:text-zinc-400 flex-1`, children: category.description })), _jsxs(Div, { className: "mt-2 flex items-center justify-between gap-2", children: [_jsxs(Text, { className: "text-xs text-zinc-400 dark:text-zinc-400", children: [productCount.toLocaleString(), " ", productCount === 1 ? "item" : "items"] }), _jsxs(Span, { size: "xs", weight: "medium", className: "inline-flex items-center gap-1 rounded-md border border-zinc-200 dark:border-zinc-700 px-2.5 py-1 text-zinc-600 dark:text-zinc-300 group-hover:bg-primary group-hover:border-primary group-hover:text-white transition-colors", children: ["Browse ", _jsx(ArrowRight, { className: "h-3 w-3" })] })] })] })] }));
|
|
16
20
|
const cardClass = `group relative flex flex-col overflow-hidden rounded-xl border border-neutral-200 bg-white dark:border-zinc-700 dark:bg-zinc-900 shadow-sm transition hover:shadow-md h-full ${className}`;
|
|
17
21
|
if (href) {
|
|
18
22
|
return (_jsx(Link, { href: href, className: cardClass, children: inner }));
|
|
@@ -22,13 +22,17 @@ export function getCategoryTableColumns(onEdit, onDelete, labels = {}) {
|
|
|
22
22
|
{
|
|
23
23
|
key: "image",
|
|
24
24
|
header: "",
|
|
25
|
-
render: (cat) => (_jsx(Div, { className: `relative w-9 h-9 rounded ${__O.hidden} flex-shrink-0`, children: cat.display?.coverImage ? (_jsx(MediaImage, { src: cat.display.coverImage, alt: "", size: "thumbnail", className: "w-full h-full object-cover" })) : (_jsx(Div, { className: "w-full h-full flex items-center justify-center text-base",
|
|
25
|
+
render: (cat) => (_jsx(Div, { className: `relative w-9 h-9 rounded ${__O.hidden} flex-shrink-0`, children: cat.display?.coverImage ? (_jsx(MediaImage, { src: cat.display.coverImage, alt: "", size: "thumbnail", className: "w-full h-full object-cover" })) : (_jsx(Div, { className: "w-full h-full flex items-center justify-center text-base",
|
|
26
|
+
// audit-inline-style-ok: runtime theme color
|
|
27
|
+
style: { backgroundColor: cat.display?.color ?? "#94a3b8", color: "#fff" }, children: cat.display?.icon ?? "🗂️" })) })),
|
|
26
28
|
},
|
|
27
29
|
{
|
|
28
30
|
key: "name",
|
|
29
31
|
header: L.colName,
|
|
30
32
|
sortable: true,
|
|
31
|
-
render: (cat) => (
|
|
33
|
+
render: (cat) => (
|
|
34
|
+
// audit-inline-style-ok: computed pixel offset
|
|
35
|
+
_jsxs(Div, { style: { paddingLeft: `${cat.tier * 20}px` }, children: [cat.name, cat.tier > 0 && (_jsxs(Span, { size: "xs", className: "text-zinc-400 dark:text-zinc-400 ml-2", children: ["(Tier ", cat.tier, ")"] }))] })),
|
|
32
36
|
},
|
|
33
37
|
{
|
|
34
38
|
key: "slug",
|
|
@@ -19,7 +19,9 @@ function CategoryTreeNode({ item, children = [], activeId, onSelect, depth = 0,
|
|
|
19
19
|
setExpanded((p) => !p);
|
|
20
20
|
onSelect?.(item);
|
|
21
21
|
}, className: `flex cursor-pointer items-center gap-1 rounded-md px-2 py-1.5 text-sm transition select-none
|
|
22
|
-
${isActive ? "bg-primary-50 font-semibold text-primary-700 dark:bg-primary-900/30 dark:text-primary-400" : "text-neutral-700 hover:bg-neutral-50 dark:text-zinc-300 dark:hover:bg-zinc-800"}`,
|
|
22
|
+
${isActive ? "bg-primary-50 font-semibold text-primary-700 dark:bg-primary-900/30 dark:text-primary-400" : "text-neutral-700 hover:bg-neutral-50 dark:text-zinc-300 dark:hover:bg-zinc-800"}`,
|
|
23
|
+
// audit-inline-style-ok: computed pixel offset
|
|
24
|
+
style: { paddingLeft: `${8 + indent}px` }, children: [hasChildren && (_jsx(Span, { size: "xs", "aria-hidden": "true", className: `text-zinc-400 dark:text-zinc-400 transition-transform duration-150 ${expanded ? "rotate-90" : ""}`, children: "\u25B6" })), !hasChildren && _jsx(Span, { className: "w-3" }), item.display?.icon && (_jsx(Span, { size: "base", "aria-hidden": "true", className: "flex-shrink-0", children: item.display.icon })), _jsx(Text, { className: "flex-1 truncate", children: item.name }), item.metrics && (_jsx(Span, { size: "xs", className: "ml-auto flex-shrink-0 text-zinc-400 dark:text-zinc-400 tabular-nums", children: item.metrics.productCount }))] }), hasChildren && expanded && (_jsx(Div, { role: "group", children: children.map((child) => (_jsx(CategoryTreeNode, { item: child, activeId: activeId, onSelect: onSelect, depth: depth + 1 }, child.id))) }))] }));
|
|
23
25
|
}
|
|
24
26
|
/**
|
|
25
27
|
* CategoryTree — hierarchical expandable navigation for categories.
|
|
@@ -2,5 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { Button, Div, Heading, Text } from "../../../ui";
|
|
3
3
|
import { THEME_CONSTANTS } from "../../../tokens";
|
|
4
4
|
export function ConcernCard({ concern, onClick, className = "", }) {
|
|
5
|
-
return (_jsxs(Button, { type: "button", variant: "outline", onClick: () => onClick?.(concern), className: `group flex flex-col items-center gap-3 rounded-xl border border-neutral-200 dark:border-slate-700 bg-white dark:bg-slate-900 p-4 text-center shadow-sm transition hover:border-primary dark:hover:border-primary-400 hover:shadow-md ${className}`, children: [concern.display?.coverImage && (_jsx(Div, { role: "img", "aria-label": concern.name, className: "h-16 w-16 rounded-full bg-center bg-cover",
|
|
5
|
+
return (_jsxs(Button, { type: "button", variant: "outline", onClick: () => onClick?.(concern), className: `group flex flex-col items-center gap-3 rounded-xl border border-neutral-200 dark:border-slate-700 bg-white dark:bg-slate-900 p-4 text-center shadow-sm transition hover:border-primary dark:hover:border-primary-400 hover:shadow-md ${className}`, children: [concern.display?.coverImage && (_jsx(Div, { role: "img", "aria-label": concern.name, className: "h-16 w-16 rounded-full bg-center bg-cover",
|
|
6
|
+
// audit-inline-style-ok: dynamic image URL
|
|
7
|
+
style: { backgroundImage: `url(${concern.display.coverImage})` } })), _jsxs(Div, { children: [_jsx(Heading, { level: 3, className: "text-sm font-semibold text-neutral-900 dark:text-zinc-100 group-hover:text-primary dark:group-hover:text-primary-400", children: concern.name }), concern.description && (_jsx(Text, { className: `mt-1 text-xs ${THEME_CONSTANTS.utilities.textClamp2} text-neutral-500 dark:text-zinc-400`, children: concern.description }))] })] }));
|
|
6
8
|
}
|