@mohasinac/appkit 4.4.4 → 4.5.0
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 +2 -0
- package/dist/_internal/server/features/orders/adapters.js +1 -0
- package/dist/features/account/hooks/useDataViewMode.js +1 -1
- package/dist/features/admin/components/AdminAddressesView.js +5 -0
- package/dist/features/admin/components/AdminArtView.js +4 -0
- package/dist/features/admin/components/AdminClassifiedView.js +4 -0
- package/dist/features/admin/components/AdminContactView.js +6 -0
- package/dist/features/admin/components/AdminDigitalCodesView.js +4 -0
- package/dist/features/admin/components/AdminGroupedListingsView.js +14 -8
- package/dist/features/admin/components/AdminLiveView.js +4 -0
- package/dist/features/admin/components/AdminOrdersView.js +6 -0
- package/dist/features/admin/components/AdminProductsView.js +22 -4
- package/dist/features/admin/components/AdminReturnRequestsView.js +5 -0
- package/dist/features/admin/components/AdminReviewsView.js +4 -3
- package/dist/features/admin/components/AdminScammersView.js +6 -0
- package/dist/features/admin/components/AdminStickersView.js +4 -0
- package/dist/features/admin/components/AdminSublistingCategoriesView.js +2 -0
- package/dist/features/admin/components/AdminSupportTicketsView.js +6 -0
- package/dist/features/admin/components/AdminTeamView.js +3 -0
- package/dist/features/admin/components/AdminViewCards.d.ts +3 -1
- package/dist/features/admin/components/AdminViewCards.js +7 -7
- package/dist/features/admin/components/DataListingView.d.ts +1 -1
- package/dist/features/admin/components/DataListingView.js +26 -2
- package/dist/features/admin/hooks/useAdminListing.js +1 -1
- package/dist/features/auth/actions/profile-actions.js +3 -3
- package/dist/features/orders/schemas/firestore.d.ts +2 -0
- package/dist/features/products/components/ProductDetailPageView.js +1 -3
- package/dist/features/products/constants/action-defs.d.ts +13 -2
- package/dist/features/products/constants/action-defs.js +33 -0
- package/dist/features/reviews/components/ReviewDetailShell.js +3 -3
- package/dist/features/reviews/components/ReviewModal.js +1 -1
- package/dist/features/reviews/components/ReviewsList.js +1 -1
- package/dist/features/reviews/types/index.d.ts +2 -6
- package/dist/features/seller/components/SellerArtView.js +2 -0
- package/dist/features/seller/components/SellerClassifiedView.js +2 -0
- package/dist/features/seller/components/SellerDigitalCodesView.js +2 -0
- package/dist/features/seller/components/SellerGroupedListingsView.js +2 -0
- package/dist/features/seller/components/SellerLiveView.js +2 -0
- package/dist/features/seller/components/SellerPayoutsView.js +3 -0
- package/dist/features/seller/components/SellerProductsView.js +20 -2
- package/dist/features/seller/components/SellerStickersView.js +2 -0
- package/dist/features/tester/seed-data/orders-tester-seed-data.js +19 -1
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +39 -0
- package/dist/index.d.ts +0 -1
- package/dist/react/contexts/SessionContext.js +25 -1
- package/dist/seed/orders-seed-data.js +25 -1
- package/dist/styles.css +1 -1
- package/dist/tailwind-utilities.css +1 -1
- package/package.json +1 -1
|
@@ -362,6 +362,11 @@ export const DASHBOARD_QUICK_ACTION_ID = {
|
|
|
362
362
|
ADMIN_FAQS: "dqa-admin-faqs",
|
|
363
363
|
ADMIN_CAROUSEL: "dqa-admin-carousel",
|
|
364
364
|
ADMIN_SECTIONS: "dqa-admin-sections",
|
|
365
|
+
ADMIN_BLOG: "dqa-admin-blog",
|
|
366
|
+
ADMIN_SCAMMERS: "dqa-admin-scammers",
|
|
367
|
+
ADMIN_BUNDLES: "dqa-admin-bundles",
|
|
368
|
+
ADMIN_PRIZE_DRAWS: "dqa-admin-prize-draws",
|
|
369
|
+
ADMIN_TESTER_CHECKLIST: "dqa-admin-tester-checklist",
|
|
365
370
|
// Seller / Store
|
|
366
371
|
SELLER_ADD_PRODUCT: "dqa-seller-add-product",
|
|
367
372
|
SELLER_ADD_COUPON: "dqa-seller-add-coupon",
|
|
@@ -376,6 +381,12 @@ export const DASHBOARD_QUICK_ACTION_ID = {
|
|
|
376
381
|
SELLER_PRODUCTS: "dqa-seller-products",
|
|
377
382
|
SELLER_STOREFRONT: "dqa-seller-storefront",
|
|
378
383
|
SELLER_ANALYTICS: "dqa-seller-analytics",
|
|
384
|
+
SELLER_BUNDLES: "dqa-seller-bundles",
|
|
385
|
+
SELLER_PRIZE_DRAWS: "dqa-seller-prize-draws",
|
|
386
|
+
SELLER_OFFERS: "dqa-seller-offers",
|
|
387
|
+
SELLER_GROUPED_LISTINGS: "dqa-seller-grouped-listings",
|
|
388
|
+
SELLER_CATEGORIES: "dqa-seller-categories",
|
|
389
|
+
SELLER_PRINT_CENTER: "dqa-seller-print-center",
|
|
379
390
|
// User / Buyer
|
|
380
391
|
USER_VIEW_ORDERS: "dqa-user-view-orders",
|
|
381
392
|
USER_VIEW_WISHLIST: "dqa-user-view-wishlist",
|
|
@@ -407,6 +418,11 @@ export const DASHBOARD_QUICK_ACTION_META = {
|
|
|
407
418
|
[DASHBOARD_QUICK_ACTION_ID.ADMIN_FAQS]: { id: DASHBOARD_QUICK_ACTION_ID.ADMIN_FAQS, label: "FAQs", variant: "outline", iconName: "HelpCircle", requiresAuth: true, requiredRole: "admin", requiredPermission: "faqs.read" },
|
|
408
419
|
[DASHBOARD_QUICK_ACTION_ID.ADMIN_CAROUSEL]: { id: DASHBOARD_QUICK_ACTION_ID.ADMIN_CAROUSEL, label: "Carousel", variant: "outline", iconName: "Layout", requiresAuth: true, requiredRole: "admin", requiredPermission: "admin.settings.read" },
|
|
409
420
|
[DASHBOARD_QUICK_ACTION_ID.ADMIN_SECTIONS]: { id: DASHBOARD_QUICK_ACTION_ID.ADMIN_SECTIONS, label: "Sections", variant: "outline", iconName: "Layers", requiresAuth: true, requiredRole: "admin", requiredPermission: "admin.settings.read" },
|
|
421
|
+
[DASHBOARD_QUICK_ACTION_ID.ADMIN_BLOG]: { id: DASHBOARD_QUICK_ACTION_ID.ADMIN_BLOG, label: "Blog", variant: "outline", iconName: "BookOpen", requiresAuth: true, requiredRole: "admin", requiredPermission: "blog.read" },
|
|
422
|
+
[DASHBOARD_QUICK_ACTION_ID.ADMIN_SCAMMERS]: { id: DASHBOARD_QUICK_ACTION_ID.ADMIN_SCAMMERS, label: "Scammer Registry", variant: "outline", iconName: "AlertTriangle", requiresAuth: true, requiredRole: "admin", requiredPermission: "moderation.read" },
|
|
423
|
+
[DASHBOARD_QUICK_ACTION_ID.ADMIN_BUNDLES]: { id: DASHBOARD_QUICK_ACTION_ID.ADMIN_BUNDLES, label: "Bundles", variant: "outline", iconName: "Package2", requiresAuth: true, requiredRole: "admin", requiredPermission: "products.read" },
|
|
424
|
+
[DASHBOARD_QUICK_ACTION_ID.ADMIN_PRIZE_DRAWS]: { id: DASHBOARD_QUICK_ACTION_ID.ADMIN_PRIZE_DRAWS, label: "Prize Draws", variant: "outline", iconName: "Gift", requiresAuth: true, requiredRole: "admin", requiredPermission: "products.read" },
|
|
425
|
+
[DASHBOARD_QUICK_ACTION_ID.ADMIN_TESTER_CHECKLIST]: { id: DASHBOARD_QUICK_ACTION_ID.ADMIN_TESTER_CHECKLIST, label: "Tester Checklist", variant: "outline", iconName: "ClipboardCheck", requiresAuth: true, requiredRole: "admin", requiredPermission: "admin.settings.read" },
|
|
410
426
|
[DASHBOARD_QUICK_ACTION_ID.SELLER_ADD_PRODUCT]: { id: DASHBOARD_QUICK_ACTION_ID.SELLER_ADD_PRODUCT, label: "List a Product", variant: "primary", iconName: "Plus", requiresAuth: true, requiredRole: "seller", requiredPermission: "products.create" },
|
|
411
427
|
[DASHBOARD_QUICK_ACTION_ID.SELLER_ADD_COUPON]: { id: DASHBOARD_QUICK_ACTION_ID.SELLER_ADD_COUPON, label: "New Coupon", variant: "outline", iconName: "Tag", requiresAuth: true, requiredRole: "seller", requiredPermission: "coupons.create" },
|
|
412
428
|
[DASHBOARD_QUICK_ACTION_ID.SELLER_VIEW_ORDERS]: { id: DASHBOARD_QUICK_ACTION_ID.SELLER_VIEW_ORDERS, label: "Orders", variant: "outline", iconName: "ShoppingBag", requiresAuth: true, requiredRole: "seller" },
|
|
@@ -420,6 +436,12 @@ export const DASHBOARD_QUICK_ACTION_META = {
|
|
|
420
436
|
[DASHBOARD_QUICK_ACTION_ID.SELLER_PRODUCTS]: { id: DASHBOARD_QUICK_ACTION_ID.SELLER_PRODUCTS, label: "My Products", variant: "outline", iconName: "Package", requiresAuth: true, requiredRole: "seller" },
|
|
421
437
|
[DASHBOARD_QUICK_ACTION_ID.SELLER_STOREFRONT]: { id: DASHBOARD_QUICK_ACTION_ID.SELLER_STOREFRONT, label: "My Storefront", variant: "outline", iconName: "Store", requiresAuth: true, requiredRole: "seller" },
|
|
422
438
|
[DASHBOARD_QUICK_ACTION_ID.SELLER_ANALYTICS]: { id: DASHBOARD_QUICK_ACTION_ID.SELLER_ANALYTICS, label: "Analytics", variant: "outline", iconName: "BarChart", requiresAuth: true, requiredRole: "seller" },
|
|
439
|
+
[DASHBOARD_QUICK_ACTION_ID.SELLER_BUNDLES]: { id: DASHBOARD_QUICK_ACTION_ID.SELLER_BUNDLES, label: "Bundles", variant: "outline", iconName: "Package2", requiresAuth: true, requiredRole: "seller" },
|
|
440
|
+
[DASHBOARD_QUICK_ACTION_ID.SELLER_PRIZE_DRAWS]: { id: DASHBOARD_QUICK_ACTION_ID.SELLER_PRIZE_DRAWS, label: "Prize Draws", variant: "outline", iconName: "Gift", requiresAuth: true, requiredRole: "seller" },
|
|
441
|
+
[DASHBOARD_QUICK_ACTION_ID.SELLER_OFFERS]: { id: DASHBOARD_QUICK_ACTION_ID.SELLER_OFFERS, label: "Offers", variant: "outline", iconName: "Handshake", requiresAuth: true, requiredRole: "seller" },
|
|
442
|
+
[DASHBOARD_QUICK_ACTION_ID.SELLER_GROUPED_LISTINGS]: { id: DASHBOARD_QUICK_ACTION_ID.SELLER_GROUPED_LISTINGS, label: "Grouped Listings", variant: "outline", iconName: "Layers", requiresAuth: true, requiredRole: "seller" },
|
|
443
|
+
[DASHBOARD_QUICK_ACTION_ID.SELLER_CATEGORIES]: { id: DASHBOARD_QUICK_ACTION_ID.SELLER_CATEGORIES, label: "Categories", variant: "outline", iconName: "FolderTree", requiresAuth: true, requiredRole: "seller" },
|
|
444
|
+
[DASHBOARD_QUICK_ACTION_ID.SELLER_PRINT_CENTER]: { id: DASHBOARD_QUICK_ACTION_ID.SELLER_PRINT_CENTER, label: "Print Center", variant: "outline", iconName: "Printer", requiresAuth: true, requiredRole: "seller" },
|
|
423
445
|
[DASHBOARD_QUICK_ACTION_ID.USER_VIEW_ORDERS]: { id: DASHBOARD_QUICK_ACTION_ID.USER_VIEW_ORDERS, label: "My Orders", variant: "outline", iconName: "Package", requiresAuth: true, requiredRole: "user" },
|
|
424
446
|
[DASHBOARD_QUICK_ACTION_ID.USER_VIEW_WISHLIST]: { id: DASHBOARD_QUICK_ACTION_ID.USER_VIEW_WISHLIST, label: "Wishlist", variant: "outline", iconName: "Heart", requiresAuth: true, requiredRole: "user" },
|
|
425
447
|
[DASHBOARD_QUICK_ACTION_ID.USER_EDIT_PROFILE]: { id: DASHBOARD_QUICK_ACTION_ID.USER_EDIT_PROFILE, label: "Edit Profile", variant: "outline", iconName: "User", requiresAuth: true, requiredRole: "user" },
|
|
@@ -450,6 +472,11 @@ export const DASHBOARD_QUICK_ACTIONS = {
|
|
|
450
472
|
DASHBOARD_QUICK_ACTION_ID.ADMIN_SECTIONS,
|
|
451
473
|
DASHBOARD_QUICK_ACTION_ID.ADMIN_SUPPORT,
|
|
452
474
|
DASHBOARD_QUICK_ACTION_ID.ADMIN_MODERATION,
|
|
475
|
+
DASHBOARD_QUICK_ACTION_ID.ADMIN_BLOG,
|
|
476
|
+
DASHBOARD_QUICK_ACTION_ID.ADMIN_SCAMMERS,
|
|
477
|
+
DASHBOARD_QUICK_ACTION_ID.ADMIN_BUNDLES,
|
|
478
|
+
DASHBOARD_QUICK_ACTION_ID.ADMIN_PRIZE_DRAWS,
|
|
479
|
+
DASHBOARD_QUICK_ACTION_ID.ADMIN_TESTER_CHECKLIST,
|
|
453
480
|
DASHBOARD_QUICK_ACTION_ID.ADMIN_SETTINGS,
|
|
454
481
|
],
|
|
455
482
|
seller: [
|
|
@@ -459,8 +486,14 @@ export const DASHBOARD_QUICK_ACTIONS = {
|
|
|
459
486
|
DASHBOARD_QUICK_ACTION_ID.SELLER_ANALYTICS,
|
|
460
487
|
DASHBOARD_QUICK_ACTION_ID.SELLER_ADD_COUPON,
|
|
461
488
|
DASHBOARD_QUICK_ACTION_ID.SELLER_AUCTIONS,
|
|
489
|
+
DASHBOARD_QUICK_ACTION_ID.SELLER_BUNDLES,
|
|
490
|
+
DASHBOARD_QUICK_ACTION_ID.SELLER_PRIZE_DRAWS,
|
|
491
|
+
DASHBOARD_QUICK_ACTION_ID.SELLER_OFFERS,
|
|
492
|
+
DASHBOARD_QUICK_ACTION_ID.SELLER_GROUPED_LISTINGS,
|
|
493
|
+
DASHBOARD_QUICK_ACTION_ID.SELLER_CATEGORIES,
|
|
462
494
|
DASHBOARD_QUICK_ACTION_ID.SELLER_PAYOUT_REQ,
|
|
463
495
|
DASHBOARD_QUICK_ACTION_ID.SELLER_SHIPPING,
|
|
496
|
+
DASHBOARD_QUICK_ACTION_ID.SELLER_PRINT_CENTER,
|
|
464
497
|
DASHBOARD_QUICK_ACTION_ID.SELLER_MESSAGES,
|
|
465
498
|
DASHBOARD_QUICK_ACTION_ID.SELLER_WHATSAPP,
|
|
466
499
|
DASHBOARD_QUICK_ACTION_ID.SELLER_REVIEWS,
|
|
@@ -93,9 +93,9 @@ export function ReviewDetailShell({ review, storeHref }) {
|
|
|
93
93
|
? String(ROUTES.PUBLIC.PROFILE(reviewerProfileId))
|
|
94
94
|
: null;
|
|
95
95
|
const currentImage = lightboxIdx !== null ? images[lightboxIdx] : null;
|
|
96
|
-
return (_jsxs(_Fragment, { children: [_jsx(Div, { surface: "default", className: "border-b border-neutral-200 pt-[2.5rem]", padding: "b-xl", children: _jsxs(Div, { className: "mx-auto max-w-3xl", padding: "x-md", children: [_jsxs(Row, { gap: "sm", className: "mb-4", children: [_jsx(StarRating, { value: review.rating, size: "lg", readOnly: true }), _jsxs(Span, { color: "inverse", weight: "bold", className: "text-[var(--appkit-color-text)] dark:", size: "2xl", children: [review.rating, ".0"] }), review.verified && (_jsx(Span, { layout: "inline-flex", gap: "xs", color: "success", surface: "success-surface", size: "xs", weight: "semibold", rounded: "full", padding: "pill-md", children: "\u2713 Verified Purchase" })), review.featured && (_jsx(Span, { size: "xs", weight: "semibold", className: CLS_RATING_PILL, children: "\u2605 Featured" }))] }), review.title && (_jsx(Heading, { color: "inverse", level: 1, className: "text-[var(--appkit-color-text)] dark: mb-4 leading-snug", size: "2xl", weight: "bold", children: review.title })), _jsxs(Row, { gap: "sm", children: [review.userAvatar ? (_jsx(Div, { className: "relative h-11 w-11 flex-shrink-0 ring-2 ring-white ring-[var(--appkit-color-border-subtle)]", rounded: "full", overflow: "hidden", children: _jsx(MediaImage, { src: review.userAvatar, alt: displayName, size: "avatar" }) })) : (_jsx(Row, { textWeight: "bold", textSize: "base", centered: true, className: "h-11 w-11 flex-shrink-0 bg-primary/10 text-primary ring-2 ring-white ring-[var(--appkit-color-border-subtle)]", rounded: "full", children: initials })), _jsxs(Div, { className: "min-w-0", children: [reviewerHref ? (_jsx(Link, { href: reviewerHref, className: "text-[length:var(--appkit-text-sm)] font-semibold text-[var(--appkit-color-text)] dark:text-white hover:text-primary transition-colors", children: displayName })) : (_jsx(Span, { color: "inverse", size: "sm", weight: "semibold", className: "text-[var(--appkit-color-text)] dark:", children: review.isAnonymous ? "Anonymous" : displayName })), date && (_jsx(Text, { size: "xs", color: "muted", className: "mt-0.5", children: date }))] })] })] }) }), _jsxs(Stack, { gap: "xl", className: "mx-auto max-w-3xl", paddingY: "y-xl", paddingX: "x-md", children: [review.comment && (_jsx(Section, { children: _jsx(RichText, { html: normalizeRichTextHtml(review.comment), proseClass: "prose prose-neutral dark:prose-invert max-w-none prose-p:leading-relaxed prose-headings:font-semibold prose-img:rounded-lg prose-a:text-primary", className: "text-neutral-700 text-[var(--appkit-color-text-muted)]" }) })), images.length > 0 && (_jsxs(Section, { children: [_jsxs(Heading, { level: 2, className: "tracking-wide mb-3", color: "faint", size: "sm", weight: "semibold", transform: "uppercase", children: ["Photos (", images.length, ")"] }), _jsx(Grid, { gap: "xs", className: "grid-cols-3 sm:grid-cols-4", children: images.map((img, i) => (_jsxs(Button, { variant: "ghost", type: "button", onClick: () => setLightboxIdx(i), "aria-label": `View photo ${i + 1}`, rounded: "xl", paddingX: "none", paddingY: "none", className: "group relative aspect-square overflow-hidden border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface-elevated)] focus-visible:ring-2 focus-visible:ring-primary", children: [_jsx(MediaImage, { src: img
|
|
96
|
+
return (_jsxs(_Fragment, { children: [_jsx(Div, { surface: "default", className: "border-b border-neutral-200 pt-[2.5rem]", padding: "b-xl", children: _jsxs(Div, { className: "mx-auto max-w-3xl", padding: "x-md", children: [_jsxs(Row, { gap: "sm", className: "mb-4", children: [_jsx(StarRating, { value: review.rating, size: "lg", readOnly: true }), _jsxs(Span, { color: "inverse", weight: "bold", className: "text-[var(--appkit-color-text)] dark:", size: "2xl", children: [review.rating, ".0"] }), review.verified && (_jsx(Span, { layout: "inline-flex", gap: "xs", color: "success", surface: "success-surface", size: "xs", weight: "semibold", rounded: "full", padding: "pill-md", children: "\u2713 Verified Purchase" })), review.featured && (_jsx(Span, { size: "xs", weight: "semibold", className: CLS_RATING_PILL, children: "\u2605 Featured" }))] }), review.title && (_jsx(Heading, { color: "inverse", level: 1, className: "text-[var(--appkit-color-text)] dark: mb-4 leading-snug", size: "2xl", weight: "bold", children: review.title })), _jsxs(Row, { gap: "sm", children: [review.userAvatar ? (_jsx(Div, { className: "relative h-11 w-11 flex-shrink-0 ring-2 ring-white ring-[var(--appkit-color-border-subtle)]", rounded: "full", overflow: "hidden", children: _jsx(MediaImage, { src: review.userAvatar, alt: displayName, size: "avatar" }) })) : (_jsx(Row, { textWeight: "bold", textSize: "base", centered: true, className: "h-11 w-11 flex-shrink-0 bg-primary/10 text-primary ring-2 ring-white ring-[var(--appkit-color-border-subtle)]", rounded: "full", children: initials })), _jsxs(Div, { className: "min-w-0", children: [reviewerHref ? (_jsx(Link, { href: reviewerHref, className: "text-[length:var(--appkit-text-sm)] font-semibold text-[var(--appkit-color-text)] dark:text-white hover:text-primary transition-colors", children: displayName })) : (_jsx(Span, { color: "inverse", size: "sm", weight: "semibold", className: "text-[var(--appkit-color-text)] dark:", children: review.isAnonymous ? "Anonymous" : displayName })), date && (_jsx(Text, { size: "xs", color: "muted", className: "mt-0.5", children: date }))] })] })] }) }), _jsxs(Stack, { gap: "xl", className: "mx-auto max-w-3xl", paddingY: "y-xl", paddingX: "x-md", children: [review.comment && (_jsx(Section, { children: _jsx(RichText, { html: normalizeRichTextHtml(review.comment), proseClass: "prose prose-neutral dark:prose-invert max-w-none prose-p:leading-relaxed prose-headings:font-semibold prose-img:rounded-lg prose-a:text-primary", className: "text-neutral-700 text-[var(--appkit-color-text-muted)]" }) })), images.length > 0 && (_jsxs(Section, { children: [_jsxs(Heading, { level: 2, className: "tracking-wide mb-3", color: "faint", size: "sm", weight: "semibold", transform: "uppercase", children: ["Photos (", images.length, ")"] }), _jsx(Grid, { gap: "xs", className: "grid-cols-3 sm:grid-cols-4", children: images.map((img, i) => (_jsxs(Button, { variant: "ghost", type: "button", onClick: () => setLightboxIdx(i), "aria-label": `View photo ${i + 1}`, rounded: "xl", paddingX: "none", paddingY: "none", className: "group relative aspect-square overflow-hidden border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface-elevated)] focus-visible:ring-2 focus-visible:ring-primary", children: [_jsx(MediaImage, { src: img, alt: `Review image ${i + 1}`, size: "card", className: "transition-transform duration-300 group-hover:scale-105" }), _jsx(Row, { centered: true, className: "absolute inset-0 pointer-events-none opacity-0 group-hover:opacity-100 transition-opacity bg-[rgba(0,0,0,0.3)]", children: _jsx(Span, { color: "inverse", size: "xl", children: "\uD83D\uDD0D" }) })] }, i))) })] })), review.video && (_jsxs(Section, { children: [_jsx(Heading, { level: 2, className: "tracking-wide mb-3", color: "faint", size: "sm", weight: "semibold", transform: "uppercase", children: "Video" }), _jsx(Div, { className: `${__O.hidden} border border-[var(--appkit-color-border)] bg-[rgb(0,0,0)] aspect-video`, rounded: "xl", children: _jsx("video", { src: review.video.url, poster: review.video.thumbnailUrl, controls: true, className: "h-full w-full", preload: "metadata" }) })] })), _jsxs(Section, { className: "flex items-[center] gap-[1rem] border-t border-neutral-100", padding: "y-md", children: [_jsx(Div, { textSize: "sm", className: "text-[var(--appkit-color-text-muted)]", children: helpfulCount > 0 && (_jsxs(Span, { children: [_jsx(Span, { color: "inverse", weight: "bold", className: "text-[var(--appkit-color-text)] dark:", children: helpfulCount }), " ", helpfulCount === 1 ? "person" : "people", " found this helpful"] })) }), _jsxs(Button, { variant: "outline", type: "button", onClick: handleVote, disabled: voted || voting, gap: "sm", rounded: "lg", paddingX: "md", paddingY: "sm", textSize: "sm", weight: "medium", className: `ml-auto transition-colors ${voted
|
|
97
97
|
? CLS_HELPFUL_ACTIVE
|
|
98
|
-
: "border-[var(--appkit-color-border)] text-[var(--appkit-color-text-muted)] hover:border-primary hover:text-primary dark:hover:border-primary dark:hover:text-primary disabled:opacity-50"}`, children: [_jsx(Span, { "aria-hidden": "true", children: voted ? "✓" : "👍" }), voted ? "Marked helpful" : voting ? "Saving…" : "Helpful?"] })] }), _jsxs(Section, { className: "grid gap-[0.75rem] sm:grid-cols-3", children: [productHref && (_jsxs(Link, { href: productHref, className: CLS_RELATED_LINK, children: [_jsx(Span, { className: CLS_ICON_ORANGE, children: "\uD83D\uDCE6" }), _jsxs(Div, { className: "min-w-0", children: [_jsx(Text, { className: CLS_RELATED_LABEL, children: "Product" }), _jsx(Text, { className: CLS_RELATED_TITLE, children: review.productTitle ?? "View Product" })] })] })), sellerHref && (_jsxs(Link, { href: sellerHref, className: CLS_RELATED_LINK, children: [_jsx(Span, { className: CLS_ICON_BLUE, children: "\uD83C\uDFEA" }), _jsxs(Div, { className: "min-w-0", children: [_jsx(Text, { className: CLS_RELATED_LABEL, children: "Seller" }), _jsx(Text, { className: CLS_RELATED_TITLE, children: "View Seller" })] })] })), reviewerHref ? (_jsxs(Link, { href: reviewerHref, className: CLS_RELATED_LINK, children: [_jsx(Span, { className: CLS_ICON_PURPLE, children: "\uD83D\uDC64" }), _jsxs(Div, { className: "min-w-0", children: [_jsx(Text, { className: CLS_RELATED_LABEL, children: "Reviewer" }), _jsx(Text, { className: CLS_RELATED_TITLE, children: displayName })] })] })) : (_jsxs(Row, { surface: "default", gap: "sm", className: `border border-neutral-200 dark:border-[var(--appkit-color-border)] ${__P.p4}`, rounded: "xl", children: [_jsx(Span, { size: "xl", className: CLS_ICON_PURPLE_BARE, children: "\uD83D\uDC64" }), _jsxs(Div, { className: "min-w-0", children: [_jsx(Span, { size: "xs", className: "block mb-0.5", color: "faint", children: "Reviewer" }), _jsx(Span, { color: "inverse", size: "sm", weight: "medium", className: "block text-[var(--appkit-color-text)] dark: truncate", children: "Anonymous" })] })] }))] })] }), lightboxIdx !== null && currentImage && (_jsxs(Row, { centered: true, className: "fixed inset-0 z-50 bg-[rgba(0,0,0,0.95)]", onClick: closeLightbox, role: "dialog", "aria-modal": "true", "aria-label": "Image lightbox", children: [_jsx(IconButton, { type: "button", onClick: closeLightbox, "aria-label": "Close lightbox", variant: "scrim", rounded: "full", className: "absolute top-4 right-4 z-10 h-10 w-10", children: _jsx(Span, { size: "xl", children: "\u00D7" }) }), _jsxs(Div, { textSize: "sm", className: "absolute top-4 left-1/2 -translate-x-1/2 text-white/70", children: [lightboxIdx + 1, " / ", images.length] }), images.length > 1 && (_jsx(IconButton, { type: "button", onClick: (e) => { e.stopPropagation(); prevImage(); }, "aria-label": "Previous image", variant: "scrim", rounded: "full", className: "absolute left-4 top-1/2 -translate-y-1/2 z-10 h-12 w-12", children: _jsx(Span, { size: "2xl", children: "\u2039" }) })), _jsx(Row, { centered: true, className: "relative h-[85vh] w-[85vw]", onClick: (e) => e.stopPropagation(), children: _jsx(MediaImage, { src: currentImage
|
|
98
|
+
: "border-[var(--appkit-color-border)] text-[var(--appkit-color-text-muted)] hover:border-primary hover:text-primary dark:hover:border-primary dark:hover:text-primary disabled:opacity-50"}`, children: [_jsx(Span, { "aria-hidden": "true", children: voted ? "✓" : "👍" }), voted ? "Marked helpful" : voting ? "Saving…" : "Helpful?"] })] }), _jsxs(Section, { className: "grid gap-[0.75rem] sm:grid-cols-3", children: [productHref && (_jsxs(Link, { href: productHref, className: CLS_RELATED_LINK, children: [_jsx(Span, { className: CLS_ICON_ORANGE, children: "\uD83D\uDCE6" }), _jsxs(Div, { className: "min-w-0", children: [_jsx(Text, { className: CLS_RELATED_LABEL, children: "Product" }), _jsx(Text, { className: CLS_RELATED_TITLE, children: review.productTitle ?? "View Product" })] })] })), sellerHref && (_jsxs(Link, { href: sellerHref, className: CLS_RELATED_LINK, children: [_jsx(Span, { className: CLS_ICON_BLUE, children: "\uD83C\uDFEA" }), _jsxs(Div, { className: "min-w-0", children: [_jsx(Text, { className: CLS_RELATED_LABEL, children: "Seller" }), _jsx(Text, { className: CLS_RELATED_TITLE, children: "View Seller" })] })] })), reviewerHref ? (_jsxs(Link, { href: reviewerHref, className: CLS_RELATED_LINK, children: [_jsx(Span, { className: CLS_ICON_PURPLE, children: "\uD83D\uDC64" }), _jsxs(Div, { className: "min-w-0", children: [_jsx(Text, { className: CLS_RELATED_LABEL, children: "Reviewer" }), _jsx(Text, { className: CLS_RELATED_TITLE, children: displayName })] })] })) : (_jsxs(Row, { surface: "default", gap: "sm", className: `border border-neutral-200 dark:border-[var(--appkit-color-border)] ${__P.p4}`, rounded: "xl", children: [_jsx(Span, { size: "xl", className: CLS_ICON_PURPLE_BARE, children: "\uD83D\uDC64" }), _jsxs(Div, { className: "min-w-0", children: [_jsx(Span, { size: "xs", className: "block mb-0.5", color: "faint", children: "Reviewer" }), _jsx(Span, { color: "inverse", size: "sm", weight: "medium", className: "block text-[var(--appkit-color-text)] dark: truncate", children: "Anonymous" })] })] }))] })] }), lightboxIdx !== null && currentImage && (_jsxs(Row, { centered: true, className: "fixed inset-0 z-50 bg-[rgba(0,0,0,0.95)]", onClick: closeLightbox, role: "dialog", "aria-modal": "true", "aria-label": "Image lightbox", children: [_jsx(IconButton, { type: "button", onClick: closeLightbox, "aria-label": "Close lightbox", variant: "scrim", rounded: "full", className: "absolute top-4 right-4 z-10 h-10 w-10", children: _jsx(Span, { size: "xl", children: "\u00D7" }) }), _jsxs(Div, { textSize: "sm", className: "absolute top-4 left-1/2 -translate-x-1/2 text-white/70", children: [lightboxIdx + 1, " / ", images.length] }), images.length > 1 && (_jsx(IconButton, { type: "button", onClick: (e) => { e.stopPropagation(); prevImage(); }, "aria-label": "Previous image", variant: "scrim", rounded: "full", className: "absolute left-4 top-1/2 -translate-y-1/2 z-10 h-12 w-12", children: _jsx(Span, { size: "2xl", children: "\u2039" }) })), _jsx(Row, { centered: true, className: "relative h-[85vh] w-[85vw]", onClick: (e) => e.stopPropagation(), children: _jsx(MediaImage, { src: currentImage, alt: `Review photo ${lightboxIdx + 1}`, size: "hero", objectFit: "contain", rounded: "lg", shadow: "2xl" }) }), images.length > 1 && (_jsx(IconButton, { type: "button", onClick: (e) => { e.stopPropagation(); nextImage(); }, "aria-label": "Next image", variant: "scrim", rounded: "full", className: "absolute right-4 top-1/2 -translate-y-1/2 z-10 h-12 w-12", children: _jsx(Span, { size: "2xl", children: "\u203A" }) })), images.length > 1 && (_jsx(Row, { justify: "center", gap: "xs", className: "absolute bottom-4 left-0 right-0", padding: "x-md", children: images.map((img, i) => (_jsx(Button, { variant: "ghost", type: "button", onClick: (e) => { e.stopPropagation(); setLightboxIdx(i); }, "aria-label": `Go to image ${i + 1}`, rounded: "md", paddingX: "none", paddingY: "none", className: `h-12 w-12 flex-shrink-0 overflow-hidden border-2 transition-all ${i === lightboxIdx
|
|
99
99
|
? "border-white scale-110"
|
|
100
|
-
: "border-transparent opacity-60 hover:opacity-100"}`, children: _jsx(MediaImage, { src: img
|
|
100
|
+
: "border-transparent opacity-60 hover:opacity-100"}`, children: _jsx(MediaImage, { src: img, alt: `Thumbnail ${i + 1}`, size: "thumbnail" }) }, i))) }))] }))] }));
|
|
101
101
|
}
|
|
@@ -24,5 +24,5 @@ export function ViewReviewModal({ review, isOpen, onClose, }) {
|
|
|
24
24
|
day: "numeric",
|
|
25
25
|
})
|
|
26
26
|
: "";
|
|
27
|
-
return (_jsx(Modal, { isOpen: isOpen, onClose: onClose, title: "Review Details", children: _jsxs(Stack, { gap: "md", children: [_jsxs(Row, { gap: "3", children: [review.userAvatar ? (_jsx(Div, { className: "relative h-10 w-10 flex-shrink-0", rounded: "full", overflow: "hidden", children: _jsx(MediaImage, { src: review.userAvatar, alt: displayName, size: "avatar" }) })) : (_jsx(Row, { textWeight: "medium", textSize: "sm", className: "h-10 w-10 flex-shrink-0 bg-neutral-200 text-[var(--appkit-color-text-muted)]", align: "center", justify: "center", rounded: "full", children: displayName.charAt(0).toUpperCase() })), _jsxs(Div, { children: [_jsxs(Row, { gap: "sm", children: [_jsx(Text, { color: "inverse", className: "text-[var(--appkit-color-text)] dark:", weight: "medium", children: displayName }), review.verified && (_jsx(Text, { className: THEMED_TEXT_SUCCESS, size: "xs", children: "\u2713 Verified purchase" }))] }), _jsxs(Row, { className: "mt-0.5", gap: "sm", children: [_jsx(StarRating, { value: review.rating, size: "sm", readOnly: true }), date && (_jsx(Text, { size: "xs", color: "faint", children: date }))] })] })] }), review.title && (_jsx(Heading, { color: "inverse", level: 4, className: "text-[var(--appkit-color-text)] dark:", weight: "semibold", children: review.title })), review.comment && (_jsx(RichText, { html: normalizeRichTextHtml(review.comment), proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "text-[length:var(--appkit-text-sm)] leading-relaxed text-[var(--appkit-color-text-muted)] text-[var(--appkit-color-text-muted)]" })), review.images && review.images.length > 0 && (_jsx(Row, { wrap: true, gap: "sm", children: review.images.map((img, i) => (_jsx(Button, { variant: "ghost", type: "button", "aria-label": `View image ${i + 1}`, onClick: () => setLightboxIdx(i), rounded: "lg", paddingX: "none", paddingY: "none", className: `h-20 w-20 overflow-hidden transition hover:opacity-80 flex-shrink-0 ${lightboxIdx === i ? "ring-2 ring-primary-500" : "border-neutral-100 border-[var(--appkit-color-border)]"}`, children: _jsx(MediaImage, { src: img
|
|
27
|
+
return (_jsx(Modal, { isOpen: isOpen, onClose: onClose, title: "Review Details", children: _jsxs(Stack, { gap: "md", children: [_jsxs(Row, { gap: "3", children: [review.userAvatar ? (_jsx(Div, { className: "relative h-10 w-10 flex-shrink-0", rounded: "full", overflow: "hidden", children: _jsx(MediaImage, { src: review.userAvatar, alt: displayName, size: "avatar" }) })) : (_jsx(Row, { textWeight: "medium", textSize: "sm", className: "h-10 w-10 flex-shrink-0 bg-neutral-200 text-[var(--appkit-color-text-muted)]", align: "center", justify: "center", rounded: "full", children: displayName.charAt(0).toUpperCase() })), _jsxs(Div, { children: [_jsxs(Row, { gap: "sm", children: [_jsx(Text, { color: "inverse", className: "text-[var(--appkit-color-text)] dark:", weight: "medium", children: displayName }), review.verified && (_jsx(Text, { className: THEMED_TEXT_SUCCESS, size: "xs", children: "\u2713 Verified purchase" }))] }), _jsxs(Row, { className: "mt-0.5", gap: "sm", children: [_jsx(StarRating, { value: review.rating, size: "sm", readOnly: true }), date && (_jsx(Text, { size: "xs", color: "faint", children: date }))] })] })] }), review.title && (_jsx(Heading, { color: "inverse", level: 4, className: "text-[var(--appkit-color-text)] dark:", weight: "semibold", children: review.title })), review.comment && (_jsx(RichText, { html: normalizeRichTextHtml(review.comment), proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "text-[length:var(--appkit-text-sm)] leading-relaxed text-[var(--appkit-color-text-muted)] text-[var(--appkit-color-text-muted)]" })), review.images && review.images.length > 0 && (_jsx(Row, { wrap: true, gap: "sm", children: review.images.map((img, i) => (_jsx(Button, { variant: "ghost", type: "button", "aria-label": `View image ${i + 1}`, onClick: () => setLightboxIdx(i), rounded: "lg", paddingX: "none", paddingY: "none", className: `h-20 w-20 overflow-hidden transition hover:opacity-80 flex-shrink-0 ${lightboxIdx === i ? "ring-2 ring-primary-500" : "border-neutral-100 border-[var(--appkit-color-border)]"}`, children: _jsx(MediaImage, { src: img, alt: `Review thumbnail ${i + 1}`, size: "thumbnail" }) }, i))) })), (review.helpfulCount ?? 0) > 0 && (_jsxs(Text, { size: "xs", color: "faint", children: [review.helpfulCount, " people found this helpful"] })), _jsx(Row, { justify: "end", padding: "t-xs", children: _jsx(Button, { variant: "ghost", onClick: onClose, children: "Close" }) })] }) }));
|
|
28
28
|
}
|
|
@@ -29,7 +29,7 @@ export function ReviewCard({ review, context = "general", className = "" }) {
|
|
|
29
29
|
const showProductLink = context !== "listing" && !!productHref;
|
|
30
30
|
const showProfileLink = !!profileHref;
|
|
31
31
|
const hasFooter = showStoreLink || showProductLink || showProfileLink;
|
|
32
|
-
return (_jsxs(Stack, { padding: "5", shadow: "hover-md", className: `group h-full border border-neutral-200 transition ${className}`, rounded: "xl", surface: "default", children: [_jsxs(Link, { href: reviewHref, className: "flex flex-col flex-1 min-h-0", children: [_jsxs(Row, { align: "start", gap: "3", children: [review.userAvatar ? (_jsx(Div, { className: "relative h-9 w-9 flex-shrink-0", rounded: "full", overflow: "hidden", children: _jsx(MediaImage, { src: review.userAvatar, alt: displayName, size: "avatar" }) })) : (_jsx(Row, { textWeight: "medium", textSize: "sm", className: "h-9 w-9 flex-shrink-0 bg-neutral-200 text-[var(--appkit-color-text-muted)]", align: "center", justify: "center", rounded: "full", children: initials })), _jsxs(Div, { className: "flex-1 min-w-0", children: [_jsxs(Row, { wrap: true, gap: "sm", children: [_jsx(Span, { color: "inverse", weight: "medium", className: "text-[var(--appkit-color-text)] dark:", children: displayName }), review.verified && (_jsx(Span, { color: "success", surface: "success-surface", size: "xs", weight: "medium", rounded: "full", padding: "pill-xs", children: "Verified" })), date && (_jsx(Span, { size: "xs", color: "muted", children: date }))] }), _jsx(Div, { className: "mt-1", children: _jsx(StarRating, { value: review.rating, size: "sm", readOnly: true }) })] })] }), review.title && (_jsx(Heading, { color: "inverse", level: 4, className: "mt-3 text-[var(--appkit-color-text)] dark:", weight: "semibold", children: review.title })), review.comment && (_jsx(RichText, { html: normalizeRichTextHtml(review.comment), proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "mt-2 text-[length:var(--appkit-text-sm)] leading-relaxed text-[var(--appkit-color-text-muted)] text-[var(--appkit-color-text-muted)]" })), review.images && review.images.length > 0 && (_jsx(Row, { wrap: true, gap: "sm", className: "mt-3", children: review.images.map((img, i) => (_jsx(Div, { className: "relative h-16 w-16 flex-shrink-0 border border-neutral-100", rounded: "lg", overflow: "hidden", children: _jsx(MediaImage, { src: img
|
|
32
|
+
return (_jsxs(Stack, { padding: "5", shadow: "hover-md", className: `group h-full border border-neutral-200 transition ${className}`, rounded: "xl", surface: "default", children: [_jsxs(Link, { href: reviewHref, className: "flex flex-col flex-1 min-h-0", children: [_jsxs(Row, { align: "start", gap: "3", children: [review.userAvatar ? (_jsx(Div, { className: "relative h-9 w-9 flex-shrink-0", rounded: "full", overflow: "hidden", children: _jsx(MediaImage, { src: review.userAvatar, alt: displayName, size: "avatar" }) })) : (_jsx(Row, { textWeight: "medium", textSize: "sm", className: "h-9 w-9 flex-shrink-0 bg-neutral-200 text-[var(--appkit-color-text-muted)]", align: "center", justify: "center", rounded: "full", children: initials })), _jsxs(Div, { className: "flex-1 min-w-0", children: [_jsxs(Row, { wrap: true, gap: "sm", children: [_jsx(Span, { color: "inverse", weight: "medium", className: "text-[var(--appkit-color-text)] dark:", children: displayName }), review.verified && (_jsx(Span, { color: "success", surface: "success-surface", size: "xs", weight: "medium", rounded: "full", padding: "pill-xs", children: "Verified" })), date && (_jsx(Span, { size: "xs", color: "muted", children: date }))] }), _jsx(Div, { className: "mt-1", children: _jsx(StarRating, { value: review.rating, size: "sm", readOnly: true }) })] })] }), review.title && (_jsx(Heading, { color: "inverse", level: 4, className: "mt-3 text-[var(--appkit-color-text)] dark:", weight: "semibold", children: review.title })), review.comment && (_jsx(RichText, { html: normalizeRichTextHtml(review.comment), proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "mt-2 text-[length:var(--appkit-text-sm)] leading-relaxed text-[var(--appkit-color-text-muted)] text-[var(--appkit-color-text-muted)]" })), review.images && review.images.length > 0 && (_jsx(Row, { wrap: true, gap: "sm", className: "mt-3", children: review.images.map((img, i) => (_jsx(Div, { className: "relative h-16 w-16 flex-shrink-0 border border-neutral-100", rounded: "lg", overflow: "hidden", children: _jsx(MediaImage, { src: img, alt: `Review image ${i + 1}`, size: "thumbnail" }) }, i))) })), (review.helpfulCount ?? 0) > 0 && (_jsxs(Text, { className: "mt-3", color: "faint", size: "xs", children: [review.helpfulCount, " found this helpful"] }))] }), hasFooter && (_jsxs(Stack, { gap: "xs", className: "mt-3 border-t border-neutral-100", padding: "t-sm", children: [showStoreLink && (_jsxs(Link, { href: String(ROUTES.PUBLIC.STORE_DETAIL(review.storeSlug)), className: "flex items-center gap-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-xs)] font-medium text-primary hover:underline", children: [_jsx(Span, { "aria-hidden": "true", children: "\uD83C\uDFEA" }), _jsx(Span, { truncate: true, children: review.storeName })] })), showProductLink && (_jsxs(Link, { href: productHref, className: "flex items-center gap-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-xs)] font-medium text-[var(--appkit-color-text-muted)] text-[var(--appkit-color-text-muted)] hover:text-neutral-700 hover:text-[var(--appkit-color-text-muted)]", children: [_jsx(Span, { "aria-hidden": "true", children: "\uD83D\uDCE6" }), _jsx(Span, { truncate: true, children: review.productTitle ?? "View Product" }), _jsx(Span, { "aria-hidden": "true", className: "ml-auto text-primary group-hover:translate-x-0.5 transition-transform", children: "\u2192" })] })), showProfileLink && (_jsxs(Link, { href: profileHref, className: "flex items-center gap-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-xs)] font-medium text-[var(--appkit-color-text-muted)] text-[var(--appkit-color-text-muted)] hover:text-neutral-700 hover:text-[var(--appkit-color-text-muted)]", children: [_jsx(Span, { "aria-hidden": "true", children: "\uD83D\uDC64" }), _jsx(Span, { truncate: true, children: displayName })] }))] }))] }));
|
|
33
33
|
}
|
|
34
34
|
export function ReviewsList({ reviews, context = "general", isLoading, totalPages = 1, currentPage = 1, onPageChange, emptyLabel = "No reviews yet", }) {
|
|
35
35
|
if (isLoading) {
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import type { ListingType } from "../../products/types/index";
|
|
2
2
|
export type ReviewStatus = "pending" | "approved" | "rejected";
|
|
3
|
-
export interface ReviewImage {
|
|
4
|
-
url: string;
|
|
5
|
-
thumbnailUrl?: string;
|
|
6
|
-
}
|
|
7
3
|
export interface ReviewVideo {
|
|
8
4
|
url: string;
|
|
9
5
|
thumbnailUrl?: string;
|
|
@@ -27,7 +23,7 @@ export interface Review {
|
|
|
27
23
|
rating: 1 | 2 | 3 | 4 | 5;
|
|
28
24
|
title?: string;
|
|
29
25
|
comment?: string;
|
|
30
|
-
images?:
|
|
26
|
+
images?: string[];
|
|
31
27
|
video?: ReviewVideo;
|
|
32
28
|
status: ReviewStatus;
|
|
33
29
|
helpfulCount?: number;
|
|
@@ -70,6 +66,6 @@ export interface CreateReviewInput {
|
|
|
70
66
|
rating: number;
|
|
71
67
|
title?: string;
|
|
72
68
|
comment?: string;
|
|
73
|
-
images?:
|
|
69
|
+
images?: string[];
|
|
74
70
|
video?: ReviewVideo;
|
|
75
71
|
}
|
|
@@ -88,6 +88,8 @@ export function SellerArtView({ onCreateClick, onEditClick, onDelete, onBulkDele
|
|
|
88
88
|
getTotal: (response, mappedRows) => typeof response.meta?.total === "number" ? response.meta.total : mappedRows.length,
|
|
89
89
|
buildFilters: () => "listingType==art",
|
|
90
90
|
primaryAction: { label: "New Art Print", onClick: () => handleCreate() },
|
|
91
|
+
// Mirrors the "Edit" row action below so table rows and cards both navigate on click.
|
|
92
|
+
onRowClick: (row) => handleEdit(row.id),
|
|
91
93
|
// Rule #7: bulk-action array sourced from the SELLER_BULK_ACTIONS preset.
|
|
92
94
|
buildBulkActions: onBulkDelete
|
|
93
95
|
? (selection) => SELLER_BULK_ACTIONS.art.map((id) => ({
|
|
@@ -93,6 +93,8 @@ export function SellerClassifiedView({ onCreateClick, onEditClick, onDelete, onB
|
|
|
93
93
|
: mappedRows.length,
|
|
94
94
|
buildFilters: () => "listingType==classified",
|
|
95
95
|
primaryAction: { label: "New Classified", onClick: () => handleCreate() },
|
|
96
|
+
// Mirrors the "Edit" row action below so table rows and cards both navigate on click.
|
|
97
|
+
onRowClick: (row) => handleEdit(row.id),
|
|
96
98
|
// Rule #7: bulk-action array sourced from the SELLER_BULK_ACTIONS preset.
|
|
97
99
|
buildBulkActions: onBulkDelete
|
|
98
100
|
? (selection) => SELLER_BULK_ACTIONS.classified.map((id) => ({
|
|
@@ -95,6 +95,8 @@ export function SellerDigitalCodesView({ onCreateClick, onEditClick, onDelete, o
|
|
|
95
95
|
getTotal: (response, mappedRows) => typeof response.meta?.total === "number" ? response.meta.total : mappedRows.length,
|
|
96
96
|
buildFilters: () => "listingType==digital-code",
|
|
97
97
|
primaryAction: { label: "New Digital Code", onClick: () => handleCreate() },
|
|
98
|
+
// Mirrors the "Edit" row action below so table rows and cards both navigate on click.
|
|
99
|
+
onRowClick: (row) => handleEdit(row.id),
|
|
98
100
|
// Rule #7: bulk-action array sourced from the SELLER_BULK_ACTIONS preset.
|
|
99
101
|
buildBulkActions: onBulkDelete
|
|
100
102
|
? (selection) => SELLER_BULK_ACTIONS.digitalCodes.map((id) => ({
|
|
@@ -90,6 +90,8 @@ export function SellerGroupedListingsView({ onCreateClick, onEditClick, onDelete
|
|
|
90
90
|
}),
|
|
91
91
|
]
|
|
92
92
|
: undefined,
|
|
93
|
+
// Mirrors the "Edit" row action below so table rows and cards both navigate on click.
|
|
94
|
+
onRowClick: onEditClick ? (row) => onEditClick(row.id) : undefined,
|
|
93
95
|
renderRowActions: (row) => (_jsxs(Row, { gap: "xs", children: [_jsx(Button, { size: "sm", variant: "ghost", onClick: () => onEditClick?.(row.id), children: ACTIONS.STORE["edit-listing"].label }), _jsx(Button, { size: "sm", variant: "ghost", onClick: () => onDeleteClick?.(row.id), children: ACTIONS.STORE["delete-listing"].label })] })),
|
|
94
96
|
};
|
|
95
97
|
return _jsx(DataListingView, { config: config });
|
|
@@ -95,6 +95,8 @@ export function SellerLiveView({ onCreateClick, onEditClick, onDelete, onBulkDel
|
|
|
95
95
|
getTotal: (response, mappedRows) => typeof response.meta?.total === "number" ? response.meta.total : mappedRows.length,
|
|
96
96
|
buildFilters: () => "listingType==live",
|
|
97
97
|
primaryAction: { label: "New Live Item", onClick: () => handleCreate() },
|
|
98
|
+
// Mirrors the "Edit" row action below so table rows and cards both navigate on click.
|
|
99
|
+
onRowClick: (row) => handleEdit(row.id),
|
|
98
100
|
// Rule #7: bulk-action array sourced from the SELLER_BULK_ACTIONS preset.
|
|
99
101
|
buildBulkActions: onBulkDelete
|
|
100
102
|
? (selection) => SELLER_BULK_ACTIONS.live.map((id) => ({
|
|
@@ -124,6 +124,9 @@ export function SellerPayoutsView({ children, ...props }) {
|
|
|
124
124
|
selection.clearSelection();
|
|
125
125
|
},
|
|
126
126
|
})),
|
|
127
|
+
// Mirrors the "View" row action below so table rows and cards both open
|
|
128
|
+
// the payout detail drawer on click.
|
|
129
|
+
onRowClick: (row) => setSelectedPayout(rawByIdRef.current[row.id] ?? { id: row.id }),
|
|
127
130
|
renderRowActions: (row) => (_jsx(RowActionMenu, { actions: [
|
|
128
131
|
{
|
|
129
132
|
label: ACTIONS.STORE["view-payout"].label,
|
|
@@ -117,7 +117,7 @@ const PRODUCT_COLUMNS = [
|
|
|
117
117
|
];
|
|
118
118
|
export function SellerProductsView({ onDeleteProduct, onCreateClick, children, ...props }) {
|
|
119
119
|
const hasChildren = React.Children.count(children) > 0;
|
|
120
|
-
const { view, setView } = useDataViewMode("
|
|
120
|
+
const { view, setView } = useDataViewMode("list");
|
|
121
121
|
const dispatch = useActionDispatch();
|
|
122
122
|
const { showToast } = useToast();
|
|
123
123
|
const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
|
|
@@ -195,10 +195,28 @@ export function SellerProductsView({ onDeleteProduct, onCreateClick, children, .
|
|
|
195
195
|
? "prize-draw"
|
|
196
196
|
: "standard";
|
|
197
197
|
const priceRaw = typeof item.price === "number" ? item.price : 0;
|
|
198
|
+
// Auction listings lost their reserve/bid/end-date summary when this
|
|
199
|
+
// view was consolidated from the old dedicated SellerAuctionsView —
|
|
200
|
+
// restore it into `secondary` (the one line every row/card renders)
|
|
201
|
+
// instead of the generic condition string, which auction sellers
|
|
202
|
+
// care about far less than bid activity.
|
|
203
|
+
const auctionSecondary = kind === "auction"
|
|
204
|
+
? [
|
|
205
|
+
typeof item.reservePrice === "number"
|
|
206
|
+
? `Reserve ₹${item.reservePrice.toLocaleString("en-IN")}`
|
|
207
|
+
: null,
|
|
208
|
+
`${typeof item.bidCount === "number" ? item.bidCount : 0} bids`,
|
|
209
|
+
item.auctionEndDate
|
|
210
|
+
? `Ends ${new Date(item.auctionEndDate).toLocaleDateString("en-IN", { month: "short", day: "numeric" })}`
|
|
211
|
+
: null,
|
|
212
|
+
]
|
|
213
|
+
.filter(Boolean)
|
|
214
|
+
.join(" · ")
|
|
215
|
+
: "";
|
|
198
216
|
return {
|
|
199
217
|
id: toStringValue(item.id, `product-${index}`),
|
|
200
218
|
primary: toStringValue(item.title ?? item.name, "Untitled product"),
|
|
201
|
-
secondary: toStringValue(item.condition, ""),
|
|
219
|
+
secondary: auctionSecondary || toStringValue(item.condition, ""),
|
|
202
220
|
status: toStringValue(item.status, "draft"),
|
|
203
221
|
updatedAt: toRelativeDate(item.updatedAt ?? item.createdAt),
|
|
204
222
|
imageUrl: toStringValue(item.mainImage ?? item.images?.[0], undefined),
|
|
@@ -88,6 +88,8 @@ export function SellerStickersView({ onCreateClick, onEditClick, onDelete, onBul
|
|
|
88
88
|
getTotal: (response, mappedRows) => typeof response.meta?.total === "number" ? response.meta.total : mappedRows.length,
|
|
89
89
|
buildFilters: () => "listingType==stickers",
|
|
90
90
|
primaryAction: { label: "New Sticker Listing", onClick: () => handleCreate() },
|
|
91
|
+
// Mirrors the "Edit" row action below so table rows and cards both navigate on click.
|
|
92
|
+
onRowClick: (row) => handleEdit(row.id),
|
|
91
93
|
// Rule #7: bulk-action array sourced from the SELLER_BULK_ACTIONS preset.
|
|
92
94
|
buildBulkActions: onBulkDelete
|
|
93
95
|
? (selection) => SELLER_BULK_ACTIONS.stickers.map((id) => ({
|
|
@@ -27,8 +27,15 @@
|
|
|
27
27
|
* @tag consumers:seed/index.ts,seed-cli.mjs
|
|
28
28
|
* @tag sideEffects:none
|
|
29
29
|
*/
|
|
30
|
+
import { seedExtMedia } from "../../../seed/_helpers/media";
|
|
30
31
|
const NOW = new Date();
|
|
31
32
|
const daysAgo = (n) => new Date(NOW.getTime() - n * 86400000);
|
|
33
|
+
// Deterministic per-product thumbnail — see the identical helper's comment
|
|
34
|
+
// in appkit/src/seed/orders-seed-data.ts for why this doesn't need to match
|
|
35
|
+
// the real product's own seeded image.
|
|
36
|
+
function orderItemImage(productId) {
|
|
37
|
+
return seedExtMedia(`https://picsum.photos/seed/order-item-${productId}/300/300`);
|
|
38
|
+
}
|
|
32
39
|
const BUYER_ID = "user-tester-qa";
|
|
33
40
|
const BUYER_NAME = "QA Tester";
|
|
34
41
|
const BUYER_EMAIL = "tester@letitrip.in";
|
|
@@ -66,7 +73,7 @@ function standardOrder({ key, status, paymentStatus, extra }) {
|
|
|
66
73
|
...extra,
|
|
67
74
|
};
|
|
68
75
|
}
|
|
69
|
-
|
|
76
|
+
const _rawOrdersTesterSeedData = [
|
|
70
77
|
// ── One order per OrderStatus value (9) ──────────────────────────────────────
|
|
71
78
|
standardOrder({ key: "pending", status: "pending", paymentStatus: "pending" }),
|
|
72
79
|
standardOrder({ key: "confirmed", status: "confirmed", paymentStatus: "paid" }),
|
|
@@ -301,3 +308,14 @@ export const ordersTesterSeedData = [
|
|
|
301
308
|
updatedAt: daysAgo(1),
|
|
302
309
|
},
|
|
303
310
|
];
|
|
311
|
+
// Backfills image/imageUrls the same way appkit/src/seed/orders-seed-data.ts
|
|
312
|
+
// does, so tester-sandbox order fixtures also show a thumbnail on My Orders
|
|
313
|
+
// and order-detail like real post-2026-08-20 orders do.
|
|
314
|
+
export const ordersTesterSeedData = _rawOrdersTesterSeedData.map((order) => ({
|
|
315
|
+
...order,
|
|
316
|
+
imageUrls: order.imageUrls ?? (order.productId ? [orderItemImage(order.productId)] : undefined),
|
|
317
|
+
items: order.items?.map((item) => ({
|
|
318
|
+
...item,
|
|
319
|
+
image: item.image ?? orderItemImage(item.productId),
|
|
320
|
+
})),
|
|
321
|
+
}));
|
|
@@ -88,6 +88,12 @@ const rawTesterChecklistItems = [
|
|
|
88
88
|
{ key: "tester-hub-answer-saves", label: "Answering Yes/No and adding a comment on a test case saves without reloading" },
|
|
89
89
|
{ key: "admin-tester-access", label: "Signed-in admin accounts can open the Tester Hub and see the same checklist as testers", href: "/user/tester" },
|
|
90
90
|
{ key: "admin-testing-section", label: "Admin dashboard's Testing section shows both the Tester Checklist and Tester Feedback (results) links", href: "/admin/tester-feedback" },
|
|
91
|
+
{
|
|
92
|
+
key: "tester-flag-live-refresh",
|
|
93
|
+
label: "A tester whose isTester/canTestAdmin flag is granted by an admin gets Tester Hub access without needing to log out and back in — within a few minutes of the change, on a tab that's stayed open",
|
|
94
|
+
description: "Fixed 2026-08-20 — the client session only refreshed role/isTester/canTestAdmin/disabled/storeId on login or a full page reload, never on ordinary client-side navigation, so a flag flip by an admin was invisible until the user manually re-authenticated. Now piggybacked onto the existing 5-minute session-activity ping. To test: as admin, grant isTester to a second test account that's already logged in elsewhere with the app open; within ~5 minutes (no reload) it should gain access to /user/tester.",
|
|
95
|
+
href: "/user/tester",
|
|
96
|
+
},
|
|
91
97
|
],
|
|
92
98
|
},
|
|
93
99
|
]),
|
|
@@ -220,6 +226,12 @@ const rawTesterChecklistItems = [
|
|
|
220
226
|
label: "The My Account dashboard's \"Recent Orders\" widget rows are clickable and each shows a \"View Details\" button linking to the correct order — not a dead, non-interactive preview",
|
|
221
227
|
href: "/user",
|
|
222
228
|
},
|
|
229
|
+
{
|
|
230
|
+
key: "order-item-thumbnails-render",
|
|
231
|
+
label: "Line-item thumbnails actually render on both the My Orders list and an individual order's detail page — not a blank/missing image slot",
|
|
232
|
+
description: "A 2026-08-19 bug meant real checkout orders never carried a per-item image at all (the field didn't exist on the order document, so the detail page's image block was silently skipped rather than showing a broken-image icon). Place a fresh order through checkout and confirm its item thumbnail shows up both in the My Orders list and on the order-detail page.",
|
|
233
|
+
href: "/user/orders",
|
|
234
|
+
},
|
|
223
235
|
],
|
|
224
236
|
},
|
|
225
237
|
{
|
|
@@ -299,6 +311,12 @@ const rawTesterChecklistItems = [
|
|
|
299
311
|
description: "Verify against review-7 (the first review on \"Beyblade Original — Dranzer S\"), reachable via My Reviews or a store's Reviews tab.",
|
|
300
312
|
href: "/reviews",
|
|
301
313
|
},
|
|
314
|
+
{
|
|
315
|
+
key: "review-photos-render",
|
|
316
|
+
label: "A review's uploaded photos actually render — on the review-detail permalink page, the reviews list on a product/store page, and the admin \"View review\" modal — not a fallback icon",
|
|
317
|
+
description: "A 2026-08-19 type mismatch (review images stored as plain URL strings but read as {url,thumbnailUrl} objects) meant every review photo silently fell back to a placeholder icon everywhere. Leave a review with a photo, then confirm it renders on all three surfaces.",
|
|
318
|
+
href: "/reviews",
|
|
319
|
+
},
|
|
302
320
|
],
|
|
303
321
|
},
|
|
304
322
|
{
|
|
@@ -417,6 +435,12 @@ const rawTesterChecklistItems = [
|
|
|
417
435
|
description: "A 2026-08-19 index-shape bug broke the default storeId-scoped queries on these three seller dashboards. Confirm each loads populated on first visit and its toggle reveals the corresponding hidden fixture.",
|
|
418
436
|
href: "/store/products",
|
|
419
437
|
},
|
|
438
|
+
{
|
|
439
|
+
key: "auction-row-shows-bid-info",
|
|
440
|
+
label: "An auction's row/card in the seller Products listing (filtered to Auctions) shows its reserve price, bid count, and end date — not just condition/SKU like a standard product",
|
|
441
|
+
description: "Restored 2026-08-20 — this info was dropped when the dedicated Auctions dashboard was consolidated into the shared Products listing. Filter to \"Auctions\" on /store/products (or the admin equivalent) and confirm each row's second line reads something like \"Reserve ₹X · N bids · Ends <date>\".",
|
|
442
|
+
href: "/store/products",
|
|
443
|
+
},
|
|
420
444
|
],
|
|
421
445
|
},
|
|
422
446
|
{
|
|
@@ -618,6 +642,21 @@ const rawTesterChecklistItems = [
|
|
|
618
642
|
{ key: "collapsible-user", label: "User profile dashboard sections expand/collapse and remember their state on reload", href: "/user/profile" },
|
|
619
643
|
{ key: "mobile-table-cards", label: "Admin/seller listing tables show full-row cards by default on mobile, with a working switch back to table view" },
|
|
620
644
|
{ key: "view-mode-persist", label: "Switching the dashboard table/card view mode is remembered on your next visit (filters/search/sort are not)" },
|
|
645
|
+
{
|
|
646
|
+
key: "list-view-default",
|
|
647
|
+
label: "A dashboard listing you haven't set a view preference for opens in list (row-card) view by default, not table view",
|
|
648
|
+
description: "The default view mode across every admin/store listing page changed from table to list on 2026-08-20. If you've already picked a view before, your saved preference still wins — check on a listing you haven't visited before, or clear the preference in your profile.",
|
|
649
|
+
},
|
|
650
|
+
{
|
|
651
|
+
key: "row-table-click-consistency",
|
|
652
|
+
label: "For any given dashboard listing, table rows, grid cards, and list cards all agree on whether clicking navigates somewhere — a card never looks clickable (hover/press styling) while doing nothing, and the table view never lacks a click affordance that the card view has",
|
|
653
|
+
description: "Fixed 2026-08-20 — previously the default card renderer always looked clickable (cursor-pointer, hover state) even when the underlying listing had no real destination wired, while its table view correctly showed no affordance for the exact same config, reading as \"the table isn't clickable.\" Spot-check a few dashboard listings in both table and list/grid view.",
|
|
654
|
+
},
|
|
655
|
+
{
|
|
656
|
+
key: "list-card-row-actions",
|
|
657
|
+
label: "Row-level action buttons (the same ones in the table's overflow menu — edit, approve, delete, etc.) also appear directly on each list/grid card, not just in the table view",
|
|
658
|
+
href: "/admin/orders",
|
|
659
|
+
},
|
|
621
660
|
],
|
|
622
661
|
},
|
|
623
662
|
{
|
package/dist/index.d.ts
CHANGED
|
@@ -2621,7 +2621,6 @@ export type { ReviewCreateInput } from "./features/reviews/index";
|
|
|
2621
2621
|
export type { ReviewDocument } from "./features/reviews/index";
|
|
2622
2622
|
export type { ReviewFilterVariant } from "./features/reviews/index";
|
|
2623
2623
|
export type { ReviewFiltersProps } from "./features/reviews/index";
|
|
2624
|
-
export type { ReviewImage } from "./features/reviews/index";
|
|
2625
2624
|
export type { ReviewListParams } from "./features/reviews/index";
|
|
2626
2625
|
export type { ReviewListResponse } from "./features/reviews/index";
|
|
2627
2626
|
export type { ReviewModerationInput } from "./features/reviews/index";
|
|
@@ -173,7 +173,31 @@ export function SessionProvider({ children, initialUser, endpoints: endpointOver
|
|
|
173
173
|
void normalizeError(error);
|
|
174
174
|
logger.debug("Session activity update failed", { error });
|
|
175
175
|
}
|
|
176
|
-
|
|
176
|
+
// Piggyback a fresh RBAC-relevant profile re-fetch on the same 5-minute
|
|
177
|
+
// tick. onAuthStateChanged only fires on login/logout/full-page-reload —
|
|
178
|
+
// never on client-side soft navigation — so a role/isTester/canTestAdmin
|
|
179
|
+
// flag flipped by an admin while the user already has the app open in a
|
|
180
|
+
// tab stays invisible to them indefinitely until they hard-reload or
|
|
181
|
+
// re-login. Root-caused 2026-08-20: a tester granted canTestAdmin
|
|
182
|
+
// couldn't see the Tester Hub because their SessionContext was still
|
|
183
|
+
// holding the flags from whenever they first loaded the app.
|
|
184
|
+
try {
|
|
185
|
+
const fresh = await fetchUserProfileFromServer();
|
|
186
|
+
if (!fresh || fresh.uid !== user.uid)
|
|
187
|
+
return;
|
|
188
|
+
const rbacFieldsChanged = fresh.role !== user.role ||
|
|
189
|
+
fresh.isTester !== user.isTester ||
|
|
190
|
+
fresh.canTestAdmin !== user.canTestAdmin ||
|
|
191
|
+
fresh.disabled !== user.disabled ||
|
|
192
|
+
fresh.storeId !== user.storeId;
|
|
193
|
+
if (rbacFieldsChanged)
|
|
194
|
+
setUser(fresh);
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
void normalizeError(error);
|
|
198
|
+
logger.debug("Periodic profile refresh failed", { error });
|
|
199
|
+
}
|
|
200
|
+
}, [user, ep.sessionActivity, fetchUserProfileFromServer]);
|
|
177
201
|
useEffect(() => {
|
|
178
202
|
updateSessionActivityRef.current = updateSessionActivity;
|
|
179
203
|
}, [updateSessionActivity]);
|
|
@@ -12,8 +12,18 @@
|
|
|
12
12
|
* @tag consumers:seed/index.ts,seed/runner.ts
|
|
13
13
|
* @tag sideEffects:none
|
|
14
14
|
*/
|
|
15
|
+
import { seedExtMedia } from "./_helpers/media";
|
|
15
16
|
const NOW = new Date();
|
|
16
17
|
const daysAgo = (n) => new Date(NOW.getTime() - n * 86400000);
|
|
18
|
+
// Deterministic per-product thumbnail — picsum.photos returns a stable image
|
|
19
|
+
// for any seed string, so this doesn't need to match the real product seed's
|
|
20
|
+
// own image, just be present and consistent across reseeds. Backfills the
|
|
21
|
+
// `image`/`imageUrls` fields real checkout orders only started carrying
|
|
22
|
+
// 2026-08-20 (see OrderDocumentItem.image), so seeded order fixtures show a
|
|
23
|
+
// thumbnail on My Orders / order-detail like real orders do.
|
|
24
|
+
function orderItemImage(productId) {
|
|
25
|
+
return seedExtMedia(`https://picsum.photos/seed/order-item-${productId}/300/300`);
|
|
26
|
+
}
|
|
17
27
|
// Deterministic 6-char base36 suffix derived from a seed string — replaces the
|
|
18
28
|
// previous Math.random() suffix, which regenerated a fresh id on every module
|
|
19
29
|
// import (every `load`/`status`/`delete` CLI invocation), so re-running `load`
|
|
@@ -360,8 +370,22 @@ const cashOrderVerified = {
|
|
|
360
370
|
createdAt: daysAgo(4),
|
|
361
371
|
updatedAt: daysAgo(2),
|
|
362
372
|
};
|
|
373
|
+
// Backfills image/imageUrls (see orderItemImage() above) on every fixture
|
|
374
|
+
// regardless of which shape it uses (multi-item `items[]` vs the legacy
|
|
375
|
+
// single-item top-level fields), so no seeded order silently renders without
|
|
376
|
+
// a thumbnail the way real pre-2026-08-20 orders do.
|
|
377
|
+
function withOrderImages(order) {
|
|
378
|
+
return {
|
|
379
|
+
...order,
|
|
380
|
+
imageUrls: order.imageUrls ?? (order.productId ? [orderItemImage(order.productId)] : undefined),
|
|
381
|
+
items: order.items?.map((item) => ({
|
|
382
|
+
...item,
|
|
383
|
+
image: item.image ?? orderItemImage(item.productId),
|
|
384
|
+
})),
|
|
385
|
+
};
|
|
386
|
+
}
|
|
363
387
|
export const ordersSeedData = [
|
|
364
388
|
cashOrderPendingProof,
|
|
365
389
|
cashOrderVerified,
|
|
366
390
|
...[..._rawOrdersSeedData, ...expandedOrders].slice(0, 48),
|
|
367
|
-
];
|
|
391
|
+
].map(withOrderImages);
|