@mohasinac/appkit 3.3.6 → 3.4.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 (133) hide show
  1. package/dist/_internal/client/features/classified/ClassifiedDetailView.js +2 -1
  2. package/dist/_internal/client/features/digital-code/DigitalCodeDetailView.js +2 -1
  3. package/dist/_internal/client/features/live/LiveItemDetailView.js +2 -1
  4. package/dist/_internal/client/features/lottery/LotteryAdminEditView.js +1 -1
  5. package/dist/_internal/client/features/lottery/LotteryDetailView.js +2 -1
  6. package/dist/_internal/client/features/lottery/LotteryListView.js +2 -1
  7. package/dist/_internal/client/features/lottery/PrizeDrawLotteryDetailView.js +2 -1
  8. package/dist/_internal/server/features/checkout/actions.js +0 -1
  9. package/dist/_internal/server/features/lottery/actions.js +0 -2
  10. package/dist/_internal/server/features/reviews/actions.js +1 -1
  11. package/dist/features/about/components/FAQPageView.js +0 -2
  12. package/dist/features/about/components/FeesView.js +13 -1
  13. package/dist/features/about/components/HowCheckoutWorksView.js +17 -3
  14. package/dist/features/about/components/HowEmiWorksView.d.ts +3 -0
  15. package/dist/features/about/components/HowEmiWorksView.js +117 -0
  16. package/dist/features/about/components/PublicProfileView.js +1 -4
  17. package/dist/features/about/index.d.ts +2 -0
  18. package/dist/features/about/index.js +1 -0
  19. package/dist/features/account/components/BecomeSellerView.d.ts +1 -1
  20. package/dist/features/account/components/BecomeSellerView.js +1 -1
  21. package/dist/features/account/components/NotificationBell.js +1 -2
  22. package/dist/features/account/components/UserSupportView.js +0 -1
  23. package/dist/features/admin/components/AdminAdsView.js +3 -3
  24. package/dist/features/admin/components/AdminBlogEditorView.js +2 -2
  25. package/dist/features/admin/components/AdminBundleEditorView.js +0 -4
  26. package/dist/features/admin/components/AdminCarouselEditorView.js +3 -3
  27. package/dist/features/admin/components/AdminCarouselView.js +3 -2
  28. package/dist/features/admin/components/AdminGuideHubView.js +2 -2
  29. package/dist/features/admin/components/AdminMediaView.js +2 -4
  30. package/dist/features/admin/components/AdminUserEditorView.d.ts +1 -1
  31. package/dist/features/admin/components/AdminUserEditorView.js +1 -1
  32. package/dist/features/admin/components/QuickActionsPanel.js +2 -2
  33. package/dist/features/auctions/components/AuctionDetailPageView.js +1 -1
  34. package/dist/features/blog/components/BlogFeaturedCard.js +2 -1
  35. package/dist/features/blog/components/BlogIndexListing.js +1 -1
  36. package/dist/features/blog/components/BlogListView.js +2 -1
  37. package/dist/features/blog/components/BlogPostForm.js +1 -1
  38. package/dist/features/blog/components/BlogPostView.js +2 -1
  39. package/dist/features/cart/components/CartDrawer.js +2 -1
  40. package/dist/features/categories/components/BrandDetailPageView.js +2 -1
  41. package/dist/features/categories/components/BundleCollage.js +2 -3
  42. package/dist/features/categories/components/CategoriesIndexListing.js +1 -5
  43. package/dist/features/categories/components/CategoryDetailPageView.js +2 -2
  44. package/dist/features/categories/components/CategoryGrid.js +2 -1
  45. package/dist/features/categories/components/CategoryProductsView.d.ts +1 -1
  46. package/dist/features/categories/components/CategoryProductsView.js +1 -1
  47. package/dist/features/categories/components/ConcernCard.js +2 -1
  48. package/dist/features/collections/components/CollectionCard.js +2 -1
  49. package/dist/features/consultation/components/ConsultationForm.js +0 -1
  50. package/dist/features/contact/components/ContactInfoSidebar.js +2 -2
  51. package/dist/features/copilot/components/AdminCopilotView.js +0 -9
  52. package/dist/features/events/components/EventFormDrawer.d.ts +1 -1
  53. package/dist/features/events/components/EventFormDrawer.js +1 -1
  54. package/dist/features/events/components/EventPollWidget.js +2 -2
  55. package/dist/features/events/components/EventsIndexListing.js +1 -1
  56. package/dist/features/faq/actions/faq-actions.d.ts +2 -10
  57. package/dist/features/faq/actions/faq-actions.js +0 -4
  58. package/dist/features/homepage/components/AdSlot.js +2 -1
  59. package/dist/features/homepage/components/CustomCardsSection.js +2 -3
  60. package/dist/features/homepage/components/FeaturedBundlesSection.js +2 -3
  61. package/dist/features/homepage/components/GoogleReviewsSection.js +4 -3
  62. package/dist/features/homepage/components/HeroBanner.js +0 -1
  63. package/dist/features/homepage/components/SectionCarousel.d.ts +1 -1
  64. package/dist/features/homepage/components/SectionCarousel.js +1 -2
  65. package/dist/features/homepage/components/SocialFeedSection.js +2 -5
  66. package/dist/features/homepage/components/SocialPostCard.js +4 -3
  67. package/dist/features/media/MediaPickerModal.js +2 -3
  68. package/dist/features/media/upload/MediaUploadList.js +2 -2
  69. package/dist/features/orders/components/OrdersList.js +2 -1
  70. package/dist/features/pre-orders/components/PreOrderDetailPageView.js +1 -0
  71. package/dist/features/products/api/route.js +1 -2
  72. package/dist/features/products/components/CompareOverlay.js +2 -3
  73. package/dist/features/products/components/CustomSectionTabContent.js +2 -2
  74. package/dist/features/products/components/FeatureBadge.js +0 -6
  75. package/dist/features/products/components/GroupSettingsPanel.js +2 -3
  76. package/dist/features/products/components/MarketplaceBundleCard.js +2 -1
  77. package/dist/features/products/components/MarketplacePrizeDrawCard.js +2 -3
  78. package/dist/features/products/components/PrizeDrawCollage.js +2 -3
  79. package/dist/features/products/components/PrizeDrawEntryActions.d.ts +1 -1
  80. package/dist/features/products/components/PrizeDrawEntryActions.js +3 -3
  81. package/dist/features/products/components/PrizeRevealModal.js +3 -4
  82. package/dist/features/products/components/ProductDetailActions.d.ts +1 -1
  83. package/dist/features/products/components/ProductDetailActions.js +1 -1
  84. package/dist/features/products/components/ProductDetailPageView.d.ts +3 -1
  85. package/dist/features/products/components/ProductDetailPageView.js +3 -2
  86. package/dist/features/products/components/ProductFeatureBadges.d.ts +7 -1
  87. package/dist/features/products/components/ProductFeatureBadges.js +16 -3
  88. package/dist/features/products/components/ProductGradingTab.js +2 -2
  89. package/dist/features/products/components/ProductsIndexPageView.js +2 -2
  90. package/dist/features/products/components/SublistingCarouselSection.js +3 -4
  91. package/dist/features/promotions/components/CouponsIndexListing.d.ts +1 -1
  92. package/dist/features/promotions/components/CouponsIndexListing.js +1 -1
  93. package/dist/features/reviews/components/ReviewDetailShell.js +4 -3
  94. package/dist/features/reviews/components/ReviewModal.js +2 -1
  95. package/dist/features/reviews/components/ReviewsIndexListing.js +1 -1
  96. package/dist/features/reviews/components/ReviewsList.js +2 -1
  97. package/dist/features/scams/components/ScamAwarenessModal.js +2 -2
  98. package/dist/features/search/components/SearchView.d.ts +1 -1
  99. package/dist/features/search/components/SearchView.js +1 -1
  100. package/dist/features/seller/components/SellerAddressesView.js +1 -5
  101. package/dist/features/seller/components/SellerPreOrdersView.js +3 -2
  102. package/dist/features/shell/StepForm.js +1 -1
  103. package/dist/features/stores/components/InteractiveStoreCard.js +1 -2
  104. package/dist/features/stores/components/StoreAboutView.js +2 -2
  105. package/dist/features/stores/components/StoreCapabilitiesGuideView.js +2 -2
  106. package/dist/features/stores/components/StoreFinanceGuideView.js +2 -2
  107. package/dist/features/stores/components/StoreGuideHubView.js +2 -2
  108. package/dist/features/stores/components/StoreListingsGuideView.js +2 -2
  109. package/dist/features/stores/components/StoreNavTabs.js +2 -2
  110. package/dist/features/stores/components/StoreOrdersGuideView.js +2 -2
  111. package/dist/features/stores/components/StoreSettingsGuideView.js +3 -3
  112. package/dist/features/wishlist/components/WishlistView.js +0 -7
  113. package/dist/index.d.ts +2 -0
  114. package/dist/index.js +3 -0
  115. package/dist/next/routing/route-map.d.ts +2 -0
  116. package/dist/next/routing/route-map.js +1 -0
  117. package/dist/seed/factories/product.factory.js +0 -32
  118. package/dist/seed/faq-seed-data.js +23 -4
  119. package/dist/styles.css +1 -1
  120. package/dist/tailwind-utilities.css +1 -1
  121. package/dist/tokens/index.js +6 -458
  122. package/dist/ui/DataTable.js +0 -21
  123. package/dist/ui/components/Anchor.d.ts +3 -1
  124. package/dist/ui/components/Anchor.js +1 -0
  125. package/dist/ui/components/Breadcrumb.js +2 -1
  126. package/dist/ui/components/FormActionBar.js +2 -1
  127. package/dist/ui/components/ListingLayout.d.ts +1 -1
  128. package/dist/ui/components/ListingLayout.js +1 -1
  129. package/dist/ui/components/Motion.js +1 -1
  130. package/dist/ui/components/QuickCreateModal.js +2 -1
  131. package/dist/ui/components/SlottedListingView.d.ts +1 -1
  132. package/dist/ui/components/SlottedListingView.js +1 -2
  133. package/package.json +1 -1
@@ -5,7 +5,6 @@ import { Button, Row, Select } from "../../../ui";
5
5
  import { Form } from "../../../ui/components/Form";
6
6
  import { FieldInput } from "../../../ui/forms/FieldInput";
7
7
  import { FieldTextarea } from "../../../ui/forms/FieldTextarea";
8
- const CLS_INPUT = "w-full rounded-md border border-neutral-300 px-3 py-2 text-sm";
9
8
  const TIME_SLOTS = [
10
9
  "9:00 AM",
11
10
  "10:00 AM",
@@ -1,8 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Div, Heading, Row, Span, Stack, Text } from "../../../ui";
2
+ import { Anchor, Div, Heading, Row, Span, Stack, Text } from "../../../ui";
3
3
  const __P = {
4
4
  p4: "p-4",
5
5
  };
6
6
  export function ContactInfoSidebar({ infoItems, labels = {}, renderActions, }) {
7
- return (_jsxs(Stack, { as: "aside", gap: "lg", children: [_jsxs(Div, { children: [_jsx(Heading, { level: 3, className: "mb-4", children: labels.title ?? "Contact Information" }), _jsx(Stack, { gap: "md", children: infoItems.map((item) => (_jsxs(Row, { align: "start", gap: "3", children: [_jsx(Span, { size: "xl", className: "flex-shrink-0 mt-0.5", children: item.icon }), _jsxs(Div, { children: [_jsx(Text, { className: "tracking-wide mb-0.5", color: "muted", size: "xs", weight: "medium", transform: "uppercase", children: item.label }), item.href ? (_jsx("a", { href: item.href, className: "text-sm text-primary-800 dark:text-primary-300 hover:underline", children: item.value })) : (_jsx(Text, { size: "sm", children: item.value }))] })] }, item.label))) })] }), (labels.businessHoursLabel || labels.responseTimeLabel) && (_jsxs(Stack, { gap: "sm", border: "default", surface: "muted", className: __P.p4, rounded: "xl", children: [labels.businessHoursLabel && labels.businessHoursValue && (_jsxs(Div, { children: [_jsx(Text, { className: "tracking-wide", color: "muted", size: "xs", weight: "semibold", transform: "uppercase", children: labels.businessHoursLabel }), _jsx(Text, { className: "mt-0.5", size: "sm", children: labels.businessHoursValue })] })), labels.responseTimeLabel && labels.responseTimeValue && (_jsxs(Div, { children: [_jsx(Text, { className: "tracking-wide", color: "muted", size: "xs", weight: "semibold", transform: "uppercase", children: labels.responseTimeLabel }), _jsx(Text, { className: "mt-0.5", size: "sm", children: labels.responseTimeValue })] }))] })), renderActions?.()] }));
7
+ return (_jsxs(Stack, { as: "aside", gap: "lg", children: [_jsxs(Div, { children: [_jsx(Heading, { level: 3, className: "mb-4", children: labels.title ?? "Contact Information" }), _jsx(Stack, { gap: "md", children: infoItems.map((item) => (_jsxs(Row, { align: "start", gap: "3", children: [_jsx(Span, { size: "xl", className: "flex-shrink-0 mt-0.5", children: item.icon }), _jsxs(Div, { children: [_jsx(Text, { className: "tracking-wide mb-0.5", color: "muted", size: "xs", weight: "medium", transform: "uppercase", children: item.label }), item.href ? (_jsx(Anchor, { href: item.href, size: "sm", underline: "hover", children: item.value })) : (_jsx(Text, { size: "sm", children: item.value }))] })] }, item.label))) })] }), (labels.businessHoursLabel || labels.responseTimeLabel) && (_jsxs(Stack, { gap: "sm", border: "default", surface: "muted", className: __P.p4, rounded: "xl", children: [labels.businessHoursLabel && labels.businessHoursValue && (_jsxs(Div, { children: [_jsx(Text, { className: "tracking-wide", color: "muted", size: "xs", weight: "semibold", transform: "uppercase", children: labels.businessHoursLabel }), _jsx(Text, { className: "mt-0.5", size: "sm", children: labels.businessHoursValue })] })), labels.responseTimeLabel && labels.responseTimeValue && (_jsxs(Div, { children: [_jsx(Text, { className: "tracking-wide", color: "muted", size: "xs", weight: "semibold", transform: "uppercase", children: labels.responseTimeLabel }), _jsx(Text, { className: "mt-0.5", size: "sm", children: labels.responseTimeValue })] }))] })), renderActions?.()] }));
8
8
  }
@@ -18,15 +18,6 @@ function MessageBubble({ msg }) {
18
18
  const isUser = msg.role === "user";
19
19
  return (_jsx(Div, { layout: "flex", justify: isUser ? "end" : "start", children: _jsxs(Div, { textSize: "sm", className: `max-w-[80%] py-[0.625rem] ${isUser ? "bg-primary text-white" : "bg-[var(--appkit-color-surface)] dark:bg-[var(--appkit-color-surface-elevated)] text-[var(--appkit-color-text)]"}`, padding: "x-md", style: { borderRadius: isUser ? "1.5rem 0.125rem 1.5rem 1.5rem" : "0.125rem 1.5rem 1.5rem 1.5rem" }, children: [_jsx(Text, { className: "whitespace-pre-wrap leading-relaxed", size: "sm", children: msg.content }), msg.durationMs ? (_jsxs(Span, { size: "xs", className: "mt-1 block opacity-60", children: [(msg.durationMs / 1000).toFixed(1), "s"] })) : null] }) }));
20
20
  }
21
- function renderCopilotChatPanel(props) {
22
- const { messages, conversationId, isLoading, error, input, setInput, labels, messagesEndRef, handleSubmit } = props;
23
- return (_jsxs(Stack, { className: "border border-neutral-200 h-[calc(100vh-300px)]", surface: "default", rounded: "xl", children: [_jsx(Div, { border: "bottom", className: __P.p3, children: _jsxs(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "xs", weight: "medium", children: [labels?.conversationId ?? LBL_CONVERSATION_ID, ": ", conversationId] }) }), _jsxs(Stack, { className: `flex-1 ${__O.yAuto} ${__P.p4}`, gap: "md", children: [messages.length === 0 ? (_jsxs(Stack, { justify: "center", className: "h-full text-center", align: "center", children: [_jsx(Text, { weight: "medium", children: labels?.noMessages ?? "No messages yet" }), _jsx(Text, { variant: "secondary", className: "mt-1", size: "sm", children: labels?.noMessagesDesc ?? "Ask anything to get started" })] })) : null, messages.map((msg) => _jsx(MessageBubble, { msg: msg }, msg.id)), isLoading ? (_jsx(Row, { justify: "start", children: _jsx(Div, { paddingY: "y-xs-tall", surface: "muted", className: "", padding: "x-md", style: { borderRadius: "0.125rem 1.5rem 1.5rem 1.5rem" }, children: _jsx(Span, { size: "sm", className: "opacity-60 animate-pulse", children: "Thinking..." }) }) })) : null, error ? _jsx(Text, { className: CLS_ERROR_TEXT, children: labels?.errorLabel ?? "An error occurred. Please try again." }) : null, _jsx(Div, { ref: messagesEndRef })] }), _jsx(Div, { border: "top", className: __P.p3, children: _jsxs(Form, { onSubmit: handleSubmit, gap: "xs", className: "flex", children: [_jsx(Input, { type: "text", bare: true, value: input, onChange: (e) => setInput(e.target.value), placeholder: labels?.inputPlaceholder ?? "Ask anything...", disabled: isLoading, className: "flex-1 h-10" }), _jsx(Button, { type: "submit", variant: "primary", disabled: isLoading || !input.trim(), className: "shrink-0", children: isLoading ? labels?.sendingButton ?? "Sending..." : labels?.sendButton ?? "Send" })] }) })] }));
24
- }
25
- function renderCopilotHistoryPanel(props) {
26
- const { historyQuery, conversationInput, setConversationInput, loadConversation, labels } = props;
27
- return (_jsxs(Stack, { surface: "card", padding: "sm", gap: "3", children: [_jsx(Heading, { level: 3, size: "sm", weight: "semibold", children: labels?.historyTitle ?? "Conversation History" }), _jsxs(Form, { spacing: "sm", onSubmit: (e) => { e.preventDefault(); if (!conversationInput.trim())
28
- return; loadConversation(conversationInput.trim()); }, children: [_jsx(Input, { label: labels?.conversationId ?? LBL_CONVERSATION_ID, value: conversationInput, onChange: (e) => setConversationInput(e.target.value), placeholder: "conv_..." }), _jsx(Button, { type: "submit", variant: "secondary", size: "sm", className: "w-full", children: labels?.loadConversation ?? "Load conversation" })] }), historyQuery.error ? (_jsx(Alert, { variant: "warning", title: "History unavailable", children: historyQuery.error instanceof Error ? historyQuery.error.message : "Could not load history" })) : null, _jsxs(Stack, { className: `max-h-72 ${__O.yAuto}`, gap: "sm", children: [(historyQuery.data?.messages ?? []).map((log, index) => (_jsxs(Div, { className: "border border-neutral-200", rounded: "lg", padding: "xs", children: [_jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "xs", weight: "medium", children: new Date(log.createdAt).toLocaleString() }), _jsxs(Text, { className: `mt-1`, truncate: 3, size: "sm", children: ["Q: ", log.prompt] }), _jsxs(Text, { className: `mt-1`, color: "muted", truncate: 3, size: "sm", children: ["A: ", log.response] })] }, `${log.createdAt}-${index}`))), !historyQuery.isLoading && (historyQuery.data?.messages?.length ?? 0) === 0 ? (_jsx(Text, { variant: "secondary", size: "sm", children: "No server history found for this conversation yet." })) : null] })] }));
29
- }
30
21
  export function AdminCopilotView({ endpoint, historyEndpoint = "/api/copilot/history", renderHeader, labels = {}, className = "", }) {
31
22
  const [input, setInput] = useState("");
32
23
  const [conversationInput, setConversationInput] = useState("");
@@ -19,4 +19,4 @@ export interface EventFormDrawerProps {
19
19
  renderFooter?: () => React.ReactNode;
20
20
  className?: string;
21
21
  }
22
- export declare function EventFormDrawer({ isOpen, onClose, renderHeader, renderBaseFields, renderMediaFields, renderTypeConfig, renderTypeSelector, renderFooter, className, }: EventFormDrawerProps): React.JSX.Element | null;
22
+ export declare function EventFormDrawer({ isOpen, onClose: _onClose, renderHeader, renderBaseFields, renderMediaFields, renderTypeConfig, renderTypeSelector, renderFooter, className, }: EventFormDrawerProps): React.JSX.Element | null;
@@ -1,6 +1,6 @@
1
1
  import { jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Stack } from "../../../ui";
3
- export function EventFormDrawer({ isOpen, onClose, renderHeader, renderBaseFields, renderMediaFields, renderTypeConfig, renderTypeSelector, renderFooter, className = "", }) {
3
+ export function EventFormDrawer({ isOpen, onClose: _onClose, renderHeader, renderBaseFields, renderMediaFields, renderTypeConfig, renderTypeSelector, renderFooter, className = "", }) {
4
4
  if (!isOpen)
5
5
  return null;
6
6
  return (_jsxs(Stack, { className: `${className}`, gap: "lg", children: [renderHeader?.(), renderTypeSelector?.(), renderBaseFields?.(), renderMediaFields?.(), renderTypeConfig?.(), renderFooter?.()] }));
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useState } from "react";
4
4
  import { useAuth } from "../../../react/contexts/SessionContext";
5
5
  import { ROUTES } from "../../../next";
6
- import { Div, LoginRequiredModal, Span, Stack, Text } from "../../../ui";
6
+ import { Div, LoginRequiredModal, Span, Stack, Text, TextLink } from "../../../ui";
7
7
  import { normalizeError } from "../../../errors/normalize";
8
8
  const CLS_THANKS_BOX = "rounded-xl border border-success dark:border-success bg-success-surface px-6 py-8 text-center space-y-2";
9
9
  export function EventPollWidget({ eventId, pollConfig, eventStatus, totalEntries, entriesEndpoint, className = "", }) {
@@ -62,7 +62,7 @@ export function EventPollWidget({ eventId, pollConfig, eventStatus, totalEntries
62
62
  return (_jsxs(Stack, { paddingX: "x-5", className: className, gap: "3", rounded: "xl", padding: "y-md", border: "default", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "This poll has closed." }), totalEntries !== undefined && (_jsxs(Text, { size: "sm", color: "faint", children: [totalEntries.toLocaleString(), " vote", totalEntries !== 1 ? "s" : "", " cast"] })), _jsx(Stack, { gap: "sm", children: pollConfig.options.map((opt) => (_jsx(Div, { textSize: "sm", paddingY: "y-xs-tall", color: "muted", border: "subtle", padding: "x-md", rounded: "lg", children: opt.label }, opt.id))) })] }));
63
63
  }
64
64
  if (pollConfig.requireLogin && !user) {
65
- return (_jsxs(Stack, { className: `text-center ${className}`, gap: "3", rounded: "xl", paddingY: "y-xl", paddingX: "x-lg", border: "default", children: [_jsx(Text, { weight: "semibold", color: "primary", children: "Login to vote" }), _jsx(Text, { size: "sm", color: "muted", children: "You need an account to participate in this poll." }), _jsx("a", { href: String(ROUTES.AUTH.LOGIN), className: "inline-block rounded-xl bg-primary px-6 py-2.5 text-sm font-semibold text-white hover:bg-primary-600", children: "Log In" })] }));
65
+ return (_jsxs(Stack, { className: `text-center ${className}`, gap: "3", rounded: "xl", paddingY: "y-xl", paddingX: "x-lg", border: "default", children: [_jsx(Text, { weight: "semibold", color: "primary", children: "Login to vote" }), _jsx(Text, { size: "sm", color: "muted", children: "You need an account to participate in this poll." }), _jsx(TextLink, { variant: "bare", href: String(ROUTES.AUTH.LOGIN), rounded: "xl", size: "sm", weight: "semibold", className: "inline-block bg-primary px-6 py-2.5 text-white hover:bg-primary-600", children: "Log In" })] }));
66
66
  }
67
67
  if (isSubmitted) {
68
68
  return (_jsxs(Div, { className: `${CLS_THANKS_BOX} ${className}`, children: [_jsx(Text, { className: "text-success", weight: "semibold", children: "Vote recorded!" }), _jsx(Text, { size: "sm", color: "muted", children: "Thanks for participating." })] }));
@@ -116,7 +116,7 @@ export function EventsIndexListing({ initialData }) {
116
116
  sort: table.get(TABLE_KEYS.SORT) || sortBy(EVENT_FIELDS.STARTS_AT, "ASC"),
117
117
  filters: filterParts.length > 0 ? sieveAnd(...filterParts) : undefined,
118
118
  };
119
- const { events, total, totalPages, isLoading } = useEvents(params, { initialData });
119
+ const { events, totalPages, isLoading } = useEvents(params, { initialData });
120
120
  const currentPage = table.getNumber(TABLE_KEYS.PAGE, 1);
121
121
  const commitSearch = useCallback(() => {
122
122
  table.set(TABLE_KEYS.QUERY, searchInput.trim());
@@ -161,17 +161,10 @@ export declare const faqUpdateSchema: z.ZodObject<{
161
161
  }>;
162
162
  export type FaqCreateInput = z.infer<typeof faqCreateSchema>;
163
163
  export type FaqUpdateInput = z.infer<typeof faqUpdateSchema>;
164
- declare const voteSchema: z.ZodObject<{
165
- faqId: z.ZodString;
166
- vote: z.ZodEnum<["helpful", "not-helpful"]>;
167
- }, "strip", z.ZodTypeAny, {
168
- faqId: string;
169
- vote: "helpful" | "not-helpful";
170
- }, {
164
+ export interface VoteFaqActionInput {
171
165
  faqId: string;
172
166
  vote: "helpful" | "not-helpful";
173
- }>;
174
- export type VoteFaqActionInput = z.infer<typeof voteSchema>;
167
+ }
175
168
  export interface VoteFaqActionResult {
176
169
  helpful: number;
177
170
  notHelpful: number;
@@ -188,4 +181,3 @@ export declare function listFaqs(params?: {
188
181
  }): Promise<FirebaseSieveResult<FAQDocument>>;
189
182
  export declare function listPublicFaqs(category?: string, limit?: number): Promise<FAQDocument[]>;
190
183
  export declare function getFaqById(id: string): Promise<FAQDocument | null>;
191
- export {};
@@ -30,10 +30,6 @@ export const faqCreateSchema = faqBaseSchema.refine((data) => {
30
30
  return matches.length <= 10;
31
31
  }, { message: "Too many template variables (max 10)" });
32
32
  export const faqUpdateSchema = faqBaseSchema.partial();
33
- const voteSchema = z.object({
34
- faqId: z.string().min(1),
35
- vote: z.enum(["helpful", "not-helpful"]),
36
- });
37
33
  // --- Actions --------------------------------------------------------------
38
34
  export async function voteFaq(input) {
39
35
  const faq = await faqsRepository.findById(input.faqId);
@@ -5,6 +5,7 @@ import { useEffect, useRef, useState } from "react";
5
5
  import { getAdSlot, isAdSlotRenderable } from "../ad-registry";
6
6
  import { useActiveAd } from "../hooks/useActiveAd";
7
7
  import { Anchor, Div, Span, Text } from "../../../ui";
8
+ import { MediaImage } from "../../media/MediaImage";
8
9
  // ---------------------------------------------------------------------------
9
10
  // Sub-renderers
10
11
  // ---------------------------------------------------------------------------
@@ -33,7 +34,7 @@ function ManualAdBanner({ ad }) {
33
34
  const { creative } = ad;
34
35
  if (!creative)
35
36
  return null;
36
- return (_jsxs(Anchor, { href: creative.ctaHref || "#", target: creative.ctaHref ? "_blank" : undefined, rel: "noopener noreferrer", underline: "none", layout: "flex", gap: "lg", rounded: "lg", padding: "inline", hoverSurface: "subtle", className: "w-full overflow-hidden bg-[var(--appkit-color-surface)]", children: [creative.imageUrl ? (_jsx("img", { src: creative.imageUrl, alt: creative.title || "Advertisement", className: "h-14 w-14 rounded object-cover flex-shrink-0", loading: "lazy" })) : null, _jsxs(Div, { className: "flex-1 min-w-0", children: [creative.title ? (_jsx(Text, { className: "truncate", color: "primary", size: "sm", weight: "semibold", children: creative.title })) : null, creative.body ? (_jsx(Text, { className: "truncate", color: "muted", size: "xs", children: creative.body })) : null] }), creative.ctaLabel ? (_jsx(Span, { color: "inverse", size: "xs", weight: "semibold", className: "flex-shrink-0 bg-primary py-[0.375rem]", rounded: "md", padding: "x-sm", children: creative.ctaLabel })) : null] }));
37
+ return (_jsxs(Anchor, { href: creative.ctaHref || "#", target: creative.ctaHref ? "_blank" : undefined, rel: "noopener noreferrer", underline: "none", layout: "flex", gap: "lg", rounded: "lg", padding: "inline", hoverSurface: "subtle", className: "w-full overflow-hidden bg-[var(--appkit-color-surface)]", children: [creative.imageUrl ? (_jsx(Div, { className: "relative h-14 w-14 flex-shrink-0 overflow-hidden", rounded: "default", children: _jsx(MediaImage, { src: creative.imageUrl, alt: creative.title || "Advertisement", size: "thumbnail" }) })) : null, _jsxs(Div, { className: "flex-1 min-w-0", children: [creative.title ? (_jsx(Text, { className: "truncate", color: "primary", size: "sm", weight: "semibold", children: creative.title })) : null, creative.body ? (_jsx(Text, { className: "truncate", color: "muted", size: "xs", children: creative.body })) : null] }), creative.ctaLabel ? (_jsx(Span, { color: "inverse", size: "xs", weight: "semibold", className: "flex-shrink-0 bg-primary py-[0.375rem]", rounded: "md", padding: "x-sm", children: creative.ctaLabel })) : null] }));
37
38
  }
38
39
  // ---------------------------------------------------------------------------
39
40
  // Primary component
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { Row } from "@mohasinac/appkit/ui";
4
- import { Div, Heading, HorizontalScroller, Section, Stack, Text } from "../../../ui";
4
+ import { Anchor, Div, Heading, HorizontalScroller, Section, Stack, Text } from "../../../ui";
5
5
  import { DynamicBgDiv } from "../../../ui/components/DynamicBgDiv";
6
6
  import { THEMED_TEXT_PRIMARY } from "../../../_internal/shared/styles/themed";
7
7
  import { SectionCarousel } from "./SectionCarousel";
@@ -45,7 +45,7 @@ const VARIANT_CLASS = {
45
45
  function CardItem({ card }) {
46
46
  const radiusClass = RADIUS_CLASS[card.borderRadius ?? "lg"] ?? "rounded-lg";
47
47
  const shadowClass = SHADOW_CLASS[card.shadowLevel ?? "sm"] ?? "shadow-sm";
48
- return (_jsxs(DynamicBgDiv, { color: card.backgroundColor, textColor: card.textColor, className: `flex flex-col overflow-hidden ${radiusClass} ${shadowClass} bg-[var(--appkit-color-surface)]`, children: [card.image && (_jsx(Div, { className: `relative w-full aspect-video ${__O.hidden}`, children: _jsx(MediaImage, { src: card.image, alt: card.imageAlt ?? "", size: "card" }) })), _jsxs(Stack, { className: `${__P.p4} flex-1`, gap: "sm", children: [card.eyebrow && (_jsx(Text, { className: "tracking-widest opacity-70", size: "xs", weight: "semibold", transform: "uppercase", children: card.eyebrow })), card.title && (_jsx(Text, { className: "leading-snug", size: "base", weight: "bold", children: card.title })), card.body && (_jsx(Text, { className: "opacity-80 leading-relaxed flex-1", size: "sm", children: card.body })), card.buttons && card.buttons.length > 0 && (_jsx(Row, { gap: "sm", wrap: true, className: "mt-2", children: card.buttons.map((btn, i) => (_jsx("a", { href: btn.href, target: btn.target ?? "_self", rel: btn.target === "_blank" ? "noopener noreferrer" : undefined, className: VARIANT_CLASS[btn.variant] ?? VARIANT_CLASS.primary, children: btn.label }, i))) })), card.formEmbed && (_jsx("iframe", { src: card.formEmbed, sandbox: "allow-forms allow-scripts", loading: "lazy", className: "w-full mt-3 border-0 rounded-md min-h-[200px]", title: "Embedded form" }))] })] }));
48
+ return (_jsxs(DynamicBgDiv, { color: card.backgroundColor, textColor: card.textColor, className: `flex flex-col overflow-hidden ${radiusClass} ${shadowClass} bg-[var(--appkit-color-surface)]`, children: [card.image && (_jsx(Div, { className: `relative w-full aspect-video ${__O.hidden}`, children: _jsx(MediaImage, { src: card.image, alt: card.imageAlt ?? "", size: "card" }) })), _jsxs(Stack, { className: `${__P.p4} flex-1`, gap: "sm", children: [card.eyebrow && (_jsx(Text, { className: "tracking-widest opacity-70", size: "xs", weight: "semibold", transform: "uppercase", children: card.eyebrow })), card.title && (_jsx(Text, { className: "leading-snug", size: "base", weight: "bold", children: card.title })), card.body && (_jsx(Text, { className: "opacity-80 leading-relaxed flex-1", size: "sm", children: card.body })), card.buttons && card.buttons.length > 0 && (_jsx(Row, { gap: "sm", wrap: true, className: "mt-2", children: card.buttons.map((btn, i) => (_jsx(Anchor, { href: btn.href, target: btn.target ?? "_self", rel: btn.target === "_blank" ? "noopener noreferrer" : undefined, tone: "none", underline: "none", className: VARIANT_CLASS[btn.variant] ?? VARIANT_CLASS.primary, children: btn.label }, i))) })), card.formEmbed && (_jsx("iframe", { src: card.formEmbed, sandbox: "allow-forms allow-scripts", loading: "lazy", className: "w-full mt-3 border-0 rounded-md min-h-[200px]", title: "Embedded form" }))] })] }));
49
49
  }
50
50
  // --- Section Header ----------------------------------------------------------
51
51
  function SectionHeader({ title }) {
@@ -56,7 +56,6 @@ function SectionHeader({ title }) {
56
56
  }
57
57
  export function CustomCardsSection(config) {
58
58
  const { title, layout, columns = 3, cards, autoScroll, scrollIntervalMs } = config;
59
- const themed = { textPrimary: THEMED_TEXT_PRIMARY };
60
59
  if (!cards?.length)
61
60
  return null;
62
61
  // autoScroll: wrap all cards in SectionCarousel (client carousel)
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import Link from "next/link";
3
3
  import { Badge, Div, Heading, Row, Section, Stack, Text } from "../../../ui";
4
+ import { MediaImage } from "../../media/MediaImage";
4
5
  import { ROUTES } from "../../../next/routing/route-map";
5
6
  import { formatCurrency } from "../../../utils/number.formatter";
6
7
  import { BUNDLE_COPY, BUNDLE_STOCK_VARIANT, } from "../../../_internal/shared/features/categories/bundle-copy";
@@ -21,9 +22,7 @@ function FeaturedBundleCard({ bundle, onBuyNow }) {
21
22
  const memberCount = bundle.bundleProductIds?.length ?? 0;
22
23
  const cover = bundle.display?.coverImage;
23
24
  const href = String(ROUTES.PUBLIC.BUNDLE_DETAIL?.(bundle.slug) ?? "#");
24
- return (_jsxs(Stack, { gap: "none", surface: "default", rounded: "xl", border: "default", children: [_jsxs(Link, { href: href, className: "group block p-3 hover:no-underline", children: [_jsx(Div, { className: `mb-2 aspect-square ${__O.hidden}`, rounded: "lg", surface: "subtle", children: cover ? (
25
- // eslint-disable-next-line @next/next/no-img-element
26
- _jsx("img", { src: cover, alt: bundle.name, className: "h-full w-full object-cover transition-transform group-hover:scale-105", loading: "lazy" })) : (_jsx(Row, { textSize: "3xl", className: "h-full w-full", align: "center", justify: "center", children: PLACEHOLDER_EMOJI })) }), _jsx(Text, { className: "line-clamp-2", size: "sm", weight: "semibold", children: bundle.name }), _jsxs(Row, { gap: "sm", align: "center", className: "mt-1", wrap: true, children: [_jsx(Text, { size: "sm", weight: "bold", children: bundle.bundlePriceInPaise
25
+ return (_jsxs(Stack, { gap: "none", surface: "default", rounded: "xl", border: "default", children: [_jsxs(Link, { href: href, className: "group block p-3 hover:no-underline", children: [_jsx(Div, { className: `relative mb-2 aspect-square ${__O.hidden}`, rounded: "lg", surface: "subtle", children: cover ? (_jsx(MediaImage, { src: cover, alt: bundle.name, size: "card", className: "transition-transform group-hover:scale-105" })) : (_jsx(Row, { textSize: "3xl", className: "h-full w-full", align: "center", justify: "center", children: PLACEHOLDER_EMOJI })) }), _jsx(Text, { className: "line-clamp-2", size: "sm", weight: "semibold", children: bundle.name }), _jsxs(Row, { gap: "sm", align: "center", className: "mt-1", wrap: true, children: [_jsx(Text, { size: "sm", weight: "bold", children: bundle.bundlePriceInPaise
27
26
  ? formatCurrency(bundle.bundlePriceInPaise / 100, "INR")
28
27
  : BUNDLE_COPY.featured.priceFallback }), _jsxs(Text, { size: "xs", color: "muted", children: ["\u00B7 ", BUNDLE_COPY.featured.itemCount(memberCount)] }), stock !== "in_stock" && (_jsx(Badge, { variant: BUNDLE_STOCK_VARIANT[stock], children: BUNDLE_COPY.stockBadge.listVariantOutOfStock }))] })] }), onBuyNow && (_jsx(Div, { border: "subtle", className: `border-t ${__P.p3}`, padding: "t-xs", children: _jsx(BundleBuyNowCta, { bundleSlug: bundle.slug, outOfStock: stock === "out_of_stock", onBuyNow: onBuyNow, compact: true }) }))] }));
29
28
  }
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { normalizeError } from "../../../errors/normalize";
3
- import { Div, Heading, Row, Section, Span, Stack, Text } from "../../../ui";
3
+ import { Anchor, Div, Heading, Row, Section, Span, Stack, Text } from "../../../ui";
4
+ import { MediaImage } from "../../media/MediaImage";
4
5
  import { siteSettingsRepository } from "../../admin/repository/site-settings.repository";
5
6
  import { fetchGoogleReviews } from "../lib/google-reviews-fetcher";
6
7
  const __P = {
@@ -26,7 +27,7 @@ function ReviewCard({ review, showRating, showDate, }) {
26
27
  day: "numeric",
27
28
  })
28
29
  : null;
29
- return (_jsxs(Stack, { className: `${__P.p4} bg-[var(--appkit-color-surface)] border border-[var(--appkit-color-border)]`, gap: "3", rounded: "xl", shadow: "sm", children: [_jsxs(Row, { align: "center", gap: "3", children: [review.authorPhotoUrl ? (_jsx("img", { src: review.authorPhotoUrl, alt: review.authorName, className: "w-9 h-9 rounded-full object-cover flex-shrink-0", loading: "lazy", referrerPolicy: "no-referrer" })) : (_jsx(Row, { className: "w-9 h-9 bg-[var(--appkit-color-primary)] flex-shrink-0", align: "center", justify: "center", rounded: "full", children: _jsx(Span, { color: "inverse", size: "sm", weight: "bold", children: review.authorName.charAt(0).toUpperCase() }) })), _jsxs(Div, { className: "min-w-0", children: [_jsx(Text, { className: "truncate", size: "sm", weight: "semibold", children: review.authorName }), dateStr && (_jsx(Text, { size: "xs", color: "muted", children: dateStr }))] })] }), showRating && _jsx(StarRating, { rating: review.rating }), review.text && (_jsx(Text, { className: "leading-relaxed line-clamp-4", color: "muted", size: "sm", children: review.text })), _jsx(GoogleBadge, {})] }));
30
+ return (_jsxs(Stack, { className: `${__P.p4} bg-[var(--appkit-color-surface)] border border-[var(--appkit-color-border)]`, gap: "3", rounded: "xl", shadow: "sm", children: [_jsxs(Row, { align: "center", gap: "3", children: [review.authorPhotoUrl ? (_jsx(Div, { className: "relative w-9 h-9 flex-shrink-0 overflow-hidden", rounded: "full", children: _jsx(MediaImage, { src: review.authorPhotoUrl, alt: review.authorName, size: "avatar" }) })) : (_jsx(Row, { className: "w-9 h-9 bg-[var(--appkit-color-primary)] flex-shrink-0", align: "center", justify: "center", rounded: "full", children: _jsx(Span, { color: "inverse", size: "sm", weight: "bold", children: review.authorName.charAt(0).toUpperCase() }) })), _jsxs(Div, { className: "min-w-0", children: [_jsx(Text, { className: "truncate", size: "sm", weight: "semibold", children: review.authorName }), dateStr && (_jsx(Text, { size: "xs", color: "muted", children: dateStr }))] })] }), showRating && _jsx(StarRating, { rating: review.rating }), review.text && (_jsx(Text, { className: "leading-relaxed line-clamp-4", color: "muted", size: "sm", children: review.text })), _jsx(GoogleBadge, {})] }));
30
31
  }
31
32
  // --- Empty / Not Configured States -------------------------------------------
32
33
  function NotConfiguredState() {
@@ -67,5 +68,5 @@ export async function GoogleReviewsSection(config) {
67
68
  : "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4";
68
69
  const cardClass = layout === "carousel" ? "flex-shrink-0 w-[85%] sm:w-72 snap-start" : "";
69
70
  const mapsHref = googleMapsUrl || (placeId ? `https://search.google.com/local/reviews?placeid=${placeId}` : "");
70
- return (_jsx(Section, { padding: "y-3xl", surface: "muted", children: _jsxs(Div, { className: "w-full max-w-7xl mx-auto", padding: "x-md", children: [_jsxs(Row, { className: "mb-8", align: "end", justify: "between", gap: "md", children: [_jsxs(_Fragment, { children: [_jsx(Heading, { level: 2, children: "What Our Customers Say" }), aggregateRating > 0 && (_jsxs(Row, { className: "mt-1", align: "center", gap: "sm", children: [_jsx(StarRating, { rating: Math.round(aggregateRating) }), _jsxs(Text, { size: "sm", variant: "muted", children: [aggregateRating.toFixed(1), " \u00B7 ", totalRatings.toLocaleString(), " reviews"] })] }))] }), linkToGoogleMaps && mapsHref && (_jsx("a", { href: mapsHref, target: "_blank", rel: "noopener noreferrer", className: CLS_GMAPS_LINK, children: "View on Google \u2192" }))] }), reviews.length === 0 ? (_jsx(EmptyState, {})) : (_jsx(Div, { className: gridClass, children: reviews.map((review, i) => (_jsx(Div, { className: cardClass, children: _jsx(ReviewCard, { review: review, showRating: showRating, showDate: showDate }) }, `${review.authorName}-${i}`))) }))] }) }));
71
+ return (_jsx(Section, { padding: "y-3xl", surface: "muted", children: _jsxs(Div, { className: "w-full max-w-7xl mx-auto", padding: "x-md", children: [_jsxs(Row, { className: "mb-8", align: "end", justify: "between", gap: "md", children: [_jsxs(_Fragment, { children: [_jsx(Heading, { level: 2, children: "What Our Customers Say" }), aggregateRating > 0 && (_jsxs(Row, { className: "mt-1", align: "center", gap: "sm", children: [_jsx(StarRating, { rating: Math.round(aggregateRating) }), _jsxs(Text, { size: "sm", variant: "muted", children: [aggregateRating.toFixed(1), " \u00B7 ", totalRatings.toLocaleString(), " reviews"] })] }))] }), linkToGoogleMaps && mapsHref && (_jsx(Anchor, { href: mapsHref, tone: "none", underline: "hover", className: CLS_GMAPS_LINK, children: "View on Google \u2192" }))] }), reviews.length === 0 ? (_jsx(EmptyState, {})) : (_jsx(Div, { className: gridClass, children: reviews.map((review, i) => (_jsx(Div, { className: cardClass, children: _jsx(ReviewCard, { review: review, showRating: showRating, showDate: showDate }) }, `${review.authorName}-${i}`))) }))] }) }));
71
72
  }
@@ -4,7 +4,6 @@ import { useState, useEffect, useRef, useCallback } from "react";
4
4
  import Image from "next/image";
5
5
  import Link from "next/link";
6
6
  import { Button, Div, Heading, Row, Span, Stack } from "../../../ui";
7
- const FONT_BANGERS = "var(--font-bangers, Bangers, cursive)";
8
7
  export function HeroBanner({ banners, autoplayMs = 5000 }) {
9
8
  const [current, setCurrent] = useState(0);
10
9
  const [animating, setAnimating] = useState(false);
@@ -72,4 +72,4 @@ export interface SectionCarouselProps<T = unknown> {
72
72
  */
73
73
  minItemWidth?: number;
74
74
  }
75
- export declare function SectionCarousel<T = unknown>({ title, description, headingVariant, pillLabel, backgroundImage, viewMoreHref, viewMoreLabel, items, renderItem, perView, gap, autoScroll, autoScrollInterval, keyExtractor, rows, className, isLoading, skeletonCount, lightText, showPeek, minItemWidth, }: SectionCarouselProps<T>): React.JSX.Element | null;
75
+ export declare function SectionCarousel<T = unknown>({ title, description, headingVariant, pillLabel, backgroundImage, viewMoreHref, viewMoreLabel, items, renderItem, perView, gap, autoScroll, autoScrollInterval, keyExtractor, rows, className, isLoading, skeletonCount, lightText, showPeek: _showPeek, minItemWidth, }: SectionCarouselProps<T>): React.JSX.Element | null;
@@ -9,11 +9,10 @@ const __O = {
9
9
  };
10
10
  // --- Skeleton ----------------------------------------------------------------
11
11
  function CarouselSkeleton({ count }) {
12
- const themed = { bgSecondary: THEMED_BG_SECONDARY, textPrimary: THEMED_TEXT_PRIMARY, textSecondary: THEMED_TEXT_SECONDARY };
13
12
  return (_jsx(Div, { layout: "flex", gap: "4", className: `${__O.hidden}`, padding: "x-md", children: Array.from({ length: count }).map((_, i) => (_jsxs(Stack, { className: "flex-none min-w-[clamp(150px,18vw,260px)] max-w-[clamp(240px,36vw,380px)] h-[clamp(180px,26vh,260px)]", gap: "sm", children: [_jsx(Div, { className: `aspect-square ${skeleton.image}`, rounded: "xl" }), _jsx(Div, { className: `${skeleton.text} w-3/4` }), _jsx(Div, { className: `${skeleton.text} w-1/2` })] }, i))) }));
14
13
  }
15
14
  // --- Component ---------------------------------------------------------------
16
- export function SectionCarousel({ title, description, headingVariant = "editorial", pillLabel, backgroundImage, viewMoreHref, viewMoreLabel = "View all →", items, renderItem, perView = CAROUSEL_PER_VIEW.standard, gap = 16, autoScroll = false, autoScrollInterval = 3500, keyExtractor, rows = 1, className = "", isLoading = false, skeletonCount = 4, lightText, showPeek = false, minItemWidth = 220, }) {
15
+ export function SectionCarousel({ title, description, headingVariant = "editorial", pillLabel, backgroundImage, viewMoreHref, viewMoreLabel = "View all →", items, renderItem, perView = CAROUSEL_PER_VIEW.standard, gap = 16, autoScroll = false, autoScrollInterval = 3500, keyExtractor, rows = 1, className = "", isLoading = false, skeletonCount = 4, lightText, showPeek: _showPeek = false, minItemWidth = 220, }) {
17
16
  if (!isLoading && items.length === 0)
18
17
  return null;
19
18
  const hasBg = Boolean(backgroundImage);
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { normalizeError } from "../../../errors/normalize";
3
- import { Div, Heading, Row, Section, Stack, Text } from "../../../ui";
3
+ import { Anchor, Div, Heading, Row, Section, Stack, Text } from "../../../ui";
4
4
  import { SocialPostCard } from "./SocialPostCard";
5
5
  import { fetchInstagramPosts, fetchFacebookPosts, fetchTikTokPosts, fetchDeviantArtPosts, } from "../lib/social-feed-fetcher";
6
6
  import { siteSettingsRepository } from "../../admin/repository/site-settings.repository";
@@ -22,9 +22,6 @@ function platformProfileUrl(platform, handle) {
22
22
  }
23
23
  }
24
24
  // --- Skeleton ----------------------------------------------------------------
25
- function SocialFeedSkeleton({ count }) {
26
- return (_jsx(Div, { layout: "grid", gap: "3", className: "grid-cols-2 sm:grid-cols-3 lg:grid-cols-4", children: Array.from({ length: count }).map((_, i) => (_jsx(Div, { className: "aspect-square animate-pulse", surface: "subtle", rounded: "xl" }, i))) }));
27
- }
28
25
  // --- Empty state -------------------------------------------------------------
29
26
  function SocialFeedEmpty({ platform }) {
30
27
  return (_jsxs(Stack, { justify: "center", color: "faint", padding: "y-4xl", align: "center", children: [_jsxs("svg", { className: "w-10 h-10 mb-3", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: [_jsx("rect", { x: "3", y: "3", width: "18", height: "18", rx: "3" }), _jsx("path", { d: "M9 9h6M9 12h6M9 15h4" })] }), _jsxs(Text, { size: "sm", children: ["No posts from ", PLATFORM_LABELS[platform], " yet."] })] }));
@@ -102,5 +99,5 @@ export async function SocialFeedSection(config) {
102
99
  : layout === "masonry"
103
100
  ? "break-inside-avoid"
104
101
  : "";
105
- return (_jsx(Section, { padding: "y-3xl", surface: "muted", children: _jsxs(Div, { className: "w-full max-w-7xl mx-auto", padding: "x-md", children: [_jsxs(Row, { className: "mb-8", align: "end", justify: "between", gap: "md", children: [_jsxs(_Fragment, { children: [_jsx(Heading, { level: 2, className: "mb-1", children: title || `${PLATFORM_LABELS[platform]} Feed` }), subtitle && (_jsx(Text, { size: "sm", variant: "muted", children: subtitle }))] }), profileUrl && (_jsxs("a", { href: profileUrl, target: "_blank", rel: "noopener noreferrer", className: "shrink-0 text-sm font-medium text-[var(--appkit-color-primary)] hover:opacity-80", children: ["Follow on ", PLATFORM_LABELS[platform], " \u2192"] }))] }), error ? (_jsx(Div, { textSize: "sm", className: "text-center", color: "faint", padding: "y-3xl", children: error })) : posts.length === 0 ? (_jsx(SocialFeedEmpty, { platform: platform })) : (_jsx(Div, { className: gridClass, children: posts.slice(0, count).map((post) => (_jsx(Div, { className: cardClass, children: _jsx(SocialPostCard, { post: post, showCaption: showCaption, showStats: showStats }) }, post.id))) }))] }) }));
102
+ return (_jsx(Section, { padding: "y-3xl", surface: "muted", children: _jsxs(Div, { className: "w-full max-w-7xl mx-auto", padding: "x-md", children: [_jsxs(Row, { className: "mb-8", align: "end", justify: "between", gap: "md", children: [_jsxs(_Fragment, { children: [_jsx(Heading, { level: 2, className: "mb-1", children: title || `${PLATFORM_LABELS[platform]} Feed` }), subtitle && (_jsx(Text, { size: "sm", variant: "muted", children: subtitle }))] }), profileUrl && (_jsxs(Anchor, { href: profileUrl, tone: "none", underline: "none", className: "shrink-0 text-sm font-medium text-[var(--appkit-color-primary)] hover:opacity-80", children: ["Follow on ", PLATFORM_LABELS[platform], " \u2192"] }))] }), error ? (_jsx(Div, { textSize: "sm", className: "text-center", color: "faint", padding: "y-3xl", children: error })) : posts.length === 0 ? (_jsx(SocialFeedEmpty, { platform: platform })) : (_jsx(Div, { className: gridClass, children: posts.slice(0, count).map((post) => (_jsx(Div, { className: cardClass, children: _jsx(SocialPostCard, { post: post, showCaption: showCaption, showStats: showStats }) }, post.id))) }))] }) }));
106
103
  }
@@ -1,5 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Div, Row, Span, Stack, Text } from "../../../ui";
2
+ import { Anchor, Div, Row, Span, Stack, Text } from "../../../ui";
3
+ import { MediaImage } from "../../media/MediaImage";
3
4
  const __P = {
4
5
  p3: "p-3",
5
6
  };
@@ -46,12 +47,12 @@ function YouTubeCard({ post, showCaption }) {
46
47
  const thumbnail = `https://img.youtube.com/vi/${post.videoId}/maxresdefault.jpg`;
47
48
  const href = `https://youtu.be/${post.videoId}`;
48
49
  const meta = PLATFORM_META.youtube;
49
- return (_jsxs("a", { href: href, target: "_blank", rel: "noopener noreferrer", className: "group relative block aspect-video w-full overflow-hidden rounded-xl bg-zinc-900 focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--appkit-color-youtube)]", "aria-label": post.caption ?? `Watch on YouTube`, children: [_jsx("img", { src: thumbnail, alt: post.caption ?? "YouTube video", className: "h-full w-full object-cover transition-transform duration-300 group-hover:scale-105", loading: "lazy" }), _jsx(Row, { className: "absolute inset-0", align: "center", justify: "center", children: _jsx(Row, { className: "h-14 w-14 bg-[var(--appkit-color-youtube)] transition-transform duration-200 group-hover:scale-110", align: "center", justify: "center", rounded: "full", shadow: "lg", children: _jsx("svg", { className: "ml-1 h-6 w-6 text-white", viewBox: "0 0 24 24", fill: "currentColor", children: _jsx("path", { d: "M8 5v14l11-7z" }) }) }) }), _jsxs(Row, { textWeight: "medium", textSize: "xs", className: `absolute right-2 top-2 text-white px-[0.375rem] ${meta.colorClass}`, padding: "y-2xs", align: "center", gap: "xs", rounded: "default", children: [meta.icon, _jsx(Span, { className: "hidden sm:inline", children: meta.label })] }), post.channelName && (_jsx(Div, { textSize: "xs", surface: "overlay-lg", className: "absolute bottom-2 left-2 py-[0.125rem] text-white backdrop-blur-sm", padding: "x-xs", rounded: "default", children: post.channelName })), showCaption && post.caption && (_jsx(Stack, { justify: "end", surface: "overlay-lg", className: `absolute inset-0 ${__P.p3} opacity-0 transition-opacity duration-200 group-hover:opacity-100`, children: _jsx(Text, { color: "inverse", className: "line-clamp-2", size: "xs", children: post.caption }) }))] }));
50
+ return (_jsxs(Anchor, { href: href, tone: "none", underline: "none", className: "group relative block aspect-video w-full overflow-hidden rounded-xl bg-zinc-900 focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--appkit-color-youtube)]", "aria-label": post.caption ?? `Watch on YouTube`, children: [_jsx(MediaImage, { src: thumbnail, alt: post.caption ?? "YouTube video", size: "card", className: "transition-transform duration-300 group-hover:scale-105" }), _jsx(Row, { className: "absolute inset-0", align: "center", justify: "center", children: _jsx(Row, { className: "h-14 w-14 bg-[var(--appkit-color-youtube)] transition-transform duration-200 group-hover:scale-110", align: "center", justify: "center", rounded: "full", shadow: "lg", children: _jsx("svg", { className: "ml-1 h-6 w-6 text-white", viewBox: "0 0 24 24", fill: "currentColor", children: _jsx("path", { d: "M8 5v14l11-7z" }) }) }) }), _jsxs(Row, { textWeight: "medium", textSize: "xs", className: `absolute right-2 top-2 text-white px-[0.375rem] ${meta.colorClass}`, padding: "y-2xs", align: "center", gap: "xs", rounded: "default", children: [meta.icon, _jsx(Span, { className: "hidden sm:inline", children: meta.label })] }), post.channelName && (_jsx(Div, { textSize: "xs", surface: "overlay-lg", className: "absolute bottom-2 left-2 py-[0.125rem] text-white backdrop-blur-sm", padding: "x-xs", rounded: "default", children: post.channelName })), showCaption && post.caption && (_jsx(Stack, { justify: "end", surface: "overlay-lg", className: `absolute inset-0 ${__P.p3} opacity-0 transition-opacity duration-200 group-hover:opacity-100`, children: _jsx(Text, { color: "inverse", className: "line-clamp-2", size: "xs", children: post.caption }) }))] }));
50
51
  }
51
52
  export function SocialPostCard({ post, showCaption = true, showStats = true }) {
52
53
  if (post.platform === "youtube") {
53
54
  return _jsx(YouTubeCard, { post: post, showCaption: showCaption });
54
55
  }
55
56
  const meta = PLATFORM_META[post.platform];
56
- return (_jsxs("a", { href: post.permalink, target: "_blank", rel: "noopener noreferrer", className: "group relative block aspect-square overflow-hidden rounded-xl bg-[var(--appkit-color-surface)] focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--appkit-color-primary)] bg-[var(--appkit-color-surface-elevated)]", "aria-label": post.caption ?? `View on ${meta.label}`, children: [post.imageUrl ? (_jsx("img", { src: post.imageUrl, alt: post.caption ?? `${meta.label} post`, className: "h-full w-full object-cover transition-transform duration-300 group-hover:scale-105", loading: "lazy" })) : (_jsx(Row, { className: "h-full w-full", surface: "subtle", align: "center", justify: "center", children: _jsx(Span, { size: "sm", color: "faint", children: "No preview" }) })), post.mediaType === "video" && (_jsx(Row, { className: "pointer-events-none absolute inset-0", align: "center", justify: "center", children: _jsx(Row, { surface: "overlay-sm", className: "h-12 w-12 backdrop-blur-sm", align: "center", justify: "center", rounded: "full", children: _jsx("svg", { className: "ml-1 h-5 w-5 text-white", viewBox: "0 0 24 24", fill: "currentColor", children: _jsx("path", { d: "M8 5v14l11-7z" }) }) }) })), post.mediaType === "carousel" && (_jsx(Div, { className: "pointer-events-none absolute right-2 top-2", children: _jsx(Row, { surface: "overlay-sm", className: "h-6 w-6 backdrop-blur-sm", rounded: "default", align: "center", justify: "center", children: _jsxs("svg", { className: "h-3.5 w-3.5 text-white", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [_jsx("rect", { x: "2", y: "3", width: "15", height: "15", rx: "2" }), _jsx("path", { d: "M7 8h14M7 12h14M7 16h14" })] }) }) })), _jsxs(Row, { textWeight: "medium", textSize: "xs", className: `absolute left-2 top-2 px-[0.375rem] text-white ${meta.colorClass}`, padding: "y-2xs", align: "center", gap: "xs", rounded: "default", children: [meta.icon, _jsx(Span, { className: "hidden sm:inline", children: meta.label })] }), _jsxs(Stack, { justify: "end", surface: "overlay-xl", className: `pointer-events-none absolute inset-0 ${__P.p3} opacity-0 transition-opacity duration-200 group-hover:opacity-100`, children: [showCaption && post.caption && (_jsx(Text, { color: "inverse", className: "mb-2 line-clamp-3", size: "xs", children: post.caption })), showStats && (_jsxs(Row, { textSize: "xs", className: "text-white/80", align: "center", gap: "3", children: [post.stats.likes != null && (_jsxs(Span, { layout: "flex", gap: "xs", children: [_jsx("svg", { className: "h-3 w-3", viewBox: "0 0 24 24", fill: "currentColor", children: _jsx("path", { d: "M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z" }) }), formatCount(post.stats.likes)] })), post.stats.views != null && (_jsxs(Span, { layout: "flex", gap: "xs", children: [_jsx("svg", { className: "h-3 w-3", viewBox: "0 0 24 24", fill: "currentColor", children: _jsx("path", { d: "M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z" }) }), formatCount(post.stats.views)] })), post.stats.comments != null && (_jsxs(Span, { layout: "flex", gap: "xs", children: [_jsx("svg", { className: "h-3 w-3", viewBox: "0 0 24 24", fill: "currentColor", children: _jsx("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" }) }), formatCount(post.stats.comments)] }))] }))] })] }));
57
+ return (_jsxs(Anchor, { href: post.permalink, tone: "none", underline: "none", className: "group relative block aspect-square overflow-hidden rounded-xl bg-[var(--appkit-color-surface)] focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--appkit-color-primary)] bg-[var(--appkit-color-surface-elevated)]", "aria-label": post.caption ?? `View on ${meta.label}`, children: [post.imageUrl ? (_jsx(MediaImage, { src: post.imageUrl, alt: post.caption ?? `${meta.label} post`, size: "card", className: "transition-transform duration-300 group-hover:scale-105" })) : (_jsx(Row, { className: "h-full w-full", surface: "subtle", align: "center", justify: "center", children: _jsx(Span, { size: "sm", color: "faint", children: "No preview" }) })), post.mediaType === "video" && (_jsx(Row, { className: "pointer-events-none absolute inset-0", align: "center", justify: "center", children: _jsx(Row, { surface: "overlay-sm", className: "h-12 w-12 backdrop-blur-sm", align: "center", justify: "center", rounded: "full", children: _jsx("svg", { className: "ml-1 h-5 w-5 text-white", viewBox: "0 0 24 24", fill: "currentColor", children: _jsx("path", { d: "M8 5v14l11-7z" }) }) }) })), post.mediaType === "carousel" && (_jsx(Div, { className: "pointer-events-none absolute right-2 top-2", children: _jsx(Row, { surface: "overlay-sm", className: "h-6 w-6 backdrop-blur-sm", rounded: "default", align: "center", justify: "center", children: _jsxs("svg", { className: "h-3.5 w-3.5 text-white", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [_jsx("rect", { x: "2", y: "3", width: "15", height: "15", rx: "2" }), _jsx("path", { d: "M7 8h14M7 12h14M7 16h14" })] }) }) })), _jsxs(Row, { textWeight: "medium", textSize: "xs", className: `absolute left-2 top-2 px-[0.375rem] text-white ${meta.colorClass}`, padding: "y-2xs", align: "center", gap: "xs", rounded: "default", children: [meta.icon, _jsx(Span, { className: "hidden sm:inline", children: meta.label })] }), _jsxs(Stack, { justify: "end", surface: "overlay-xl", className: `pointer-events-none absolute inset-0 ${__P.p3} opacity-0 transition-opacity duration-200 group-hover:opacity-100`, children: [showCaption && post.caption && (_jsx(Text, { color: "inverse", className: "mb-2 line-clamp-3", size: "xs", children: post.caption })), showStats && (_jsxs(Row, { textSize: "xs", className: "text-white/80", align: "center", gap: "3", children: [post.stats.likes != null && (_jsxs(Span, { layout: "flex", gap: "xs", children: [_jsx("svg", { className: "h-3 w-3", viewBox: "0 0 24 24", fill: "currentColor", children: _jsx("path", { d: "M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z" }) }), formatCount(post.stats.likes)] })), post.stats.views != null && (_jsxs(Span, { layout: "flex", gap: "xs", children: [_jsx("svg", { className: "h-3 w-3", viewBox: "0 0 24 24", fill: "currentColor", children: _jsx("path", { d: "M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z" }) }), formatCount(post.stats.views)] })), post.stats.comments != null && (_jsxs(Span, { layout: "flex", gap: "xs", children: [_jsx("svg", { className: "h-3 w-3", viewBox: "0 0 24 24", fill: "currentColor", children: _jsx("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" }) }), formatCount(post.stats.comments)] }))] }))] })] }));
57
58
  }
@@ -4,6 +4,7 @@ import { normalizeError } from "../../errors/normalize";
4
4
  import React, { useState, useCallback } from "react";
5
5
  import { Modal } from "../../ui/components/Modal";
6
6
  import { Button, Div, Input, Label, Row, Stack, Text, Alert } from "../../ui";
7
+ import { MediaImage } from "./MediaImage";
7
8
  import { MediaUploadField } from "./upload/MediaUploadField";
8
9
  import { useMediaUpload, useMediaCleanup } from "./index";
9
10
  const MEDIA_LIST_ENDPOINT = "/api/admin/media";
@@ -128,9 +129,7 @@ export function MediaPickerModal({ isOpen, onClose, onSelect, accept = "image/*"
128
129
  isSelected
129
130
  ? "border-primary-500 ring-2 ring-primary-300"
130
131
  : "border-[var(--appkit-color-border)]",
131
- ].join(" "), "aria-pressed": isSelected, children: [_jsx(Row, { className: "aspect-square w-full", surface: "subtle", align: "center", justify: "center", children: isImage ? (
132
- // eslint-disable-next-line @next/next/no-img-element
133
- _jsx("img", { src: f.downloadURL, alt: f.name, className: "h-full w-full object-cover", loading: "lazy" })) : (_jsx(Text, { size: "sm", color: "muted", children: f.contentType ?? "file" })) }), _jsx(Text, { className: "block truncate p-1.5 font-mono", size: "xs", children: f.name.split("/").pop() })] }, f.name));
132
+ ].join(" "), "aria-pressed": isSelected, children: [_jsx(Row, { className: "relative aspect-square w-full", surface: "subtle", align: "center", justify: "center", children: isImage ? (_jsx(MediaImage, { src: f.downloadURL, alt: f.name, size: "thumbnail" })) : (_jsx(Text, { size: "sm", color: "muted", children: f.contentType ?? "file" })) }), _jsx(Text, { className: "block truncate p-1.5 font-mono", size: "xs", children: f.name.split("/").pop() })] }, f.name));
134
133
  }), existingLoading && (_jsx(Text, { paddingY: "lg", className: "col-span-full", align: "center", color: "muted", children: "Loading\u2026" })), !existingLoading && filteredExisting.length === 0 && (_jsx(Text, { paddingY: "lg", className: "col-span-full", align: "center", color: "muted", children: "No files match." }))] })] })), tab === "upload" && (_jsx(MediaUploadField, { label: "File", value: uploadedUrl, onChange: setUploadedUrl, onUpload: handleUpload, accept: accept, maxSizeMB: maxSizeMB, onAbort: handleAbort })), tab === "url" && (_jsxs(Stack, { gap: "sm", children: [_jsx(Label, { htmlFor: "media-picker-url", children: "Image or video URL" }), _jsx(Input, { id: "media-picker-url", value: externalUrl, onChange: (e) => {
135
134
  setExternalUrl(e.target.value);
136
135
  setUrlError("");
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useEffect, useRef, useState } from "react";
4
- import { Alert, Button, Div, Label, Row, Spinner, Stack, Text } from "../../../ui";
4
+ import { Alert, Anchor, Button, Div, Label, Row, Spinner, Stack, Text } from "../../../ui";
5
5
  import { MediaImage } from "../MediaImage";
6
6
  import { MediaVideo } from "../MediaVideo";
7
7
  import { inferMediaTypeFromMime } from "../types/index";
@@ -107,5 +107,5 @@ export function MediaUploadList({ label, value, onChange, onUpload, accept = "im
107
107
  unstageUrl(target.url);
108
108
  }
109
109
  };
110
- return (_jsxs(Stack, { gap: "sm", children: [_jsx(Label, { className: "block", color: "muted", size: "sm", weight: "medium", children: label }), value.length > 0 && (_jsx(Div, { layout: "grid", gap: "3", className: "grid-cols-1 sm:grid-cols-2", children: value.map((item, index) => (_jsxs(Div, { rounded: "lg", padding: "sm", border: "default", surface: "muted", children: [item.type === "video" ? (_jsx(Div, { className: `relative aspect-square max-h-24 ${__O.hidden}`, rounded: "lg", children: _jsx(MediaVideo, { src: item.url, alt: item.alt || `Media ${index + 1}`, controls: true }) })) : item.type === "image" ? (_jsx(Div, { className: `relative aspect-square max-h-24 ${__O.hidden}`, rounded: "lg", children: _jsx(MediaImage, { src: item.url, alt: item.alt || `Media ${index + 1}`, size: "card", objectFit: "contain" }) })) : (_jsx("a", { href: item.url, target: "_blank", rel: "noopener noreferrer", className: CLS_PDF_LINK, children: fileNameFromUrl(item.url) })), !disabled && (_jsx(Div, { className: "mt-2", children: _jsx(Button, { type: "button", size: "sm", variant: "danger", onClick: () => removeAt(index), children: "Remove" }) }))] }, `${item.url}-${index}`))) })), !disabled && value.length < maxItems && (_jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => inputRef.current?.click(), disabled: isLoading, children: isLoading ? "Uploading..." : `+ Add Files (${value.length}/${maxItems})` })), isLoading && (_jsxs(Row, { align: "center", gap: "sm", children: [_jsx(Spinner, { size: "sm" }), _jsx(Text, { size: "sm", variant: "secondary", children: "Uploading" })] })), _jsx("input", { ref: inputRef, type: "file", multiple: true, accept: accept, onChange: handleFileChange, className: "hidden", "aria-hidden": "true", "data-testid": "media-upload-list-input" }), helperText && !error && (_jsx(Text, { variant: "secondary", size: "xs", children: helperText })), error && _jsx(Alert, { variant: "error", children: error })] }));
110
+ return (_jsxs(Stack, { gap: "sm", children: [_jsx(Label, { className: "block", color: "muted", size: "sm", weight: "medium", children: label }), value.length > 0 && (_jsx(Div, { layout: "grid", gap: "3", className: "grid-cols-1 sm:grid-cols-2", children: value.map((item, index) => (_jsxs(Div, { rounded: "lg", padding: "sm", border: "default", surface: "muted", children: [item.type === "video" ? (_jsx(Div, { className: `relative aspect-square max-h-24 ${__O.hidden}`, rounded: "lg", children: _jsx(MediaVideo, { src: item.url, alt: item.alt || `Media ${index + 1}`, controls: true }) })) : item.type === "image" ? (_jsx(Div, { className: `relative aspect-square max-h-24 ${__O.hidden}`, rounded: "lg", children: _jsx(MediaImage, { src: item.url, alt: item.alt || `Media ${index + 1}`, size: "card", objectFit: "contain" }) })) : (_jsx(Anchor, { href: item.url, tone: "none", underline: "none", className: CLS_PDF_LINK, children: fileNameFromUrl(item.url) })), !disabled && (_jsx(Div, { className: "mt-2", children: _jsx(Button, { type: "button", size: "sm", variant: "danger", onClick: () => removeAt(index), children: "Remove" }) }))] }, `${item.url}-${index}`))) })), !disabled && value.length < maxItems && (_jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => inputRef.current?.click(), disabled: isLoading, children: isLoading ? "Uploading..." : `+ Add Files (${value.length}/${maxItems})` })), isLoading && (_jsxs(Row, { align: "center", gap: "sm", children: [_jsx(Spinner, { size: "sm" }), _jsx(Text, { size: "sm", variant: "secondary", children: "Uploading" })] })), _jsx("input", { ref: inputRef, type: "file", multiple: true, accept: accept, onChange: handleFileChange, className: "hidden", "aria-hidden": "true", "data-testid": "media-upload-list-input" }), helperText && !error && (_jsx(Text, { variant: "secondary", size: "xs", children: helperText })), error && _jsx(Alert, { variant: "error", children: error })] }));
111
111
  }
@@ -1,5 +1,6 @@
1
1
  import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
2
  import { Div, Pagination, Row, Span, Stack, Text } from "../../../ui";
3
+ import { MediaImage } from "../../media/MediaImage";
3
4
  import { formatCurrency } from "../../../utils/number.formatter";
4
5
  import { getDefaultLocale } from "../../../core/baseline-resolver";
5
6
  const CLS_PRIZE_BADGE = "inline-flex items-center rounded-full bg-fuchsia-100 dark:bg-fuchsia-900/30 px-2.5 py-0.5 text-fuchsia-700 dark:text-fuchsia-300";
@@ -34,7 +35,7 @@ export function OrderCard({ order, onClick, labels = {} }) {
34
35
  .sort()[0];
35
36
  return (_jsxs(Div, { padding: "5", role: onClick ? "button" : undefined, tabIndex: onClick ? 0 : undefined, onKeyDown: onClick
36
37
  ? (e) => (e.key === "Enter" || e.key === " ") && onClick(order)
37
- : undefined, onClick: onClick ? () => onClick(order) : undefined, className: `border border-neutral-200 ${onClick ? "cursor-pointer transition hover:shadow-md" : ""}`, rounded: "xl", surface: "default", children: [_jsxs(Row, { wrap: true, align: "start", justify: "between", gap: "3", children: [_jsxs(Div, { children: [_jsxs(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "xs", children: ["Order #", order.id.slice(-8).toUpperCase()] }), date && (_jsx(Text, { className: "mt-0.5", color: "faint", size: "xs", children: date }))] }), _jsx(Span, { size: "xs", weight: "semibold", className: `capitalize ${statusColor}`, rounded: "full", padding: "pill-md", children: labels[order.orderStatus] ?? order.orderStatus.replace(/_/g, " ") })] }), revealsRemaining > 0 && (_jsxs(Row, { gap: "sm", className: "mt-2", children: [_jsxs(Span, { size: "xs", weight: "semibold", className: CLS_PRIZE_BADGE, children: [revealsRemaining, " ", revealsRemaining === 1 ? "reveal" : "reveals", " pending"] }), earliestDeadline && (_jsxs(Span, { size: "xs", color: "muted", children: ["before ", new Date(earliestDeadline).toLocaleDateString(getDefaultLocale(), { month: "short", day: "numeric" })] }))] })), _jsxs(Row, { wrap: true, gap: "3", className: "mt-4", children: [order.items.slice(0, 3).map((item, i) => (_jsxs(Row, { gap: "sm", children: [item.image && (_jsx("img", { src: item.image, alt: item.title, className: "h-10 w-10 object-cover rounded-lg" })), _jsxs(Div, { children: [_jsx(Text, { truncate: true, size: "sm", weight: "medium", color: "primary", children: item.title }), _jsxs(Text, { size: "xs", color: "faint", children: ["\u00D7", item.quantity] })] })] }, i))), order.items.length > 3 && (_jsxs(Span, { size: "xs", className: "self-center", color: "faint", children: ["+", order.items.length - 3, " more"] }))] }), _jsxs(Row, { justify: "between", className: "mt-4 border-t border-neutral-100", padding: "t-sm", children: [_jsxs(Span, { size: "sm", className: "text-[var(--appkit-color-text-muted)]", children: [order.currency ?? "", " Total"] }), _jsx(Span, { weight: "semibold", className: "text-[var(--appkit-color-text)]", children: formatCurrency(order.total, order.currency) })] })] }));
38
+ : undefined, onClick: onClick ? () => onClick(order) : undefined, className: `border border-neutral-200 ${onClick ? "cursor-pointer transition hover:shadow-md" : ""}`, rounded: "xl", surface: "default", children: [_jsxs(Row, { wrap: true, align: "start", justify: "between", gap: "3", children: [_jsxs(Div, { children: [_jsxs(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "xs", children: ["Order #", order.id.slice(-8).toUpperCase()] }), date && (_jsx(Text, { className: "mt-0.5", color: "faint", size: "xs", children: date }))] }), _jsx(Span, { size: "xs", weight: "semibold", className: `capitalize ${statusColor}`, rounded: "full", padding: "pill-md", children: labels[order.orderStatus] ?? order.orderStatus.replace(/_/g, " ") })] }), revealsRemaining > 0 && (_jsxs(Row, { gap: "sm", className: "mt-2", children: [_jsxs(Span, { size: "xs", weight: "semibold", className: CLS_PRIZE_BADGE, children: [revealsRemaining, " ", revealsRemaining === 1 ? "reveal" : "reveals", " pending"] }), earliestDeadline && (_jsxs(Span, { size: "xs", color: "muted", children: ["before ", new Date(earliestDeadline).toLocaleDateString(getDefaultLocale(), { month: "short", day: "numeric" })] }))] })), _jsxs(Row, { wrap: true, gap: "3", className: "mt-4", children: [order.items.slice(0, 3).map((item, i) => (_jsxs(Row, { gap: "sm", children: [item.image && (_jsx(Div, { className: "relative h-10 w-10 flex-shrink-0 overflow-hidden", rounded: "lg", children: _jsx(MediaImage, { src: item.image, alt: item.title, size: "thumbnail" }) })), _jsxs(Div, { children: [_jsx(Text, { truncate: true, size: "sm", weight: "medium", color: "primary", children: item.title }), _jsxs(Text, { size: "xs", color: "faint", children: ["\u00D7", item.quantity] })] })] }, i))), order.items.length > 3 && (_jsxs(Span, { size: "xs", className: "self-center", color: "faint", children: ["+", order.items.length - 3, " more"] }))] }), _jsxs(Row, { justify: "between", className: "mt-4 border-t border-neutral-100", padding: "t-sm", children: [_jsxs(Span, { size: "sm", className: "text-[var(--appkit-color-text-muted)]", children: [order.currency ?? "", " Total"] }), _jsx(Span, { weight: "semibold", className: "text-[var(--appkit-color-text)]", children: formatCurrency(order.total, order.currency) })] })] }));
38
39
  }
39
40
  export function OrdersList({ orders, isLoading, onOrderClick, totalPages = 1, currentPage = 1, onPageChange, emptyLabel = "No orders found", }) {
40
41
  if (isLoading) {
@@ -54,6 +54,7 @@ function PreOrderInfoSection({ title, productionStatus, maxPerUser, deliveryDate
54
54
  returnable: "Cancellable",
55
55
  freeShipping: "Free Shipping",
56
56
  codAvailable: "Cash on Delivery",
57
+ emiAvailable: "EMI Available",
57
58
  wishlistCount: (n) => `${n} wishlisted`,
58
59
  categoryProductCount: (n, cat) => `${n} in ${cat}`,
59
60
  } }), (categoryName || category || brand) && (_jsxs(Row, { gap: "sm", wrap: true, children: [category && (_jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(category)), className: "inline-flex items-center rounded-full border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] px-2.5 py-1 text-xs font-medium text-[var(--appkit-color-text-muted)] transition-colors hover:border-primary-300 hover:bg-primary-50 hover:text-primary-700 dark:hover:border-primary-700/60 dark:hover:bg-primary-900/20 dark:hover:text-primary-400", children: categoryName || category })), !category && categoryName && (_jsx(Span, { layout: "inline-flex", size: "xs", weight: "medium", border: "subtle", rounded: "full", padding: "pill-sm-tall", surface: "muted", color: "muted", children: categoryName })), brand && brandSlug && (_jsx(Link, { href: String(ROUTES.PUBLIC.BRAND_DETAIL(brandSlug)), className: "inline-flex items-center rounded-full border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] px-2.5 py-1 text-xs font-medium text-[var(--appkit-color-text-muted)] transition-colors hover:border-primary-300 hover:bg-primary-50 hover:text-primary-700 dark:hover:border-primary-700/60 dark:hover:bg-primary-900/20 dark:hover:text-primary-400", children: brand })), brand && !brandSlug && (_jsx(Span, { layout: "inline-flex", size: "xs", weight: "medium", border: "subtle", rounded: "full", padding: "pill-sm-tall", surface: "muted", color: "muted", children: brand }))] })), productFeatures && features.length > 0 && (_jsx(FeatureBadgeList, { productFeatureIds: features, features: productFeatures })), !productFeatures && features.length > 0 && (_jsxs(Div, { border: "subtle", surface: "muted", padding: "inline", rounded: "xl", children: [_jsx(Text, { className: "mb-2 tracking-wide", color: "muted", size: "xs", weight: "semibold", transform: "uppercase", children: "About this product" }), _jsx(Ul, { spacing: "comfortable", size: "sm", color: "primary", children: features.map((f, i) => (_jsxs(Li, { layout: "flex-start", gap: "2", children: [_jsx(Span, { className: "mt-0.5 flex-shrink-0 text-primary-500", children: "\u2022" }), f] }, i))) })] })), descriptionHtml && (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "text-sm leading-relaxed text-[var(--appkit-color-text-muted)] line-clamp-4" })), safeSeller && (_jsx(Div, { className: `${__P.p3}`, border: "subtle", rounded: "xl", surface: "muted", children: _jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Div, { children: [_jsx(Text, { className: "text-[10px] tracking-wide mb-0.5", color: "faint", transform: "uppercase", children: "Sold by" }), _jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: safeSeller })] }), storeHref && (_jsx(Link, { href: storeHref, className: "shrink-0 rounded-lg bg-primary/10 dark:bg-primary/20 px-3 py-1.5 text-xs font-semibold text-primary-700 dark:text-primary-300 hover:bg-primary/20 dark:hover:bg-primary/30 transition-colors", children: "Visit Store \u2192" }))] }) }))] }));
@@ -184,9 +184,8 @@ export const POST = createRouteHandler({
184
184
  auth: true,
185
185
  roles: ["seller", "moderator", "admin"],
186
186
  schema: productMutateSchema,
187
- handler: async ({ user, body }) => {
187
+ handler: async ({ body }) => {
188
188
  const payload = body;
189
- const userRecord = (user ?? {});
190
189
  const { db } = getProviders();
191
190
  if (!db) {
192
191
  return NextResponse.json({ success: false, error: "Database provider not registered" }, { status: 503 });
@@ -13,6 +13,7 @@ const CLS_REMOVE_BTN = "shrink-0 rounded-full p-1 text-zinc-400 hover:bg-[var(--
13
13
  import Link from "next/link";
14
14
  import { X } from "lucide-react";
15
15
  import { Button, Div, Heading, Row, Span, Stack, Text } from "../../../ui";
16
+ import { MediaImage } from "../../media/MediaImage";
16
17
  import { useSwipe } from "../../../react/hooks/useSwipe";
17
18
  import { formatCurrency } from "../../../utils/number.formatter";
18
19
  import { COMPARE_MAX_ITEMS } from "../constants/action-defs";
@@ -57,9 +58,7 @@ function FieldRow({ label, children }) {
57
58
  function CompareColumn({ item, productType, labels, onRemove, onClose }) {
58
59
  const href = detailHref(item, productType);
59
60
  const img = item.mainImage ?? item.images?.[0];
60
- return (_jsxs(Stack, { gap: "md", className: COLUMN_CARD_CLASS, children: [_jsxs(Row, { justify: "between", align: "start", gap: "sm", children: [_jsx(Link, { href: href, target: "_blank", rel: REL_NOOPENER, className: "text-sm font-semibold text-[var(--appkit-color-text)] hover:underline line-clamp-2", children: item.title ?? item.id }), onRemove && (_jsx("button", { type: "button", onClick: () => onRemove(item.id), "aria-label": `${labels.remove} ${item.title ?? item.id}`, className: CLS_REMOVE_BTN, children: _jsx(X, { className: "h-4 w-4" }) }))] }), _jsx(FieldRow, { label: labels.field.image, children: _jsx(Link, { href: href, target: "_blank", rel: REL_NOOPENER, children: _jsx(Div, { className: `relative aspect-square ${__O.hidden}`, rounded: "lg", surface: "subtle", children: img ? (
61
- // eslint-disable-next-line @next/next/no-img-element
62
- _jsx("img", { src: img, alt: item.title ?? item.id, className: "h-full w-full object-cover" })) : null }) }) }), _jsx(FieldRow, { label: labels.field.price, children: _jsx(Text, { size: "base", weight: "semibold", color: "primary", children: priceLabel(item) }) }), _jsx(FieldRow, { label: labels.field.condition, children: _jsx(Text, { children: item.condition ? _jsx(Span, { className: CHIP_CLASS, children: item.condition }) : "—" }) }), _jsx(FieldRow, { label: labels.field.brand, children: _jsx(Text, { children: item.brand ? _jsx(Span, { className: CHIP_CLASS, children: item.brand }) : "—" }) }), _jsx(FieldRow, { label: labels.field.category, children: _jsx(Text, { children: (Array.isArray(item.categoryNames) ? item.categoryNames[0] : item.categoryName) || (Array.isArray(item.categorySlugs) ? item.categorySlugs[0] : item.category) ? (_jsx(Span, { className: CHIP_CLASS, children: (Array.isArray(item.categoryNames) ? item.categoryNames[0] : item.categoryName) ?? (Array.isArray(item.categorySlugs) ? item.categorySlugs[0] : item.category) })) : ("—") }) }), _jsx(FieldRow, { label: labels.field.store, children: _jsx(Text, { size: "sm", color: "muted", children: item.storeName ?? "—" }) }), _jsx(Button, { asChild: true, variant: "primary", size: "sm", className: "mt-auto", children: _jsx(Link, { href: href, target: "_blank", rel: REL_NOOPENER, onClick: onClose, children: labels.view }) })] }));
61
+ return (_jsxs(Stack, { gap: "md", className: COLUMN_CARD_CLASS, children: [_jsxs(Row, { justify: "between", align: "start", gap: "sm", children: [_jsx(Link, { href: href, target: "_blank", rel: REL_NOOPENER, className: "text-sm font-semibold text-[var(--appkit-color-text)] hover:underline line-clamp-2", children: item.title ?? item.id }), onRemove && (_jsx("button", { type: "button", onClick: () => onRemove(item.id), "aria-label": `${labels.remove} ${item.title ?? item.id}`, className: CLS_REMOVE_BTN, children: _jsx(X, { className: "h-4 w-4" }) }))] }), _jsx(FieldRow, { label: labels.field.image, children: _jsx(Link, { href: href, target: "_blank", rel: REL_NOOPENER, children: _jsx(Div, { className: `relative aspect-square ${__O.hidden}`, rounded: "lg", surface: "subtle", children: img ? (_jsx(MediaImage, { src: img, alt: item.title ?? item.id, size: "card" })) : null }) }) }), _jsx(FieldRow, { label: labels.field.price, children: _jsx(Text, { size: "base", weight: "semibold", color: "primary", children: priceLabel(item) }) }), _jsx(FieldRow, { label: labels.field.condition, children: _jsx(Text, { children: item.condition ? _jsx(Span, { className: CHIP_CLASS, children: item.condition }) : "—" }) }), _jsx(FieldRow, { label: labels.field.brand, children: _jsx(Text, { children: item.brand ? _jsx(Span, { className: CHIP_CLASS, children: item.brand }) : "—" }) }), _jsx(FieldRow, { label: labels.field.category, children: _jsx(Text, { children: (Array.isArray(item.categoryNames) ? item.categoryNames[0] : item.categoryName) || (Array.isArray(item.categorySlugs) ? item.categorySlugs[0] : item.category) ? (_jsx(Span, { className: CHIP_CLASS, children: (Array.isArray(item.categoryNames) ? item.categoryNames[0] : item.categoryName) ?? (Array.isArray(item.categorySlugs) ? item.categorySlugs[0] : item.category) })) : ("—") }) }), _jsx(FieldRow, { label: labels.field.store, children: _jsx(Text, { size: "sm", color: "muted", children: item.storeName ?? "—" }) }), _jsx(Button, { asChild: true, variant: "primary", size: "sm", className: "mt-auto", children: _jsx(Link, { href: href, target: "_blank", rel: REL_NOOPENER, onClick: onClose, children: labels.view }) })] }));
63
62
  }
64
63
  function useFetchedProducts(productIds, passedItems, isOpen) {
65
64
  const [fetched, setFetched] = useState([]);
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Row } from "@mohasinac/appkit/ui";
3
- import { Dd, Dl, Dt, RichText, Stack, Text } from "../../../ui";
3
+ import { Anchor, Dd, Dl, Dt, RichText, Stack, Text } from "../../../ui";
4
4
  import { normalizeRichTextHtml } from "../../../utils/string.formatter";
5
5
  function renderFieldValue(f) {
6
6
  if (f.type === "boolean")
@@ -10,5 +10,5 @@ function renderFieldValue(f) {
10
10
  export function CustomSectionTabContent({ section }) {
11
11
  const html = section.text ? normalizeRichTextHtml(section.text) : null;
12
12
  const fields = section.fields?.filter((f) => f.key && f.value) ?? [];
13
- return (_jsxs(Stack, { gap: "md", children: [html && (_jsx(RichText, { html: html, proseClass: "prose prose-sm sm:prose max-w-none dark:prose-invert", className: "text-[var(--appkit-color-text-muted)]" })), fields.length > 0 && (_jsx(Dl, { divide: "subtle", rounded: "xl", border: "subtle", className: "overflow-hidden", children: fields.map((f, i) => (_jsxs(Row, { gap: "md", oddEven: "zebra", surface: "default", padding: "inline", children: [_jsx(Dt, { weight: "medium", className: "w-36 flex-shrink-0 capitalize", color: "primary", children: f.key }), _jsx(Dd, { className: "flex-1", color: "muted", children: f.type === "url" ? (_jsx("a", { href: f.value, target: "_blank", rel: "noopener noreferrer", className: "text-primary-600 hover:underline dark:text-primary-400 break-all", children: f.value })) : (renderFieldValue(f)) })] }, i))) })), !html && fields.length === 0 && (_jsx(Text, { size: "sm", color: "faint", children: "No content in this section." }))] }));
13
+ return (_jsxs(Stack, { gap: "md", children: [html && (_jsx(RichText, { html: html, proseClass: "prose prose-sm sm:prose max-w-none dark:prose-invert", className: "text-[var(--appkit-color-text-muted)]" })), fields.length > 0 && (_jsx(Dl, { divide: "subtle", rounded: "xl", border: "subtle", className: "overflow-hidden", children: fields.map((f, i) => (_jsxs(Row, { gap: "md", oddEven: "zebra", surface: "default", padding: "inline", children: [_jsx(Dt, { weight: "medium", className: "w-36 flex-shrink-0 capitalize", color: "primary", children: f.key }), _jsx(Dd, { className: "flex-1", color: "muted", children: f.type === "url" ? (_jsx(Anchor, { href: f.value, tone: "none", underline: "hover", className: "text-primary-600 dark:text-primary-400 break-all", children: f.value })) : (renderFieldValue(f)) })] }, i))) })), !html && fields.length === 0 && (_jsx(Text, { size: "sm", color: "faint", children: "No content in this section." }))] }));
14
14
  }
@@ -36,12 +36,6 @@ function FeatureIcon({ icon, className = ICON_SIZE_CLASS, }) {
36
36
  const Cmp = FEATURE_ICON_MAP[icon] ?? FEATURE_ICON_MAP.tag;
37
37
  return _jsx(Cmp, { className: className });
38
38
  }
39
- function colorStyleFor(iconColor) {
40
- if (!iconColor)
41
- return undefined;
42
- const ref = `var(${iconColor})`;
43
- return { color: ref, borderColor: ref };
44
- }
45
39
  export function FeatureBadge({ featureId, features }) {
46
40
  const f = features.find((x) => x.id === featureId);
47
41
  const spanRef = useRef(null);