@mohasinac/appkit 4.30.16 → 4.30.17

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 (97) hide show
  1. package/dist/_internal/shared/types/action-error-reporter.d.ts +40 -0
  2. package/dist/_internal/shared/types/action-error-reporter.js +27 -0
  3. package/dist/_internal/shared/types/action-result.d.ts +10 -0
  4. package/dist/_internal/shared/types/action-result.js +26 -0
  5. package/dist/client/api/surface-error.d.ts +6 -4
  6. package/dist/client/api/surface-error.js +19 -8
  7. package/dist/contracts/realtime-session.d.ts +27 -0
  8. package/dist/contracts/realtime-session.js +118 -0
  9. package/dist/core/unit-of-work.js +21 -13
  10. package/dist/errors/base-error.js +14 -1
  11. package/dist/errors/database-error.js +11 -1
  12. package/dist/errors/describe-cause.d.ts +5 -0
  13. package/dist/errors/describe-cause.js +76 -0
  14. package/dist/errors/error-display-map.d.ts +32 -1
  15. package/dist/errors/error-display-map.js +46 -0
  16. package/dist/errors/error-mapping.d.ts +47 -7
  17. package/dist/errors/error-mapping.js +46 -5
  18. package/dist/errors/index.d.ts +1 -0
  19. package/dist/errors/index.js +4 -0
  20. package/dist/features/about/components/PublicProfileView.js +4 -4
  21. package/dist/features/account/components/AddressBook.js +2 -2
  22. package/dist/features/account/components/AddressesIndexListing.js +2 -2
  23. package/dist/features/admin/components/AdminAnalyticsView.js +2 -2
  24. package/dist/features/admin/components/AdminCouponsView.js +2 -2
  25. package/dist/features/admin/components/AdminGuideHubView.js +2 -2
  26. package/dist/features/admin/components/AdminMediaView.js +2 -2
  27. package/dist/features/admin/components/AdminViewCards.js +2 -2
  28. package/dist/features/auctions/components/AuctionDetailPageView.js +1 -1
  29. package/dist/features/auctions/components/MarketplaceAuctionGrid.d.ts +1 -2
  30. package/dist/features/auctions/components/MarketplaceAuctionGrid.js +8 -7
  31. package/dist/features/auctions/components/PlaceBidFormClient.js +32 -15
  32. package/dist/features/before-after/components/BeforeAfterGallery.js +2 -2
  33. package/dist/features/blog/components/BlogIndexListing.js +3 -3
  34. package/dist/features/blog/components/BlogListView.js +3 -3
  35. package/dist/features/blog/components/BlogPostView.js +2 -2
  36. package/dist/features/categories/components/BundleCollage.js +2 -2
  37. package/dist/features/categories/components/CategoriesIndexListing.js +3 -3
  38. package/dist/features/categories/components/CategoryBundlesListing.js +2 -2
  39. package/dist/features/categories/components/CategoryGrid.js +2 -2
  40. package/dist/features/categories/components/CategoryProductsListing.js +2 -2
  41. package/dist/features/categories/components/CategoryStoresListing.js +2 -2
  42. package/dist/features/collections/components/CollectionCard.js +2 -2
  43. package/dist/features/events/components/EventRafflesSection.js +2 -2
  44. package/dist/features/events/components/EventsIndexListing.js +2 -2
  45. package/dist/features/events/components/EventsListView.js +3 -3
  46. package/dist/features/homepage/components/FeaturedBundlesSection.js +2 -2
  47. package/dist/features/homepage/components/GoogleReviewsSection.js +2 -2
  48. package/dist/features/homepage/components/HomepageSkeleton.js +1 -1
  49. package/dist/features/homepage/components/SecurityHighlightsSection.js +1 -1
  50. package/dist/features/homepage/components/SocialFeedSection.js +2 -2
  51. package/dist/features/homepage/components/StatsCounterSection.js +1 -1
  52. package/dist/features/homepage/components/TrustBadges.js +2 -2
  53. package/dist/features/homepage/components/TrustFeaturesSection.js +1 -1
  54. package/dist/features/messages/hooks/useConversation.js +39 -15
  55. package/dist/features/messages/hooks/useConversations.js +36 -14
  56. package/dist/features/messages/repository/conversations.repository.d.ts +23 -0
  57. package/dist/features/messages/repository/conversations.repository.js +53 -0
  58. package/dist/features/pre-orders/components/PreOrdersIndexListing.js +2 -3
  59. package/dist/features/products/components/AuctionsIndexListing.js +2 -3
  60. package/dist/features/products/components/PrizeDrawCollage.js +2 -2
  61. package/dist/features/products/components/PrizeDrawsIndexListing.js +2 -3
  62. package/dist/features/products/components/PrizeDrawsSection.js +2 -2
  63. package/dist/features/products/components/ProductGrid.js +2 -7
  64. package/dist/features/products/components/ProductsIndexListing.js +3 -3
  65. package/dist/features/products/components/RelatedProducts.js +2 -2
  66. package/dist/features/promotions/components/CouponsIndexListing.js +1 -1
  67. package/dist/features/reviews/components/ReviewDetailPageView.js +2 -2
  68. package/dist/features/reviews/components/ReviewsListingPanel.js +2 -2
  69. package/dist/features/seller/components/SellerCouponsView.js +2 -2
  70. package/dist/features/seller/components/SellerDashboardView.js +3 -3
  71. package/dist/features/server-errors/schemas/firestore.d.ts +9 -0
  72. package/dist/features/stores/components/StoreAuctionsListing.js +2 -3
  73. package/dist/features/stores/components/StoreClassifiedsListing.js +2 -3
  74. package/dist/features/stores/components/StoreDigitalCodesListing.js +2 -3
  75. package/dist/features/stores/components/StoreGuideHubView.js +2 -2
  76. package/dist/features/stores/components/StoreLiveItemsListing.js +2 -3
  77. package/dist/features/stores/components/StorePreOrdersListing.js +2 -3
  78. package/dist/features/stores/components/StoreProductsListing.js +2 -2
  79. package/dist/features/stores/components/StoresIndexListing.js +3 -3
  80. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +54 -1
  81. package/dist/next/api/routeHandler.js +27 -7
  82. package/dist/providers/db-firebase/base.repository.js +34 -18
  83. package/dist/react/hooks/useRealtimeEvent.js +22 -4
  84. package/dist/server.d.ts +2 -0
  85. package/dist/server.js +5 -0
  86. package/dist/styles.css +84 -20
  87. package/dist/tailwind-utilities.css +1 -1
  88. package/dist/tokens/tokens.css +7 -2
  89. package/dist/ui/components/Layout.d.ts +31 -18
  90. package/dist/ui/components/Layout.js +30 -19
  91. package/dist/ui/components/Layout.style.css +76 -17
  92. package/dist/ui/components/ListingLayout.d.ts +2 -0
  93. package/dist/ui/components/ListingLayout.js +16 -2
  94. package/dist/ui/components/StickyToolbar.js +3 -48
  95. package/dist/ui/components/toolbar-collapse.d.ts +44 -0
  96. package/dist/ui/components/toolbar-collapse.js +93 -0
  97. package/package.json +1 -1
@@ -31,18 +31,58 @@ export type HttpErrorCode = (typeof HTTP_ERROR_CODES)[keyof typeof HTTP_ERROR_CO
31
31
  export interface MappedError {
32
32
  status: number;
33
33
  code: string;
34
+ /**
35
+ * The CLIENT-FACING message. For any `status >= 500` in production this is a
36
+ * generic string — never the thrown error's own text.
37
+ */
34
38
  message: string;
39
+ /**
40
+ * The unscrubbed message, present only when `message` was replaced.
41
+ *
42
+ * 🛑 Recorders (serverErrors, logs) MUST use `internalMessage ?? message`.
43
+ * Responses MUST use `message`. The whole point of the split is that
44
+ * scrubbing the UI must not blind the log store — before this existed, the
45
+ * raw text was the only copy of the fault and it was being shipped to the
46
+ * browser instead (a `/var/task/...` path leaked to end users), while
47
+ * scrubbing it naively would have left nothing anywhere.
48
+ */
49
+ internalMessage?: string;
35
50
  issues?: unknown[];
51
+ /**
52
+ * True when the status/code came from the FIRESTORE error map — i.e. the
53
+ * datastore itself refused, rather than our own business logic deciding.
54
+ *
55
+ * Needed because the two are indistinguishable by code alone. Firestore
56
+ * `permission-denied` (an expired service-account key) and
57
+ * `resource-exhausted` (quota blown) map to 403/429 — exactly the codes our
58
+ * RBAC guard and rate limiter emit in normal operation. Before this flag,
59
+ * those infrastructure failures fell below the `status >= 500` persist
60
+ * threshold and were recorded nowhere; adding their CODES to
61
+ * `LOG_AS_INCIDENT_CODES` instead would have persisted every ordinary 404
62
+ * and every rate-limited request along with them.
63
+ */
64
+ infrastructure?: boolean;
36
65
  }
37
66
  /**
38
- * Map any thrown value to a stable HTTP error envelope shape.
67
+ * Classify a thrown value AND make the result safe to send to a browser.
39
68
  *
40
- * Order of precedence:
41
- * - AppError subclasses use their declared statusCode + code
42
- * - DatabaseError unwraps its `data` to look for a wrapped FirestoreError
43
- * - FirestoreError (numeric .code or string .code) maps via the table above
44
- * - ZodError shape maps to 400 VALIDATION_FAILED + issues
45
- * - Unknown Error → 500 INTERNAL; message scrubbed when isProduction
69
+ * 🛑 The scrub is keyed on **status**, not on which branch matched.
70
+ *
71
+ * It used to be the other way round: `isProduction` was consulted in exactly
72
+ * one place — the unknown-error tail at the bottom of `classifyError` — while
73
+ * the `DatabaseError`, `AppError` and `ApiError` branches above it returned
74
+ * `err.message` verbatim, in production. Since almost every real 500 in this
75
+ * codebase is a `DatabaseError`, the scrub essentially never ran. That is how
76
+ * a Node `MODULE_NOT_FOUND` — including the absolute server path
77
+ * `/var/task/.next/server/chunks/...` and its full require stack — was rendered
78
+ * as user-facing copy inside the "Place your bid" modal.
79
+ *
80
+ * Keying on status means a branch added later cannot reintroduce the leak by
81
+ * forgetting to check a flag.
82
+ *
83
+ * 4xx messages are NOT scrubbed: they are deliberate, user-actionable copy
84
+ * ("Bid too low", "Coupon expired") produced by our own validation, and
85
+ * replacing them with a generic string would make the product worse.
46
86
  */
47
87
  export declare function mapToHttpError(err: unknown, opts?: {
48
88
  isProduction?: boolean;
@@ -34,6 +34,8 @@ export const HTTP_ERROR_CODES = {
34
34
  CLIENT_WINDOW_ERROR: "CLIENT_WINDOW_ERROR",
35
35
  CLIENT_UNHANDLED: "CLIENT_UNHANDLED",
36
36
  };
37
+ /** Shown to users for any 5xx in production. */
38
+ const GENERIC_5XX_MESSAGE = "An internal error occurred";
37
39
  function isZodLikeError(err) {
38
40
  return (typeof err === "object" &&
39
41
  err !== null &&
@@ -84,6 +86,7 @@ function mapFirestore(err) {
84
86
  status: mapped.status,
85
87
  code: mapped.code,
86
88
  message: err.message ?? mapped.code,
89
+ infrastructure: true,
87
90
  };
88
91
  }
89
92
  /**
@@ -94,9 +97,12 @@ function mapFirestore(err) {
94
97
  * - DatabaseError unwraps its `data` to look for a wrapped FirestoreError
95
98
  * - FirestoreError (numeric .code or string .code) maps via the table above
96
99
  * - ZodError shape maps to 400 VALIDATION_FAILED + issues
97
- * - Unknown Error → 500 INTERNAL; message scrubbed when isProduction
100
+ * - Unknown Error → 500 INTERNAL
101
+ *
102
+ * Production scrubbing of 5xx messages is applied ONCE, by `mapToHttpError`
103
+ * below — deliberately not inside these branches. See the note there.
98
104
  */
99
- export function mapToHttpError(err, opts) {
105
+ function classifyError(err, opts) {
100
106
  const isProduction = opts?.isProduction ?? false;
101
107
  // AppError subclasses first — they carry status + code authoritatively
102
108
  if (err instanceof ValidationError) {
@@ -194,15 +200,50 @@ export function mapToHttpError(err, opts) {
194
200
  };
195
201
  }
196
202
  }
197
- // Unknown — keep message in dev, scrub in prod
203
+ // Unknown
204
+ void isProduction; // scrubbing happens once, in mapToHttpError
198
205
  const rawMessage = err instanceof Error
199
206
  ? err.message
200
207
  : typeof err === "string"
201
208
  ? err
202
- : "An internal error occurred";
209
+ : GENERIC_5XX_MESSAGE;
203
210
  return {
204
211
  status: 500,
205
212
  code: HTTP_ERROR_CODES.INTERNAL,
206
- message: isProduction ? "An internal error occurred" : rawMessage,
213
+ message: rawMessage,
214
+ };
215
+ }
216
+ /**
217
+ * Classify a thrown value AND make the result safe to send to a browser.
218
+ *
219
+ * 🛑 The scrub is keyed on **status**, not on which branch matched.
220
+ *
221
+ * It used to be the other way round: `isProduction` was consulted in exactly
222
+ * one place — the unknown-error tail at the bottom of `classifyError` — while
223
+ * the `DatabaseError`, `AppError` and `ApiError` branches above it returned
224
+ * `err.message` verbatim, in production. Since almost every real 500 in this
225
+ * codebase is a `DatabaseError`, the scrub essentially never ran. That is how
226
+ * a Node `MODULE_NOT_FOUND` — including the absolute server path
227
+ * `/var/task/.next/server/chunks/...` and its full require stack — was rendered
228
+ * as user-facing copy inside the "Place your bid" modal.
229
+ *
230
+ * Keying on status means a branch added later cannot reintroduce the leak by
231
+ * forgetting to check a flag.
232
+ *
233
+ * 4xx messages are NOT scrubbed: they are deliberate, user-actionable copy
234
+ * ("Bid too low", "Coupon expired") produced by our own validation, and
235
+ * replacing them with a generic string would make the product worse.
236
+ */
237
+ export function mapToHttpError(err, opts) {
238
+ const isProduction = opts?.isProduction ?? false;
239
+ const mapped = classifyError(err, opts);
240
+ if (!isProduction || mapped.status < 500)
241
+ return mapped;
242
+ if (mapped.message === GENERIC_5XX_MESSAGE)
243
+ return mapped;
244
+ return {
245
+ ...mapped,
246
+ message: GENERIC_5XX_MESSAGE,
247
+ internalMessage: mapped.message,
207
248
  };
208
249
  }
@@ -17,3 +17,4 @@ export { safeRead, installDegradedReadReporter, type DegradedReadReport, } from
17
17
  export { normalizeError, getErrorMessage, isAuthError, isApiNormalized, isAppNormalized, isFirebaseAuthNormalized, isFirebaseFirestoreNormalized, isFirebaseStorageNormalized, isNativeNormalized, isNetworkNormalized, isUnknownNormalized, isZodNormalized, } from "./normalize";
18
18
  export type { NormalizedError, NormalizedApiError, NormalizedAppError, NormalizedFirebaseAuthError, NormalizedFirebaseFirestoreError, NormalizedFirebaseStorageError, NormalizedNativeError, NormalizedNetworkError, NormalizedUnknownThrownValue, NormalizedZodError, } from "./normalize";
19
19
  export * from "./degraded";
20
+ export { describeCauseChain } from "./describe-cause";
@@ -26,3 +26,7 @@ export { safeRead, installDegradedReadReporter, } from "./safe-read";
26
26
  // `appkit/src/utils/auth-error.ts` (now a thin re-export shim).
27
27
  export { normalizeError, getErrorMessage, isAuthError, isApiNormalized, isAppNormalized, isFirebaseAuthNormalized, isFirebaseFirestoreNormalized, isFirebaseStorageNormalized, isNativeNormalized, isNetworkNormalized, isUnknownNormalized, isZodNormalized, } from "./normalize";
28
28
  export * from "./degraded";
29
+ // Flattens Error.cause into a persistable string. Required by every recorder:
30
+ // AppError rebases its own stack onto the wrap site, so the frames naming the
31
+ // real fault exist only on the cause.
32
+ export { describeCauseChain } from "./describe-cause";
@@ -119,7 +119,7 @@ function renderProfileHero(t, ctx) {
119
119
  return (_jsx(Section, { color: "inverse", tone: "accent-banner", paddingY: "y-2-5xl", children: _jsx(Div, { className: `${page.container.md}`, children: _jsxs(Stack, { direction: "sm-row", wrap: true, align: "center", smAlign: "end", gap: "md", children: [_jsx(Row, { align: "center", justify: "center", className: `w-20 h-20 flex-shrink-0 ${__O.hidden}`, surface: "default", rounded: "full", children: photoURL ? _jsx(MediaImage, { src: photoURL, alt: displayName, size: "avatar", fallback: "\uD83D\uDC64" }) : _jsx(User, { className: "w-10 h-10 text-white/60" }) }), _jsxs(Stack, { gap: "xs", className: "text-center sm:text-left", children: [_jsxs(Row, { justify: "start", gap: "xs", wrap: true, children: [_jsx(Heading, { color: "inverse", level: 1, variant: "none", className: "mb-0", children: displayName }), isSeller && _jsx(Span, { color: "inverse", size: "xs", weight: "semibold", className: "bg-[white]/20", padding: "pill-sm", rounded: "full", children: t("roleSeller") })] }), _jsx(Text, { color: "inverse", variant: "none", size: "sm", children: memberSince })] }), isSeller && storeSlug && (_jsx(Div, { className: "sm:ml-auto", children: _jsxs(Link, { href: String(ROUTES.PUBLIC.STORE_DETAIL(storeSlug)), className: "inline-flex items-center gap-[var(--appkit-space-1-5)] rounded-full border border-white/30 bg-white/10 hover:bg-white/20 px-[var(--appkit-space-4)] py-[var(--appkit-space-2)] text-[length:var(--appkit-text-sm)] font-medium text-white transition-colors", children: [_jsx(ExternalLink, { className: "w-3.5 h-3.5" }), t("visitStore")] }) }))] }) }) }));
120
120
  }
121
121
  function renderProfileStatsRow(flex, statItems) {
122
- return (_jsx(Grid, { gap: "md", className: "grid-cols-2 sm:grid-cols-4", children: statItems.map(({ icon: Icon, label, value }) => (_jsxs(Div, { rounded: "xl", className: `${__P.p4} text-center`, border: "default", surface: "muted", children: [_jsx(Row, { centered: true, className: `${flex.center} mb-1`, children: _jsx(Icon, { className: "w-4 h-4 text-neutral-400" }) }), _jsx(Text, { size: "lg", weight: "bold", children: value }), _jsx(Text, { variant: "secondary", size: "xs", children: label })] }, label))) }));
122
+ return (_jsx(Grid, { cols: "statTiles", gap: "md", children: statItems.map(({ icon: Icon, label, value }) => (_jsxs(Div, { rounded: "xl", className: `${__P.p4} text-center`, border: "default", surface: "muted", children: [_jsx(Row, { centered: true, className: `${flex.center} mb-1`, children: _jsx(Icon, { className: "w-4 h-4 text-neutral-400" }) }), _jsx(Text, { size: "lg", weight: "bold", children: value }), _jsx(Text, { variant: "secondary", size: "xs", children: label })] }, label))) }));
123
123
  }
124
124
  function renderProfileBioSection(pub) {
125
125
  if (!pub?.bio && !pub?.location && !pub?.website)
@@ -132,11 +132,11 @@ function renderStoreDescriptionSection(isSeller, storeSlug, storeDescription, st
132
132
  return (_jsx(Section, { children: _jsxs(Div, { rounded: "2xl", className: `${__P.p6}`, border: "default", surface: "subtle", children: [_jsx(Heading, { level: 3, className: "mb-2", children: storeName }), _jsx(Text, { variant: "secondary", className: "leading-relaxed", size: "sm", children: storeDescription }), _jsxs(Link, { href: String(ROUTES.PUBLIC.STORE_DETAIL(storeSlug)), className: "mt-3 inline-flex items-center gap-[var(--appkit-space-1)] text-[length:var(--appkit-text-sm)] font-medium text-primary hover:underline", children: [t("visitStore"), " \u2192"] })] }) }));
133
133
  }
134
134
  function renderProfileListingsSection(t, products, storeSlug) {
135
- return (_jsxs(Section, { children: [_jsx(Heading, { level: 2, className: "mb-4", children: t("listingsTitle") }), products.length === 0 ? (_jsxs(Div, { rounded: "2xl", padding: "3xl", className: "text-center", border: "default", surface: "subtle", children: [_jsx(ShoppingBag, { className: CLS_EMPTY_ICON }), _jsx(Text, { variant: "secondary", size: "sm", children: t("noListings") })] })) : (_jsx(Grid, { gap: "md", className: "grid-cols-2 sm:grid-cols-3 lg:grid-cols-4", children: products.slice(0, 8).map((p) => _jsx(ProductCard, { product: toProductItem(p), href: getProductHref(p) }, p.id)) })), products.length > 8 && storeSlug && (_jsx(Div, { className: "mt-4 text-center", children: _jsx(Link, { href: String(ROUTES.PUBLIC.STORE_PRODUCTS(storeSlug)), className: "text-[length:var(--appkit-text-sm)] font-medium text-primary hover:underline", children: t("viewAllListings", { count: products.length }) }) }))] }));
135
+ return (_jsxs(Section, { children: [_jsx(Heading, { level: 2, className: "mb-4", children: t("listingsTitle") }), products.length === 0 ? (_jsxs(Div, { rounded: "2xl", padding: "3xl", className: "text-center", border: "default", surface: "subtle", children: [_jsx(ShoppingBag, { className: CLS_EMPTY_ICON }), _jsx(Text, { variant: "secondary", size: "sm", children: t("noListings") })] })) : (_jsx(Grid, { cols: "cards", gap: "md", children: products.slice(0, 8).map((p) => _jsx(ProductCard, { product: toProductItem(p), href: getProductHref(p) }, p.id)) })), products.length > 8 && storeSlug && (_jsx(Div, { className: "mt-4 text-center", children: _jsx(Link, { href: String(ROUTES.PUBLIC.STORE_PRODUCTS(storeSlug)), className: "text-[length:var(--appkit-text-sm)] font-medium text-primary hover:underline", children: t("viewAllListings", { count: products.length }) }) }))] }));
136
136
  }
137
137
  function renderAuthoredReviewsSection(t, reviews, displayName) {
138
- return (_jsxs(Section, { children: [_jsx(Heading, { level: 2, className: "mb-4", children: t("reviewsAuthoredTitle", { name: displayName }) }), reviews.length === 0 ? (_jsxs(Div, { rounded: "2xl", padding: "3xl", className: "text-center", border: "default", surface: "subtle", children: [_jsx(Star, { className: CLS_EMPTY_ICON }), _jsx(Text, { variant: "secondary", size: "sm", children: t("noReviewsAuthored", { name: displayName }) })] })) : (_jsx(Grid, { gap: "md", className: "grid-cols-1 sm:grid-cols-2", children: reviews.slice(0, 6).map((review) => _jsx(ReviewCard, { review: review }, review.id)) }))] }));
138
+ return (_jsxs(Section, { children: [_jsx(Heading, { level: 2, className: "mb-4", children: t("reviewsAuthoredTitle", { name: displayName }) }), reviews.length === 0 ? (_jsxs(Div, { rounded: "2xl", padding: "3xl", className: "text-center", border: "default", surface: "subtle", children: [_jsx(Star, { className: CLS_EMPTY_ICON }), _jsx(Text, { variant: "secondary", size: "sm", children: t("noReviewsAuthored", { name: displayName }) })] })) : (_jsx(Grid, { cols: "cardsWide", gap: "md", children: reviews.slice(0, 6).map((review) => _jsx(ReviewCard, { review: review }, review.id)) }))] }));
139
139
  }
140
140
  function renderBuyerReceivedReviewsSection(t, reviews) {
141
- return (_jsxs(Section, { children: [_jsx(Heading, { level: 2, className: "mb-4", children: t("reviewsReceivedAsBuyerTitle") || "Reviews Received" }), _jsx(Grid, { gap: "md", className: "grid-cols-1 sm:grid-cols-2", children: reviews.slice(0, 6).map((review) => _jsx(ReviewCard, { review: review }, review.id)) })] }));
141
+ return (_jsxs(Section, { children: [_jsx(Heading, { level: 2, className: "mb-4", children: t("reviewsReceivedAsBuyerTitle") || "Reviews Received" }), _jsx(Grid, { cols: "cardsWide", gap: "md", children: reviews.slice(0, 6).map((review) => _jsx(ReviewCard, { review: review }, review.id)) })] }));
142
142
  }
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Button, Div, Span, Stack, Text } from "../../../ui";
2
+ import { Button, Div, Grid, Span, Stack, Text } from "../../../ui";
3
3
  export function AddressCard({ address, onEdit, onDelete, onSetDefault, labels = {}, }) {
4
4
  const line1 = "line1" in address && address.line1
5
5
  ? address.line1
@@ -10,5 +10,5 @@ export function AddressCard({ address, onEdit, onDelete, onSetDefault, labels =
10
10
  return (_jsxs(Div, { surface: "card", padding: "sm", className: "relative", children: [address.isDefault && (_jsx(Span, { color: "success", surface: "success-surface", size: "xs", weight: "medium", className: "absolute right-3 top-3", rounded: "full", padding: "pill-xs", children: labels.defaultBadge ?? "Default" })), address.label && (_jsx(Text, { className: "mb-1 tracking-wide text-[var(--appkit-color-text-muted)]", size: "xs", weight: "semibold", transform: "uppercase", children: address.label })), _jsx(Text, { className: "text-[var(--appkit-color-text)]", size: "sm", children: line1 }), line2 && _jsx(Text, { className: "text-[var(--appkit-color-text)]", size: "sm", children: line2 }), _jsxs(Text, { className: "text-[var(--appkit-color-text)]", size: "sm", children: [address.city, ", ", address.state, " ", address.postalCode] }), _jsx(Text, { className: "text-[var(--appkit-color-text)]", size: "sm", children: address.country }), address.phone && (_jsx(Text, { className: "mt-1 text-[var(--appkit-color-text-muted)]", size: "sm", children: address.phone })), _jsxs(Div, { layout: "flex", gap: "1", className: "mt-3", children: [onEdit && (_jsx(Button, { onClick: () => onEdit(address), variant: "ghost", size: "sm", className: "text-[length:var(--appkit-text-xs)] font-medium text-primary hover:underline", children: labels.edit ?? "Edit" })), onDelete && (_jsx(Button, { onClick: () => onDelete(address.id), variant: "ghost", size: "sm", className: "text-[length:var(--appkit-text-xs)] font-medium text-error hover:underline", children: labels.delete ?? "Delete" })), !address.isDefault && onSetDefault && (_jsx(Button, { onClick: () => onSetDefault(address.id), variant: "outline", size: "sm", className: "ml-auto text-[length:var(--appkit-text-xs)] font-medium", children: labels.setDefault ?? "Set default" }))] })] }));
11
11
  }
12
12
  export function AddressBook({ addresses, onEdit, onDelete, onSetDefault, onAdd, emptyLabel = "No saved addresses", addLabel = "Add Address", }) {
13
- return (_jsxs(Stack, { gap: "md", children: [addresses.length === 0 && (_jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "sm", children: emptyLabel })), _jsx(Div, { layout: "grid", gap: "4", className: "sm:grid-cols-2", children: addresses.map((addr) => (_jsx(AddressCard, { address: addr, onEdit: onEdit, onDelete: onDelete, onSetDefault: onSetDefault }, addr.id))) }), onAdd && (_jsxs(Button, { rounded: "lg", onClick: onAdd, variant: "outline", paddingX: "md", paddingY: "md", textSize: "sm", weight: "medium", className: "mt-2 border border-dashed border-neutral-300 dark:border-[var(--appkit-color-border)] text-[var(--appkit-color-text-muted)] dark:text-[var(--appkit-color-text-muted)] transition hover:border-neutral-400 dark:hover:border-[var(--appkit-color-border-subtle)] hover:text-neutral-700 dark:hover:text-[var(--appkit-color-text)]", children: ["+ ", addLabel] }))] }));
13
+ return (_jsxs(Stack, { gap: "md", children: [addresses.length === 0 && (_jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "sm", children: emptyLabel })), _jsx(Grid, { cols: "cardsWide", gap: "md", children: addresses.map((addr) => (_jsx(AddressCard, { address: addr, onEdit: onEdit, onDelete: onDelete, onSetDefault: onSetDefault }, addr.id))) }), onAdd && (_jsxs(Button, { rounded: "lg", onClick: onAdd, variant: "outline", paddingX: "md", paddingY: "md", textSize: "sm", weight: "medium", className: "mt-2 border border-dashed border-neutral-300 dark:border-[var(--appkit-color-border)] text-[var(--appkit-color-text-muted)] dark:text-[var(--appkit-color-text-muted)] transition hover:border-neutral-400 dark:hover:border-[var(--appkit-color-border-subtle)] hover:text-neutral-700 dark:hover:text-[var(--appkit-color-text)]", children: ["+ ", addLabel] }))] }));
14
14
  }
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useState, useCallback, useMemo } from "react";
4
4
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
5
5
  import { useAddresses } from "../hooks/useAddresses";
6
- import { Div, ListingFilterDrawer, ListingToolbar, Stack, Text } from "../../../ui";
6
+ import { Div, Grid, ListingFilterDrawer, ListingToolbar, Stack, Text } from "../../../ui";
7
7
  import { AddressBook } from "./AddressBook";
8
8
  import { AddressFilters } from "./AddressFilters";
9
9
  import { TABLE_KEYS, VIEW_MODE } from "../../../constants/table-keys";
@@ -74,5 +74,5 @@ export function AddressesIndexListing({ onAdd, onEdit, onDelete, }) {
74
74
  },
75
75
  });
76
76
  return (_jsxs(Stack, { gap: "md", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search by address, postcode or label...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, view: view, onViewChange: (v) => { if (v === "table")
77
- return; setView(v); }, onResetAll: resetAll, hasActiveState: hasActiveState }), _jsx(Div, { className: "sm:px-[1rem]", padding: "x-sm", children: isLoading ? (_jsx(Div, { layout: "grid", gap: "4", className: "sm:grid-cols-2", children: Array.from({ length: 4 }).map((_, i) => (_jsxs(Stack, { gap: "sm", rounded: "xl", border: "default", padding: "md", className: "animate-pulse", children: [_jsx(Div, { className: "h-4 w-1/3", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-3 w-3/4", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-3 w-1/2", surface: "subtle", rounded: "default" })] }, i))) })) : addresses.length === 0 ? (_jsx(Text, { paddingY: "xl", color: "muted", size: "sm", align: "start", children: table.get(TABLE_KEYS.QUERY) ? `No addresses matching "${table.get(TABLE_KEYS.QUERY)}"` : "No saved addresses." })) : (_jsx(AddressBook, { addresses: addresses, onEdit: onEdit, onDelete: onDelete, onAdd: onAdd })) }), _jsx(ListingFilterDrawer, { open: filterOpen, onClose: () => setFilterOpen(false), onApply: applyFilters, onClear: clearFilters, activeCount: activeFilterCount, children: _jsx(AddressFilters, { table: pendingTable }) })] }));
77
+ return; setView(v); }, onResetAll: resetAll, hasActiveState: hasActiveState }), _jsx(Div, { className: "sm:px-[1rem]", padding: "x-sm", children: isLoading ? (_jsx(Grid, { cols: "cardsWide", gap: "md", children: Array.from({ length: 4 }).map((_, i) => (_jsxs(Stack, { gap: "sm", rounded: "xl", border: "default", padding: "md", className: "animate-pulse", children: [_jsx(Div, { className: "h-4 w-1/3", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-3 w-3/4", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-3 w-1/2", surface: "subtle", rounded: "default" })] }, i))) })) : addresses.length === 0 ? (_jsx(Text, { paddingY: "xl", color: "muted", size: "sm", align: "start", children: table.get(TABLE_KEYS.QUERY) ? `No addresses matching "${table.get(TABLE_KEYS.QUERY)}"` : "No saved addresses." })) : (_jsx(AddressBook, { addresses: addresses, onEdit: onEdit, onDelete: onDelete, onAdd: onAdd })) }), _jsx(ListingFilterDrawer, { open: filterOpen, onClose: () => setFilterOpen(false), onApply: applyFilters, onClear: clearFilters, activeCount: activeFilterCount, children: _jsx(AddressFilters, { table: pendingTable }) })] }));
78
78
  }
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useQuery } from "@tanstack/react-query";
4
- import { Div, Heading, Stack } from "../../../ui";
4
+ import { Div, Grid, Heading, Stack } from "../../../ui";
5
5
  import { Alert } from "../../../ui";
6
6
  import { apiClient } from "../../../http";
7
7
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
@@ -21,7 +21,7 @@ export function AdminAnalyticsView({ data, labels = {}, formatRevenue = (n) => S
21
21
  const summary = resolvedData?.summary;
22
22
  const ordersByMonth = resolvedData?.ordersByMonth ?? [];
23
23
  const topProducts = resolvedData?.topProducts ?? [];
24
- return (_jsxs(Div, { className: className, children: [renderHeader ? (renderHeader()) : labels.title ? (_jsx(Heading, { level: 1, className: "mb-6", size: "2xl", weight: "bold", children: labels.title })) : null, query.error ? (_jsx(Alert, { variant: "error", title: "Could not load analytics", children: query.error instanceof Error ? query.error.message : "Unknown error" })) : null, renderDateRange?.(), renderSummaryCards ? (renderSummaryCards()) : busy ? (_jsx(Div, { layout: "grid", gap: "4", className: "grid-cols-1 md:grid-cols-2 lg:grid-cols-4", children: Array.from({ length: 4 }).map((_, index) => (_jsx(Div, { className: "h-28 border border-[var(--appkit-color-border-subtle)] animate-pulse", surface: "default", rounded: "xl" }, index))) })) : summary ? (_jsxs(Div, { layout: "grid", gap: "4", className: "grid-cols-1 md:grid-cols-2 lg:grid-cols-4", children: [_jsx(AdminStatCard, { label: labels.totalRevenue ?? "Total Revenue", value: formatRevenue(summary.totalRevenue) }), _jsx(AdminStatCard, { label: labels.totalOrders ?? "Total Orders", value: String(summary.totalOrders) }), _jsx(AdminStatCard, { label: labels.revenueThisMonth ?? "Revenue This Month", value: formatRevenue(summary.revenueThisMonth) }), _jsx(AdminStatCard, { label: labels.ordersThisMonth ?? "Orders This Month", value: String(summary.newOrdersThisMonth) })] })) : null, renderCharts ? (renderCharts()) : ordersByMonth.length > 0 ? (_jsxs(Stack, { gap: "lg", children: [_jsx(AdminRevenueChart, { data: ordersByMonth, labels: { title: labels.revenueByMonth } }), _jsx(AdminOrdersChart, { data: ordersByMonth, labels: { title: labels.ordersByMonth } })] })) : null, renderTable ? (renderTable()) : topProducts.length > 0 ? (_jsx(AdminTopProductsTable, { products: topProducts, labels: {
24
+ return (_jsxs(Div, { className: className, children: [renderHeader ? (renderHeader()) : labels.title ? (_jsx(Heading, { level: 1, className: "mb-6", size: "2xl", weight: "bold", children: labels.title })) : null, query.error ? (_jsx(Alert, { variant: "error", title: "Could not load analytics", children: query.error instanceof Error ? query.error.message : "Unknown error" })) : null, renderDateRange?.(), renderSummaryCards ? (renderSummaryCards()) : busy ? (_jsx(Grid, { cols: "statTiles", gap: "md", children: Array.from({ length: 4 }).map((_, index) => (_jsx(Div, { className: "h-28 border border-[var(--appkit-color-border-subtle)] animate-pulse", surface: "default", rounded: "xl" }, index))) })) : summary ? (_jsxs(Grid, { cols: "statTiles", gap: "md", children: [_jsx(AdminStatCard, { label: labels.totalRevenue ?? "Total Revenue", value: formatRevenue(summary.totalRevenue) }), _jsx(AdminStatCard, { label: labels.totalOrders ?? "Total Orders", value: String(summary.totalOrders) }), _jsx(AdminStatCard, { label: labels.revenueThisMonth ?? "Revenue This Month", value: formatRevenue(summary.revenueThisMonth) }), _jsx(AdminStatCard, { label: labels.ordersThisMonth ?? "Orders This Month", value: String(summary.newOrdersThisMonth) })] })) : null, renderCharts ? (renderCharts()) : ordersByMonth.length > 0 ? (_jsxs(Stack, { gap: "lg", children: [_jsx(AdminRevenueChart, { data: ordersByMonth, labels: { title: labels.revenueByMonth } }), _jsx(AdminOrdersChart, { data: ordersByMonth, labels: { title: labels.ordersByMonth } })] })) : null, renderTable ? (renderTable()) : topProducts.length > 0 ? (_jsx(AdminTopProductsTable, { products: topProducts, labels: {
25
25
  title: labels.topProducts,
26
26
  orders: labels.orders,
27
27
  view: labels.view,
@@ -5,7 +5,7 @@ import { SIEVE_OP, Stack, sieveFilter } from "@mohasinac/appkit/client";
5
5
  import { sortBy } from "@mohasinac/appkit/client";
6
6
  import React, { useCallback } from "react";
7
7
  import { useQueryClient } from "@tanstack/react-query";
8
- import { Div, FilterChipGroup, ListingLayout, Text, useToast } from "../../../ui";
8
+ import { Div, Grid, FilterChipGroup, ListingLayout, Text, useToast } from "../../../ui";
9
9
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
10
10
  import { ROW_ACTION_META, ROW_ACTION_ID, ADMIN_BULK_ACTIONS } from "../../products/constants/action-defs";
11
11
  import { ADMIN_COUPON_TYPE_TABS } from "../constants/filter-tabs";
@@ -116,7 +116,7 @@ export function AdminCouponsView({ children, onBulkArchive, onBulkDelete, ...pro
116
116
  ], value: pendingFilters.showExpired ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, showExpired: id })), allId: "" })] })),
117
117
  renderCards: (rows, _view, selectionCtx, isLoading) => {
118
118
  rowsRef.current = rows;
119
- return isLoading ? (_jsx(Div, { gap: "3", className: "fluid-grid-card", children: Array.from({ length: 6 }).map((_, i) => (_jsxs(Stack, { border: "skeleton", className: "animate-pulse", gap: "3", rounded: "xl", padding: "md", children: [_jsx(Div, { className: "h-6 w-2/3", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-4 w-full", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-3 w-1/2", surface: "subtle", rounded: "default" })] }, i))) })) : rows.length === 0 ? (_jsx(Div, { className: "text-center", padding: "y-4xl", children: _jsx(Text, { color: "faint", children: "No coupons found" }) })) : (_jsx(Div, { gap: "3", className: "fluid-grid-card", children: rows.map((row) => (_jsx(CouponCard, { coupon: row.raw, onEdit: selectionCtx.openEditPanel, onToggleActive: handleToggle, onDelete: handleDelete }, row.id))) }));
119
+ return isLoading ? (_jsx(Grid, { cols: "cardsWide", gap: "3", children: Array.from({ length: 6 }).map((_, i) => (_jsxs(Stack, { border: "skeleton", className: "animate-pulse", gap: "3", rounded: "xl", padding: "md", children: [_jsx(Div, { className: "h-6 w-2/3", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-4 w-full", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-3 w-1/2", surface: "subtle", rounded: "default" })] }, i))) })) : rows.length === 0 ? (_jsx(Div, { className: "text-center", padding: "y-4xl", children: _jsx(Text, { color: "faint", children: "No coupons found" }) })) : (_jsx(Grid, { cols: "cardsWide", gap: "3", children: rows.map((row) => (_jsx(CouponCard, { coupon: row.raw, onEdit: selectionCtx.openEditPanel, onToggleActive: handleToggle, onDelete: handleDelete }, row.id))) }));
120
120
  },
121
121
  renderEditor: ({ editId, closePanel }) => (_jsx(AdminCouponEditorView, { couponId: editId ?? undefined, initialData: editId ? rowsRef.current.find((r) => r.id === editId)?.raw : undefined, onSaved: closePanel, onDeleted: closePanel, embedded: true })),
122
122
  resolveEditorTitle: ({ isCreate }) => (isCreate ? "Add Coupon" : "Edit Coupon"),
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Users, Package, Store, ShoppingBag, FileText, Settings, Shield, BarChart2, AlertTriangle, BookOpen, MessageSquare, CreditCard, } from "lucide-react";
3
- import { Alert, Div, Heading, Row, Section, Stack, Text, TextLink } from "../../../ui";
3
+ import { Alert, Div, Grid, Heading, Row, Section, Stack, Text, TextLink } from "../../../ui";
4
4
  import { ROUTES } from "../../../next/routing/route-map";
5
5
  const CLS_WARN_PANEL = "rounded-2xl border border-amber-200 bg-warning-surface dark:border-amber-800 p-[var(--appkit-space-6)]";
6
6
  const CLS_WARN_HEAD = "font-semibold text-amber-800 dark:text-amber-300 mb-1";
@@ -88,5 +88,5 @@ export function AdminGuideHubView({ permissions = [], isFullAdmin = false }) {
88
88
  const hasPermission = (perm) => isFullAdmin || permissions.includes(perm);
89
89
  const visibleCards = GUIDE_CARDS.filter((c) => hasPermission(c.permission));
90
90
  const hasAny = visibleCards.length > 0;
91
- return (_jsxs(Stack, { gap: "xl", padding: "b-2xl", children: [_jsx(Section, { overflow: "hidden", className: "border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)]", rounded: "2xl", shadow: "sm", children: _jsxs(Div, { paddingX: "x-md-xl", paddingY: "y-xl", className: "[background:linear-gradient(135deg,var(--appkit-color-primary-700)_0%,var(--appkit-color-cobalt)_60%,var(--appkit-color-secondary-400)_100%)]", children: [_jsxs(Row, { className: "mb-3", align: "center", gap: "3", children: [_jsx(Row, { className: "flex-shrink-0 w-10 h-10", surface: "default", align: "center", justify: "center", rounded: "xl", children: _jsx(BookOpen, { className: "w-5 h-5 text-white" }) }), _jsx(Text, { color: "inverse", className: "/80 tracking-widest", size: "sm", weight: "semibold", transform: "uppercase", children: "Admin & Employee Guide" })] }), _jsx(Heading, { color: "inverse", level: 1, className: "mb-2", mdSize: "3xl", size: "2xl", weight: "bold", children: "LetItRip Internal Guide" }), _jsx(Text, { color: "inverse", className: "/80", size: "base", children: "Everything you need to operate and manage LetItRip \u2014 India's largest collectibles marketplace." })] }) }), _jsx(Alert, { variant: "info", compact: true, children: "These guides describe expected platform behaviour. Real access to each section is enforced independently by the permission system \u2014 this page is reference only." }), !hasAny && (_jsx(Alert, { variant: "info", title: "Limited access", children: "Your permission set doesn't include read access to any admin sections yet. Contact your admin to request the permissions you need." })), hasAny && (_jsxs(Section, { children: [_jsx(Heading, { level: 2, className: "text-[var(--appkit-color-text)] mb-4", size: "lg", weight: "semibold", children: "Guides" }), _jsx(Div, { layout: "grid", gap: "4", className: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3", children: visibleCards.map(({ Icon, title, description, href }) => (_jsxs(TextLink, { variant: "bare", href: href, rounded: "xl", layout: "flex-col", gap: "md", paddingX: "lg", paddingY: "lg", shadow: "hover-md", className: "group border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] hover:border-[var(--appkit-color-primary)] transition-all", children: [_jsxs(Row, { align: "center", gap: "3", children: [_jsx(Row, { className: "flex-shrink-0 w-9 h-9 [background:linear-gradient(135deg,var(--appkit-color-primary-700)_0%,var(--appkit-color-cobalt)_100%)]", align: "center", justify: "center", rounded: "lg", children: _jsx(Icon, { className: "w-4 h-4 text-white" }) }), _jsx(Text, { className: "text-[var(--appkit-color-text)] group-hover:text-[var(--appkit-color-primary)] transition-colors", weight: "semibold", children: title })] }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)] leading-relaxed", size: "sm", children: description }), _jsx(Text, { className: "text-[var(--appkit-color-primary)] mt-auto", size: "xs", weight: "medium", children: "Read guide \u2192" })] }, title))) })] })), _jsx(Section, { className: CLS_WARN_PANEL, children: _jsxs(Row, { align: "start", gap: "3", children: [_jsx(AlertTriangle, { className: "w-5 h-5 text-warning flex-shrink-0 mt-0.5" }), _jsxs(Div, { children: [_jsx(Text, { className: CLS_WARN_HEAD, children: "Remember: with great access comes responsibility" }), _jsx(Text, { className: CLS_WARN_BODY, children: "Admin actions are logged. Never access user PII (email, phone) without a legitimate support reason. Never grant capabilities or permissions without documented approval. When in doubt, escalate to a senior admin." })] })] }) })] }));
91
+ return (_jsxs(Stack, { gap: "xl", padding: "b-2xl", children: [_jsx(Section, { overflow: "hidden", className: "border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)]", rounded: "2xl", shadow: "sm", children: _jsxs(Div, { paddingX: "x-md-xl", paddingY: "y-xl", className: "[background:linear-gradient(135deg,var(--appkit-color-primary-700)_0%,var(--appkit-color-cobalt)_60%,var(--appkit-color-secondary-400)_100%)]", children: [_jsxs(Row, { className: "mb-3", align: "center", gap: "3", children: [_jsx(Row, { className: "flex-shrink-0 w-10 h-10", surface: "default", align: "center", justify: "center", rounded: "xl", children: _jsx(BookOpen, { className: "w-5 h-5 text-white" }) }), _jsx(Text, { color: "inverse", className: "/80 tracking-widest", size: "sm", weight: "semibold", transform: "uppercase", children: "Admin & Employee Guide" })] }), _jsx(Heading, { color: "inverse", level: 1, className: "mb-2", mdSize: "3xl", size: "2xl", weight: "bold", children: "LetItRip Internal Guide" }), _jsx(Text, { color: "inverse", className: "/80", size: "base", children: "Everything you need to operate and manage LetItRip \u2014 India's largest collectibles marketplace." })] }) }), _jsx(Alert, { variant: "info", compact: true, children: "These guides describe expected platform behaviour. Real access to each section is enforced independently by the permission system \u2014 this page is reference only." }), !hasAny && (_jsx(Alert, { variant: "info", title: "Limited access", children: "Your permission set doesn't include read access to any admin sections yet. Contact your admin to request the permissions you need." })), hasAny && (_jsxs(Section, { children: [_jsx(Heading, { level: 2, className: "text-[var(--appkit-color-text)] mb-4", size: "lg", weight: "semibold", children: "Guides" }), _jsx(Grid, { cols: "cards", gap: "md", children: visibleCards.map(({ Icon, title, description, href }) => (_jsxs(TextLink, { variant: "bare", href: href, rounded: "xl", layout: "flex-col", gap: "md", paddingX: "lg", paddingY: "lg", shadow: "hover-md", className: "group border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] hover:border-[var(--appkit-color-primary)] transition-all", children: [_jsxs(Row, { align: "center", gap: "3", children: [_jsx(Row, { className: "flex-shrink-0 w-9 h-9 [background:linear-gradient(135deg,var(--appkit-color-primary-700)_0%,var(--appkit-color-cobalt)_100%)]", align: "center", justify: "center", rounded: "lg", children: _jsx(Icon, { className: "w-4 h-4 text-white" }) }), _jsx(Text, { className: "text-[var(--appkit-color-text)] group-hover:text-[var(--appkit-color-primary)] transition-colors", weight: "semibold", children: title })] }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)] leading-relaxed", size: "sm", children: description }), _jsx(Text, { className: "text-[var(--appkit-color-primary)] mt-auto", size: "xs", weight: "medium", children: "Read guide \u2192" })] }, title))) })] })), _jsx(Section, { className: CLS_WARN_PANEL, children: _jsxs(Row, { align: "start", gap: "3", children: [_jsx(AlertTriangle, { className: "w-5 h-5 text-warning flex-shrink-0 mt-0.5" }), _jsxs(Div, { children: [_jsx(Text, { className: CLS_WARN_HEAD, children: "Remember: with great access comes responsibility" }), _jsx(Text, { className: CLS_WARN_BODY, children: "Admin actions are logged. Never access user PII (email, phone) without a legitimate support reason. Never grant capabilities or permissions without documented approval. When in doubt, escalate to a senior admin." })] })] }) })] }));
92
92
  }
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import React from "react";
4
- import { Alert, Button, Div, Input, Row, Select, Stack, StackedViewShell, Text, useToast } from "../../../ui";
4
+ import { Alert, Button, Div, Grid, Input, Row, Select, Stack, StackedViewShell, Text, useToast } from "../../../ui";
5
5
  import { normalizeError } from "../../../errors/normalize";
6
6
  import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
7
7
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
@@ -64,7 +64,7 @@ function MediaBrowser({ onCopy }) {
64
64
  return files;
65
65
  return files.filter((f) => f.name.toLowerCase().includes(q));
66
66
  }, [files, search]);
67
- return (_jsxs(Stack, { className: `${__P.p4}`, gap: "3", rounded: "xl", surface: "default", border: "default", children: [_jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: "Browse existing media" }), _jsxs(Row, { align: "center", gap: "sm", wrap: true, children: [_jsx(Select, { value: prefix, onChange: (e) => setPrefix(e.target.value), options: PREFIX_OPTIONS, "aria-label": "Prefix filter", wrapperClassName: "min-w-[140px]" }), _jsx(Input, { type: "text", placeholder: "Search filename\u2026", value: search, onChange: (e) => setSearch(e.target.value), className: "flex-1 min-w-[160px]" })] }), error && (_jsx(Alert, { variant: "error", title: "Load failed", children: error })), _jsxs(Div, { layout: "grid", gap: "2", className: "grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6", children: [filtered.map((f) => {
67
+ return (_jsxs(Stack, { className: `${__P.p4}`, gap: "3", rounded: "xl", surface: "default", border: "default", children: [_jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: "Browse existing media" }), _jsxs(Row, { align: "center", gap: "sm", wrap: true, children: [_jsx(Select, { value: prefix, onChange: (e) => setPrefix(e.target.value), options: PREFIX_OPTIONS, "aria-label": "Prefix filter", wrapperClassName: "min-w-[140px]" }), _jsx(Input, { type: "text", placeholder: "Search filename\u2026", value: search, onChange: (e) => setSearch(e.target.value), className: "flex-1 min-w-[160px]" })] }), error && (_jsx(Alert, { variant: "error", title: "Load failed", children: error })), _jsxs(Grid, { cols: "navTiles", gap: "sm", children: [filtered.map((f) => {
68
68
  const isImage = (f.contentType ?? "").startsWith("image/");
69
69
  return (_jsxs(Div, { className: "group overflow-hidden", surface: "muted", rounded: "lg", border: "default", children: [_jsx(Row, { color: "muted", textSize: "xs", className: "relative aspect-square", surface: "subtle", align: "center", justify: "center", children: isImage ? (_jsx(MediaImage, { src: f.downloadURL, alt: f.name, size: "thumbnail" })) : (_jsx(Text, { align: "center", paddingX: "x-xs", className: "break-all", children: f.contentType ?? "file" })) }), _jsxs(Div, { className: "p-[0.375rem]", children: [_jsx(Text, { className: "truncate text-[10px] font-mono", color: "muted", children: f.name.split("/").pop() }), _jsx(Button, { type: "button", variant: "ghost", textSize: "xs", className: "mt-1 w-full", onClick: () => onCopy(f.downloadURL), children: ACTIONS.MEDIA["copy-url"].label })] })] }, f.name));
70
70
  }), filtered.length === 0 && !isLoading && (_jsx(Text, { paddingY: "lg", className: "col-span-full", color: "muted", size: "sm", align: "center", children: "No files found." }))] }), _jsxs(Row, { align: "center", justify: "between", children: [_jsxs(Text, { size: "xs", color: "muted", children: [filtered.length, " file(s)", nextPageToken ? " · more available" : ""] }), nextPageToken && (_jsx(Button, { type: "button", variant: "outline", onClick: () => void load(nextPageToken), disabled: isLoading, children: isLoading ? "Loading…" : "Load more" }))] })] }));
@@ -69,7 +69,7 @@ export function AdminViewCards({ rows, view, isLoading, emptyLabel = "No items f
69
69
  if (view === "list") {
70
70
  return (_jsx(Div, { rounded: "xl", border: "subtle", className: `${__O.hidden} divide-y divide-zinc-100 divide-[var(--appkit-color-border)]`, children: Array.from({ length: count }).map((_, i) => _jsx(SkeletonCard, { view: "list" }, i)) }));
71
71
  }
72
- return (_jsx(Grid, { gap: "md", className: "grid-cols-2 sm:grid-cols-3 lg:grid-cols-4", children: Array.from({ length: count }).map((_, i) => _jsx(SkeletonCard, { view: "grid" }, i)) }));
72
+ return (_jsx(Grid, { cols: "cards", gap: "md", children: Array.from({ length: count }).map((_, i) => _jsx(SkeletonCard, { view: "grid" }, i)) }));
73
73
  }
74
74
  if (rows.length === 0) {
75
75
  return (_jsx(Text, { paddingY: "3xl", size: "sm", color: "muted", align: "center", children: emptyLabel }));
@@ -77,5 +77,5 @@ export function AdminViewCards({ rows, view, isLoading, emptyLabel = "No items f
77
77
  if (view === "list") {
78
78
  return (_jsx(Div, { rounded: "xl", border: "subtle", className: `${__O.hidden} divide-y divide-zinc-100 divide-[var(--appkit-color-border)]`, children: rows.map((row) => (_jsx(AdminCardItem, { row: row, view: "list", selected: selectedIdSet?.has(row.id) ?? false, onToggleSelect: onToggleSelect, onRowClick: onRowClick, renderRowActions: renderRowActions, resourceIcon: resourceIcon }, row.id))) }));
79
79
  }
80
- return (_jsx(Grid, { gap: "md", className: "grid-cols-2 sm:grid-cols-3 lg:grid-cols-4", children: rows.map((row) => (_jsx(AdminCardItem, { row: row, view: "grid", selected: selectedIdSet?.has(row.id) ?? false, onToggleSelect: onToggleSelect, onRowClick: onRowClick, renderRowActions: renderRowActions, resourceIcon: resourceIcon }, row.id))) }));
80
+ return (_jsx(Grid, { cols: "cards", gap: "md", children: rows.map((row) => (_jsx(AdminCardItem, { row: row, view: "grid", selected: selectedIdSet?.has(row.id) ?? false, onToggleSelect: onToggleSelect, onRowClick: onRowClick, renderRowActions: renderRowActions, resourceIcon: resourceIcon }, row.id))) }));
81
81
  }
@@ -203,6 +203,6 @@ export async function AuctionDetailPageView({ id, initialAuction, onPlaceBid, on
203
203
  bidCount: typeof r.bidCount === "number" ? r.bidCount : undefined,
204
204
  slug: typeof r.slug === "string" ? r.slug : undefined,
205
205
  }));
206
- return (_jsxs(Stack, { gap: "xl", children: [related.length > 0 && (_jsx(RelatedProducts, { labels: { title: "Similar Auctions" }, renderGrid: () => (_jsx(MarketplaceAuctionGrid, { auctions: related, gridClassName: "grid grid-cols-2 gap-[var(--appkit-space-4)] sm:grid-cols-3 lg:grid-cols-4" })) })), _jsx(GroupedListingsCarousel, { groups: groups }), _jsx(RelatedItemsSection, { relatedItems: relatedItems, relatedByBrand: relatedByBrand, relatedByTags: relatedByTags, relatedByStore: relatedByStore, categoryLabel: categoryName || category || undefined, brandLabel: brand || undefined, storeLabel: storeName || undefined })] }));
206
+ return (_jsxs(Stack, { gap: "xl", children: [related.length > 0 && (_jsx(RelatedProducts, { labels: { title: "Similar Auctions" }, renderGrid: () => (_jsx(MarketplaceAuctionGrid, { auctions: related })) })), _jsx(GroupedListingsCarousel, { groups: groups }), _jsx(RelatedItemsSection, { relatedItems: relatedItems, relatedByBrand: relatedByBrand, relatedByTags: relatedByTags, relatedByStore: relatedByStore, categoryLabel: categoryName || category || undefined, brandLabel: brand || undefined, storeLabel: storeName || undefined })] }));
207
207
  } }), renderAuctionStoreReviews(storeId), onPlaceBid && (_jsx(AuctionBottomActions, { productId: String(product.id), currentBid: currentBid, startingBid: startingBid, tiers: bidIncrementTiers, minBidIncrementOverride: minBidIncrementOverride, currency: currency, isEnded: isEnded, auctionEndDate: endDate, buyNowPrice: buyNowPrice, bidCount: bidCount, tags: tags, onPlaceBid: onPlaceBid, onBuyNow: onBuyNow }))] })] }));
208
208
  }
@@ -13,10 +13,9 @@ export interface MarketplaceAuctionGridProps {
13
13
  selectable?: boolean;
14
14
  selectedIds?: string[];
15
15
  onSelectionChange?: (ids: string[]) => void;
16
- gridClassName?: string;
17
16
  emptyIcon?: ReactNode;
18
17
  labels?: MarketplaceAuctionGridLabels;
19
18
  cardLabels?: MarketplaceAuctionCardLabels;
20
19
  wishlistActions?: WishlistToggleActions;
21
20
  }
22
- export declare function MarketplaceAuctionGrid({ auctions, loading, skeletonCount, variant, selectable, selectedIds, onSelectionChange, gridClassName, emptyIcon, labels, cardLabels, wishlistActions, }: MarketplaceAuctionGridProps): import("react").JSX.Element;
21
+ export declare function MarketplaceAuctionGrid({ auctions, loading, skeletonCount, variant, selectable, selectedIds, onSelectionChange, emptyIcon, labels, cardLabels, wishlistActions, }: MarketplaceAuctionGridProps): import("react").JSX.Element;
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { Gavel } from "lucide-react";
4
- import { Div, Row, Stack, Text } from "../../../ui";
4
+ import { Div, Grid, Row, Stack, Text } from "../../../ui";
5
5
  import { MarketplaceAuctionCard, } from "./MarketplaceAuctionCard";
6
6
  const __P = {
7
7
  p3: "p-[var(--appkit-space-3)]",
@@ -20,7 +20,7 @@ function AuctionCardSkeleton({ variant = "grid", }) {
20
20
  }
21
21
  return (_jsxs(Div, { className: `${__O.hidden} animate-pulse`, rounded: "xl", surface: "subtle", children: [_jsx(Div, { surface: "skeleton", className: "aspect-square" }), _jsxs(Stack, { className: `${__P.p3}`, gap: "sm", children: [_jsx(Div, { surface: "skeleton", className: "h-4 w-3/4", rounded: "default" }), _jsx(Div, { surface: "skeleton", className: "h-3 w-1/3", rounded: "default" }), _jsx(Div, { surface: "skeleton", className: "h-5 w-1/2", rounded: "default" }), _jsxs(Row, { justify: "between", children: [_jsx(Div, { surface: "skeleton", className: "h-3 w-1/4", rounded: "default" }), _jsx(Div, { surface: "skeleton", className: "h-3 w-1/3", rounded: "default" })] }), _jsx(Row, { gap: "sm", children: _jsx(Div, { className: CLS_SKELETON_BTN }) })] })] }));
22
22
  }
23
- export function MarketplaceAuctionGrid({ auctions, loading = false, skeletonCount = 12, variant = "grid", selectable = false, selectedIds = [], onSelectionChange, gridClassName, emptyIcon, labels, cardLabels, wishlistActions, }) {
23
+ export function MarketplaceAuctionGrid({ auctions, loading = false, skeletonCount = 12, variant = "grid", selectable = false, selectedIds = [], onSelectionChange, emptyIcon, labels, cardLabels, wishlistActions, }) {
24
24
  const mergedLabels = { ...DEFAULT_GRID_LABELS, ...labels };
25
25
  const handleSelect = (id, selected) => {
26
26
  if (!onSelectionChange)
@@ -31,14 +31,15 @@ export function MarketplaceAuctionGrid({ auctions, loading = false, skeletonCoun
31
31
  }
32
32
  onSelectionChange(selectedIds.filter((selectedId) => selectedId !== id));
33
33
  };
34
- const containerClass = variant === "list"
35
- ? "flex flex-col gap-[var(--appkit-space-4)]"
36
- : `${gridClassName ?? "grid grid-cols-1 gap-[var(--appkit-space-6)] sm:grid-cols-2 xl:grid-cols-4"}`;
34
+ /* The container is either the canonical card ladder or a plain stack — the
35
+ caller no longer supplies a grid className, so a store's auctions tab and
36
+ /auctions can no longer drift apart in column count. */
37
+ const Container = ({ children }) => variant === "list" ? (_jsx(Stack, { gap: "md", children: children })) : (_jsx(Grid, { cols: "cards", gap: "lg", children: children }));
37
38
  if (loading) {
38
- return (_jsx(Div, { className: containerClass, children: Array.from({ length: skeletonCount }).map((_, index) => (_jsx(AuctionCardSkeleton, { variant: variant }, `auction-skeleton-${index}`))) }));
39
+ return (_jsx(Container, { children: Array.from({ length: skeletonCount }).map((_, index) => (_jsx(AuctionCardSkeleton, { variant: variant }, `auction-skeleton-${index}`))) }));
39
40
  }
40
41
  if (auctions.length === 0) {
41
42
  return (_jsxs(Stack, { justify: "center", padding: "y-6xl", align: "start", gap: "3", className: "text-left", children: [emptyIcon ?? _jsx(Gavel, { className: "h-16 w-16" }), _jsx(Text, { size: "xl", weight: "medium", color: "primary", children: mergedLabels.emptyTitle }), _jsx(Text, { size: "sm", color: "muted", children: mergedLabels.emptyDescription })] }));
42
43
  }
43
- return (_jsx(Div, { className: containerClass, children: auctions.map((auction) => (_jsx(MarketplaceAuctionCard, { product: auction, variant: variant, selectable: selectable, isSelected: selectedIds.includes(auction.id), onSelect: handleSelect, labels: cardLabels, wishlistActions: wishlistActions }, auction.id))) }));
44
+ return (_jsx(Container, { children: auctions.map((auction) => (_jsx(MarketplaceAuctionCard, { product: auction, variant: variant, selectable: selectable, isSelected: selectedIds.includes(auction.id), onSelect: handleSelect, labels: cardLabels, wishlistActions: wishlistActions }, auction.id))) }));
44
45
  }
@@ -2,6 +2,7 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { useEffect, useRef, useState, useTransition } from "react";
4
4
  import { useRouter } from "next/navigation";
5
+ import { useTranslations } from "next-intl";
5
6
  import { formatCurrency } from "../../../utils/number.formatter";
6
7
  import { isAuthError } from "../../../utils/auth-error";
7
8
  import { Button, CountdownDisplay, Div, LoginRequiredModal, Modal, Row, Span, Stack, Text } from "../../../ui";
@@ -14,16 +15,25 @@ import { AUCTION_BUYOUT_WINDOW_MINUTES } from "../../../_internal/shared/checkou
14
15
  import { BID_PRESET_MULTIPLIERS, isBuyNowAvailable, resolveMinBid, resolveMinBidIncrement, } from "../../../_internal/shared/features/auctions/config";
15
16
  import { normalizeError } from "../../../errors/normalize";
16
17
  import { FormErrorSummary } from "../../../ui/forms/FormErrorSummary";
18
+ import { toUserMessage } from "../../../errors/error-display-map";
17
19
  const __P = {
18
20
  p5: "p-[var(--appkit-space-5)]",
19
21
  };
20
- const BID_ERROR_DISPLAY = {
21
- BID_AUCTION_ENDED: "This auction has closed. No more bids are accepted.",
22
- BID_AMOUNT_TOO_LOW: "Your bid must exceed the current winning bid.",
23
- BID_INCREMENT_TOO_LOW: "Your bid does not meet the minimum increment requirement.",
24
- BID_SELF_BID: "You cannot bid on your own listing.",
25
- BID_USER_BANNED: "Your account is currently restricted from bidding. Contact support.",
26
- };
22
+ // The local `BID_ERROR_DISPLAY` map that used to live here is gone.
23
+ //
24
+ // It covered 5 of the ~78 codes the server can emit, and — the actual defect —
25
+ // fell back to `result.error`, the server's own message. `result.error` is
26
+ // always a string, so the safe literal after it was unreachable, and a 500
27
+ // therefore printed its raw internals into the bid field: this component is
28
+ // where `Cannot find module '../providers/db-firebase' Require stack: -
29
+ // /var/task/.next/server/chunks/...` was shown to users.
30
+ //
31
+ // Its five strings already existed as `errors.codes.BID_*` in messages/en.json,
32
+ // so nothing was lost by deleting it. `toUserMessage` resolves every code and
33
+ // terminates in `errors.codes.UNKNOWN` — never in server text.
34
+ /** Feature-specific last resort. Authored copy, never derived from an error. */
35
+ const BID_FALLBACK = "Failed to place bid. Please try again.";
36
+ const BUY_NOW_FALLBACK = "Buy Now failed. Please try again.";
27
37
  export function PlaceBidFormClient({ productId, currentBid: ssrCurrentBid, startingBid, tiers, minBidIncrementOverride, currency, isEnded, auctionEndDate, buyNowPrice, bidCount: ssrBidCount, tags = [], onPlaceBid, onBuyNow, }) {
28
38
  const router = useRouter();
29
39
  // Live-updates while the bid card is open — reflects other bidders'
@@ -47,6 +57,10 @@ export function PlaceBidFormClient({ productId, currentBid: ssrCurrentBid, start
47
57
  const [isPending, startTransition] = useTransition();
48
58
  const [isBuyNowPending, startBuyNowTransition] = useTransition();
49
59
  const [success, setSuccess] = useState(false);
60
+ // Root translator (no namespace) — `toUserMessage` passes fully-qualified
61
+ // `errors.codes.*` keys, and resolves them itself, so a missing key degrades
62
+ // to generic copy rather than throwing MISSING_MESSAGE at render time.
63
+ const t = useTranslations();
50
64
  const [showLoginModal, setShowLoginModal] = useState(false);
51
65
  /**
52
66
  * Buy Now has no field to hang an error off — it is a standalone button, not
@@ -141,16 +155,17 @@ export function PlaceBidFormClient({ productId, currentBid: ssrCurrentBid, start
141
155
  setShowLoginModal(true);
142
156
  }
143
157
  else {
144
- setFieldError("bidAmount", err instanceof Error ? err.message : "Failed to place bid. Please try again.");
158
+ // `err.message` deliberately NOT used — a thrown error here can carry
159
+ // server internals just as an envelope can.
160
+ setFieldError("bidAmount", BID_FALLBACK);
145
161
  }
146
162
  }
147
163
  }
148
164
  function bidErrorMessage(result) {
149
165
  if (result.ok)
150
166
  return null;
151
- return ((result.code && BID_ERROR_DISPLAY[result.code]) ??
152
- result.error ??
153
- "Failed to place bid. Please try again.");
167
+ // No `?? result.error` tail. That is what leaked the require stack.
168
+ return toUserMessage(result.code, t, { fallback: BID_FALLBACK });
154
169
  }
155
170
  /**
156
171
  * Show a Buy Now failure.
@@ -164,14 +179,16 @@ export function PlaceBidFormClient({ productId, currentBid: ssrCurrentBid, start
164
179
  * `place_bids` soft ban. A login prompt for those tells the buyer to fix
165
180
  * something that is not the problem.
166
181
  */
167
- function reportBuyNowFailure(code, message) {
182
+ function reportBuyNowFailure(code, _message) {
168
183
  if (code === "UNAUTHENTICATED") {
169
184
  setShowLoginModal(true);
170
185
  return;
171
186
  }
172
- setBuyNowError((code && BID_ERROR_DISPLAY[code]) ??
173
- message ??
174
- "Buy Now failed. Please try again.");
187
+ // `_message` is the SERVER's message and is intentionally ignored — it was
188
+ // the `?? message` tail here, same defect as bidErrorMessage above. Kept in
189
+ // the signature so callers need no change and the omission is visible.
190
+ void _message;
191
+ setBuyNowError(toUserMessage(code, t, { fallback: BUY_NOW_FALLBACK }));
175
192
  }
176
193
  function handleBuyNow() {
177
194
  if (!onBuyNow)
@@ -1,8 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { BeforeAfterSlider } from "./BeforeAfterSlider";
3
- import { Div, Stack, Text } from "../../../ui";
3
+ import { Div, Grid, Stack, Text } from "../../../ui";
4
4
  export function BeforeAfterGallery({ items, className = "", }) {
5
5
  if (items.length === 0)
6
6
  return null;
7
- return (_jsx(Div, { layout: "grid", gap: "6", className: `grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 ${className}`, children: items.map((item) => (_jsxs(Stack, { gap: "sm", children: [_jsx(BeforeAfterSlider, { item: item }), (item.title || item.description) && (_jsxs(Div, { className: "text-center", children: [item.title && (_jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: item.title })), item.description && (_jsx(Text, { className: "mt-0.5", color: "muted", size: "xs", children: item.description }))] }))] }, item.id))) }));
7
+ return (_jsx(Grid, { cols: "cards", gap: "lg", className: className, children: items.map((item) => (_jsxs(Stack, { gap: "sm", children: [_jsx(BeforeAfterSlider, { item: item }), (item.title || item.description) && (_jsxs(Div, { className: "text-center", children: [item.title && (_jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: item.title })), item.description && (_jsx(Text, { className: "mt-0.5", color: "muted", size: "xs", children: item.description }))] }))] }, item.id))) }));
8
8
  }
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useState, useCallback, useMemo } from "react";
4
4
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
5
5
  import { useBlogPosts } from "../hooks/useBlog";
6
- import { Div, ListingFilterDrawer, ListingToolbar, Pagination, Row, Stack, Text, StickyToolbar } from "../../../ui";
6
+ import { Div, Grid, ListingFilterDrawer, ListingToolbar, Pagination, Row, Stack, Text, StickyToolbar } from "../../../ui";
7
7
  import { ROUTES } from "../../../next";
8
8
  import { BlogCard } from "./BlogListView";
9
9
  import { BlogFilters, BLOG_PUBLIC_SORT_OPTIONS } from "./BlogFilters";
@@ -19,7 +19,7 @@ const __O = {
19
19
  function renderBlogGrid(props) {
20
20
  const { isLoading, posts, view } = props;
21
21
  if (isLoading) {
22
- return (_jsx(Div, { layout: "grid", gap: "6", className: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3", children: Array.from({ length: 6 }).map((_, i) => (_jsxs(Div, { className: `${__O.hidden} animate-pulse`, border: "subtle", rounded: "xl", children: [_jsx(Div, { className: "aspect-video", surface: "subtle" }), _jsxs(Stack, { className: `${__P.p5}`, gap: "sm", children: [_jsx(Div, { className: "h-3 w-1/4", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-4 w-3/4", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-3 w-full", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-3 w-2/3", surface: "subtle", rounded: "default" })] })] }, i))) }));
22
+ return (_jsx(Grid, { cols: "cards", gap: "lg", children: Array.from({ length: 6 }).map((_, i) => (_jsxs(Div, { className: `${__O.hidden} animate-pulse`, border: "subtle", rounded: "xl", children: [_jsx(Div, { className: "aspect-video", surface: "subtle" }), _jsxs(Stack, { className: `${__P.p5}`, gap: "sm", children: [_jsx(Div, { className: "h-3 w-1/4", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-4 w-3/4", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-3 w-full", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-3 w-2/3", surface: "subtle", rounded: "default" })] })] }, i))) }));
23
23
  }
24
24
  if (posts.length === 0) {
25
25
  return _jsx(Text, { paddingY: "3xl", color: "muted", size: "sm", align: "start", children: "No posts found." });
@@ -27,7 +27,7 @@ function renderBlogGrid(props) {
27
27
  if (view === "list") {
28
28
  return (_jsx(Stack, { className: "divide-y divide-zinc-100 divide-[var(--appkit-color-border-subtle)]", border: "subtle", rounded: "xl", children: posts.map((post) => _jsx(BlogCard, { post: post, href: String(ROUTES.BLOG.ARTICLE(post.slug)) }, post.id)) }));
29
29
  }
30
- return (_jsx(Div, { layout: "grid", gap: "6", className: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3", children: posts.map((post) => _jsx(BlogCard, { post: post, href: String(ROUTES.BLOG.ARTICLE(post.slug)) }, post.id)) }));
30
+ return (_jsx(Grid, { cols: "cards", gap: "lg", children: posts.map((post) => _jsx(BlogCard, { post: post, href: String(ROUTES.BLOG.ARTICLE(post.slug)) }, post.id)) }));
31
31
  }
32
32
  function renderBlogFilterDrawer(props) {
33
33
  const { filterOpen, setFilterOpen, activeFilterCount, clearFilters, applyFilters, pendingTable } = props;