@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.
Files changed (49) hide show
  1. package/dist/_internal/server/features/checkout/actions.js +2 -0
  2. package/dist/_internal/server/features/orders/adapters.js +1 -0
  3. package/dist/features/account/hooks/useDataViewMode.js +1 -1
  4. package/dist/features/admin/components/AdminAddressesView.js +5 -0
  5. package/dist/features/admin/components/AdminArtView.js +4 -0
  6. package/dist/features/admin/components/AdminClassifiedView.js +4 -0
  7. package/dist/features/admin/components/AdminContactView.js +6 -0
  8. package/dist/features/admin/components/AdminDigitalCodesView.js +4 -0
  9. package/dist/features/admin/components/AdminGroupedListingsView.js +14 -8
  10. package/dist/features/admin/components/AdminLiveView.js +4 -0
  11. package/dist/features/admin/components/AdminOrdersView.js +6 -0
  12. package/dist/features/admin/components/AdminProductsView.js +22 -4
  13. package/dist/features/admin/components/AdminReturnRequestsView.js +5 -0
  14. package/dist/features/admin/components/AdminReviewsView.js +4 -3
  15. package/dist/features/admin/components/AdminScammersView.js +6 -0
  16. package/dist/features/admin/components/AdminStickersView.js +4 -0
  17. package/dist/features/admin/components/AdminSublistingCategoriesView.js +2 -0
  18. package/dist/features/admin/components/AdminSupportTicketsView.js +6 -0
  19. package/dist/features/admin/components/AdminTeamView.js +3 -0
  20. package/dist/features/admin/components/AdminViewCards.d.ts +3 -1
  21. package/dist/features/admin/components/AdminViewCards.js +7 -7
  22. package/dist/features/admin/components/DataListingView.d.ts +1 -1
  23. package/dist/features/admin/components/DataListingView.js +26 -2
  24. package/dist/features/admin/hooks/useAdminListing.js +1 -1
  25. package/dist/features/auth/actions/profile-actions.js +3 -3
  26. package/dist/features/orders/schemas/firestore.d.ts +2 -0
  27. package/dist/features/products/components/ProductDetailPageView.js +1 -3
  28. package/dist/features/products/constants/action-defs.d.ts +13 -2
  29. package/dist/features/products/constants/action-defs.js +33 -0
  30. package/dist/features/reviews/components/ReviewDetailShell.js +3 -3
  31. package/dist/features/reviews/components/ReviewModal.js +1 -1
  32. package/dist/features/reviews/components/ReviewsList.js +1 -1
  33. package/dist/features/reviews/types/index.d.ts +2 -6
  34. package/dist/features/seller/components/SellerArtView.js +2 -0
  35. package/dist/features/seller/components/SellerClassifiedView.js +2 -0
  36. package/dist/features/seller/components/SellerDigitalCodesView.js +2 -0
  37. package/dist/features/seller/components/SellerGroupedListingsView.js +2 -0
  38. package/dist/features/seller/components/SellerLiveView.js +2 -0
  39. package/dist/features/seller/components/SellerPayoutsView.js +3 -0
  40. package/dist/features/seller/components/SellerProductsView.js +20 -2
  41. package/dist/features/seller/components/SellerStickersView.js +2 -0
  42. package/dist/features/tester/seed-data/orders-tester-seed-data.js +19 -1
  43. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +39 -0
  44. package/dist/index.d.ts +0 -1
  45. package/dist/react/contexts/SessionContext.js +25 -1
  46. package/dist/seed/orders-seed-data.js +25 -1
  47. package/dist/styles.css +1 -1
  48. package/dist/tailwind-utilities.css +1 -1
  49. 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.thumbnailUrl ?? img.url, 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
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.url, 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
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.thumbnailUrl ?? img.url, alt: `Thumbnail ${i + 1}`, size: "thumbnail" }) }, i))) }))] }))] }));
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.thumbnailUrl ?? img.url, 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" }) })] }) }));
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.thumbnailUrl ?? img.url, 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 })] }))] }))] }));
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?: ReviewImage[];
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?: ReviewImage[];
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("table");
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
- export const ordersTesterSeedData = [
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
- }, [user, ep.sessionActivity]);
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);