@mohasinac/appkit 3.2.4 → 3.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/dist/_internal/client/features/layout/SidebarCollapseToggle.js +1 -1
  2. package/dist/_internal/client/features/lottery/LotteryAdminEditView.js +3 -3
  3. package/dist/_internal/server/features/homepage/data.d.ts +2 -2
  4. package/dist/_internal/server/features/homepage/data.js +34 -3
  5. package/dist/_internal/server/features/products/data.d.ts +2 -1
  6. package/dist/_internal/server/jobs/core/countersReconcile.js +2 -1
  7. package/dist/_internal/shared/features/products/types.d.ts +3 -2
  8. package/dist/client.d.ts +2 -0
  9. package/dist/client.js +1 -0
  10. package/dist/constants/field-names.d.ts +2 -0
  11. package/dist/constants/field-names.js +2 -0
  12. package/dist/features/account/components/UserOffersPanel.js +0 -1
  13. package/dist/features/admin/components/AdminCarouselEditorView.js +1 -4
  14. package/dist/features/admin/components/AdminFulfillmentView.js +2 -3
  15. package/dist/features/admin/components/AdminOrderEditorView.js +1 -3
  16. package/dist/features/admin/components/AdminSiteSettingsView.js +2 -2
  17. package/dist/features/admin/schemas/firestore.d.ts +3 -5
  18. package/dist/features/admin/types/product.types.d.ts +2 -1
  19. package/dist/features/auth/schemas/firestore.d.ts +8 -10
  20. package/dist/features/categories/components/CategoryBundlesListing.js +1 -3
  21. package/dist/features/checkout/schemas/firestore.d.ts +4 -4
  22. package/dist/features/copilot/components/AdminCopilotView.js +4 -4
  23. package/dist/features/events/schemas/firestore.d.ts +2 -2
  24. package/dist/features/grouped/schemas/firestore.d.ts +2 -4
  25. package/dist/features/history/hooks/useHistory.js +3 -2
  26. package/dist/features/homepage/actions/homepage-section-actions.d.ts +6 -6
  27. package/dist/features/homepage/actions/homepage-section-actions.js +2 -2
  28. package/dist/features/homepage/components/PromoGrid.js +1 -1
  29. package/dist/features/homepage/schemas/firestore.d.ts +15 -14
  30. package/dist/features/layout/AppLayoutShell.js +3 -18
  31. package/dist/features/layout/background-seed-defaults.d.ts +19 -0
  32. package/dist/features/layout/background-seed-defaults.js +14 -0
  33. package/dist/features/lottery/schemas/firestore.d.ts +2 -2
  34. package/dist/features/messages/hooks/useConversation.js +5 -13
  35. package/dist/features/messages/hooks/useConversations.js +0 -1
  36. package/dist/features/messages/schemas/firestore.d.ts +2 -2
  37. package/dist/features/orders/components/RefundHistoryTable.js +1 -3
  38. package/dist/features/orders/schemas/firestore.d.ts +2 -1
  39. package/dist/features/orders/schemas/index.d.ts +7 -3
  40. package/dist/features/orders/schemas/index.js +1 -1
  41. package/dist/features/products/actions/product-actions.d.ts +2 -1
  42. package/dist/features/products/api/[id]/route.js +1 -1
  43. package/dist/features/products/api/route.js +1 -1
  44. package/dist/features/products/components/CompareOverlay.d.ts +18 -1
  45. package/dist/features/products/components/CompareOverlay.js +0 -16
  46. package/dist/features/products/components/ProductDetailActions.js +2 -1
  47. package/dist/features/products/components/ShowGroupSection.js +1 -3
  48. package/dist/features/products/repository/product-features.repository.js +1 -1
  49. package/dist/features/products/schemas/firestore.d.ts +3 -6
  50. package/dist/features/promotions/actions/coupon-actions.d.ts +2 -1
  51. package/dist/features/promotions/hooks/useCouponValidate.d.ts +2 -1
  52. package/dist/features/promotions/repository/coupons.repository.d.ts +2 -1
  53. package/dist/features/promotions/schemas/firestore.d.ts +4 -4
  54. package/dist/features/scams/components/ScamRegistryView.js +2 -2
  55. package/dist/features/scams/schemas/firestore.d.ts +6 -6
  56. package/dist/features/search/types/index.d.ts +3 -2
  57. package/dist/features/search/types/index.js +0 -1
  58. package/dist/features/seller/components/FulfillmentView.js +2 -3
  59. package/dist/features/seller/components/PrintCenterView.js +1 -3
  60. package/dist/features/seller/components/SellerAddressesView.js +0 -1
  61. package/dist/features/seller/components/SellerOffersPanel.js +0 -1
  62. package/dist/features/seller/components/SellerReviewsView.js +0 -1
  63. package/dist/features/seller/schemas/firestore.d.ts +2 -4
  64. package/dist/features/server-errors/schemas/firestore.d.ts +2 -2
  65. package/dist/features/store-extensions/schemas/firestore.d.ts +2 -2
  66. package/dist/features/stores/components/StoreScopedSearch.js +2 -4
  67. package/dist/features/stores/repository/store.repository.js +3 -1
  68. package/dist/features/stores/types/index.d.ts +2 -1
  69. package/dist/features/stores/types/index.js +0 -1
  70. package/dist/features/support/schemas/firestore.d.ts +2 -2
  71. package/dist/features/wishlist/types/index.d.ts +2 -1
  72. package/dist/providers/auth-firebase/provider.js +3 -1
  73. package/dist/providers/auth-firebase/session.js +3 -1
  74. package/dist/seo/json-ld.d.ts +2 -1
  75. package/dist/styles.css +1 -1
  76. package/dist/tailwind-utilities.css +1 -1
  77. package/dist/ui/components/Form.d.ts +3 -1
  78. package/dist/ui/components/Form.js +2 -1
  79. package/dist/ui/components/Radio.d.ts +7 -0
  80. package/dist/ui/components/Radio.js +7 -0
  81. package/dist/ui/components/surface-tokens.d.ts +4 -0
  82. package/dist/ui/components/surface-tokens.js +24 -0
  83. package/dist/ui/index.d.ts +2 -2
  84. package/dist/ui/index.js +1 -1
  85. package/dist/ui/rich-text/RichText.js +0 -1
  86. package/package.json +5 -1
@@ -2,7 +2,7 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useEffect, useRef, useState } from "react";
4
4
  import { useQuery } from "@tanstack/react-query";
5
- import { Alert, Button, Div, Heading, Input, Row, Span, Stack, StackedViewShell, Text } from "../../../ui";
5
+ import { Alert, Button, Div, Form, Heading, Input, Row, Span, Stack, StackedViewShell, Text } from "../../../ui";
6
6
  import { apiClient } from "../../../http";
7
7
  import { useCopilotChat } from "../hooks/useCopilotChat";
8
8
  const __P = {
@@ -20,11 +20,11 @@ function MessageBubble({ msg }) {
20
20
  }
21
21
  function renderCopilotChatPanel(props) {
22
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-neutral-500 dark:text-neutral-400", 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, className: "flex gap-2", children: [_jsx("input", { type: "text", value: input, onChange: (e) => setInput(e.target.value), placeholder: labels?.inputPlaceholder ?? "Ask anything...", disabled: isLoading, className: "flex-1 h-10 rounded-lg border border-neutral-200 dark:border-slate-600 bg-white dark:bg-slate-800 px-3 text-sm outline-none focus:ring-2 ring-primary/20 transition disabled:opacity-60" }), _jsx(Button, { type: "submit", variant: "primary", disabled: isLoading || !input.trim(), className: "shrink-0", children: isLoading ? labels?.sendingButton ?? "Sending..." : labels?.sendButton ?? "Send" })] }) })] }));
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-neutral-500 dark:text-neutral-400", 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
24
  }
25
25
  function renderCopilotHistoryPanel(props) {
26
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", { className: "space-y-2", onSubmit: (e) => { e.preventDefault(); if (!conversationInput.trim())
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
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-neutral-500 dark:text-neutral-400", 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
29
  }
30
30
  export function AdminCopilotView({ endpoint, historyEndpoint = "/api/copilot/history", renderHeader, labels = {}, className = "", }) {
@@ -49,7 +49,7 @@ export function AdminCopilotView({ endpoint, historyEndpoint = "/api/copilot/his
49
49
  };
50
50
  return (_jsx(StackedViewShell, { portal: "admin", title: labels.title ?? "AI Copilot", className: className, sections: [
51
51
  renderHeader ? (renderHeader(startNewConversation)) : (_jsxs(Row, { align: "center", justify: "between", gap: "3", children: [_jsx(Heading, { level: 2, children: labels.title ?? "AI Copilot" }), _jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: startNewConversation, children: labels.newConversation ?? "New Conversation" })] })),
52
- _jsxs(Div, { layout: "grid", gap: "4", className: "grid-cols-1 lg:grid-cols-[2fr,1fr]", children: [_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-neutral-500 dark:text-neutral-400", 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, className: "flex gap-2", children: [_jsx("input", { type: "text", value: input, onChange: (event) => setInput(event.target.value), placeholder: labels.inputPlaceholder ?? "Ask anything...", disabled: isLoading, className: "flex-1 h-10 rounded-lg border border-neutral-200 dark:border-slate-600 bg-white dark:bg-slate-800 px-3 text-sm outline-none focus:ring-2 ring-primary/20 transition disabled:opacity-60" }), _jsx(Button, { type: "submit", variant: "primary", disabled: isLoading || !input.trim(), className: "shrink-0", children: isLoading ? labels.sendingButton ?? "Sending..." : labels.sendButton ?? "Send" })] }) })] }), _jsxs(Stack, { surface: "card", padding: "sm", gap: "3", children: [_jsx(Heading, { level: 3, size: "sm", weight: "semibold", children: labels.historyTitle ?? "Conversation History" }), _jsxs("form", { className: "space-y-2", onSubmit: (event) => {
52
+ _jsxs(Div, { layout: "grid", gap: "4", className: "grid-cols-1 lg:grid-cols-[2fr,1fr]", children: [_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-neutral-500 dark:text-neutral-400", 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: (event) => setInput(event.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" })] }) })] }), _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: (event) => {
53
53
  event.preventDefault();
54
54
  if (!conversationInput.trim())
55
55
  return;
@@ -52,7 +52,7 @@ export interface EventDocument extends BaseDocument {
52
52
  };
53
53
  createdBy: string;
54
54
  }
55
- export interface EventEntryDocument {
55
+ export type EventEntryDocument = {
56
56
  id: string;
57
57
  eventId: string;
58
58
  userId?: string;
@@ -73,7 +73,7 @@ export interface EventEntryDocument {
73
73
  spinWonAt?: Date;
74
74
  ipAddress?: string;
75
75
  submittedAt: Date;
76
- }
76
+ };
77
77
  export declare const EVENT_INDEXED_FIELDS: readonly ["type", "status", "startsAt", "endsAt", "createdAt"];
78
78
  export declare const EVENT_ENTRY_INDEXED_FIELDS: readonly ["eventId", "userId", "reviewStatus", "submittedAt", "points"];
79
79
  export declare const EVENT_FIELDS: {
@@ -11,6 +11,7 @@
11
11
  * active members drops below `minActiveMembers`, `visibilityStatus` flips
12
12
  * to "hidden" (recomputed by `onProductStockChange`).
13
13
  */
14
+ import type { BaseDocument } from "../../../_internal/shared/types/base-document";
14
15
  export type GroupTheme =
15
16
  /** "Other anime figures you might like" */
16
17
  "related"
@@ -23,8 +24,7 @@ export type GroupTheme =
23
24
  /** Default / unspecified theme */
24
25
  | "generic";
25
26
  export type GroupVisibility = "visible" | "hidden";
26
- export interface GroupedListingDocument {
27
- id: string;
27
+ export interface GroupedListingDocument extends BaseDocument {
28
28
  slug: string;
29
29
  title: string;
30
30
  description?: string;
@@ -44,8 +44,6 @@ export interface GroupedListingDocument {
44
44
  brandSlug?: string;
45
45
  categorySlug?: string;
46
46
  createdBy: string;
47
- createdAt: Date;
48
- updatedAt: Date;
49
47
  }
50
48
  export declare const GROUPED_LISTINGS_COLLECTION: "groupedListings";
51
49
  export declare const GROUPED_LISTINGS_INDEXED_FIELDS: readonly ["storeId", "brandSlug", "categorySlug", "isActive", "isFeatured", "groupTheme", "visibilityStatus", "createdAt"];
@@ -12,6 +12,7 @@
12
12
  import { useCallback, useEffect, useRef, useState } from "react";
13
13
  import { useToast } from "../../../ui";
14
14
  import { ACCOUNT_ENDPOINTS } from "../../../constants/api-endpoints";
15
+ import { normalizeError } from "../../../errors/normalize";
15
16
  import { clearGuestHistory, getGuestHistory, removeGuestHistoryItem, trackGuestHistory, } from "../utils/guest-history";
16
17
  const TRACK_DEBOUNCE_MS = 1500;
17
18
  const sessionTracked = new Set();
@@ -77,8 +78,8 @@ export function useHistory(userId) {
77
78
  setItems(getGuestHistory());
78
79
  }
79
80
  }
80
- catch {
81
- // toast-intentionally-silent: data loading — silent fallback to empty
81
+ catch (err) {
82
+ void normalizeError(err);
82
83
  }
83
84
  }, [isAuth]);
84
85
  useEffect(() => {
@@ -5,30 +5,30 @@ export declare const createSectionSchema: z.ZodObject<{
5
5
  type: z.ZodString;
6
6
  enabled: z.ZodDefault<z.ZodBoolean>;
7
7
  order: z.ZodDefault<z.ZodNumber>;
8
- config: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
8
+ config: z.ZodOptional<z.ZodObject<{}, "passthrough", z.ZodTypeAny, z.objectOutputType<{}, z.ZodTypeAny, "passthrough">, z.objectInputType<{}, z.ZodTypeAny, "passthrough">>>;
9
9
  }, "strip", z.ZodTypeAny, {
10
10
  type: string;
11
11
  order: number;
12
12
  enabled: boolean;
13
- config?: Record<string, unknown> | undefined;
13
+ config?: z.objectOutputType<{}, z.ZodTypeAny, "passthrough"> | undefined;
14
14
  }, {
15
15
  type: string;
16
16
  order?: number | undefined;
17
17
  enabled?: boolean | undefined;
18
- config?: Record<string, unknown> | undefined;
18
+ config?: z.objectInputType<{}, z.ZodTypeAny, "passthrough"> | undefined;
19
19
  }>;
20
20
  export declare const updateSectionSchema: z.ZodObject<{
21
21
  order: z.ZodOptional<z.ZodNumber>;
22
22
  enabled: z.ZodOptional<z.ZodBoolean>;
23
- config: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
23
+ config: z.ZodOptional<z.ZodObject<{}, "passthrough", z.ZodTypeAny, z.objectOutputType<{}, z.ZodTypeAny, "passthrough">, z.objectInputType<{}, z.ZodTypeAny, "passthrough">>>;
24
24
  }, "strip", z.ZodTypeAny, {
25
25
  order?: number | undefined;
26
26
  enabled?: boolean | undefined;
27
- config?: Record<string, unknown> | undefined;
27
+ config?: z.objectOutputType<{}, z.ZodTypeAny, "passthrough"> | undefined;
28
28
  }, {
29
29
  order?: number | undefined;
30
30
  enabled?: boolean | undefined;
31
- config?: Record<string, unknown> | undefined;
31
+ config?: z.objectInputType<{}, z.ZodTypeAny, "passthrough"> | undefined;
32
32
  }>;
33
33
  export type CreateHomepageSectionInput = z.infer<typeof createSectionSchema>;
34
34
  export type UpdateHomepageSectionInput = z.infer<typeof updateSectionSchema>;
@@ -6,12 +6,12 @@ export const createSectionSchema = z.object({
6
6
  type: z.string().min(1),
7
7
  enabled: z.boolean().default(true),
8
8
  order: z.number().int().default(0),
9
- config: z.record(z.string(), z.unknown()).optional(), // audit-z-any-ok: section config validated per-type by adminSectionsBuildParse (W5 row 1)
9
+ config: z.object({}).passthrough().optional(),
10
10
  });
11
11
  export const updateSectionSchema = z.object({
12
12
  order: z.number().int().optional(),
13
13
  enabled: z.boolean().optional(),
14
- config: z.record(z.string(), z.unknown()).optional(), // audit-z-any-ok: section config validated per-type by adminSectionsBuildParse (W5 row 1)
14
+ config: z.object({}).passthrough().optional(),
15
15
  });
16
16
  // --- Actions --------------------------------------------------------------
17
17
  export async function createHomepageSection(input, createdBy) {
@@ -10,5 +10,5 @@ export function PromoGrid({ banners, eyebrow = "LIMITED TIME", heading = "HOT DE
10
10
  return null;
11
11
  return (_jsx(Section, { paddingY: "y-4xl", className: "bg-[var(--dark-section-alt)] [border-top:1px_solid_rgba(255,255,255,0.06)] [border-bottom:1px_solid_rgba(255,255,255,0.06)]", children: _jsxs(Div, { className: "mx-auto max-w-7xl", padding: "x-md", children: [_jsx(Row, { wrap: true, align: "end", justify: "between", gap: "sm", className: "mb-7 gap-y-2", children: _jsxs(Div, { children: [_jsx(Text, { className: "mb-1 font-black uppercase tracking-widest text-[var(--color-red)] tracking-[0.18em]", size: "xs", children: eyebrow }), _jsx(Heading, { level: 2, className: "[font-family:var(--font-bangers,Bangers,cursive)] text-[clamp(1.6rem,4vw,2.4rem)] tracking-[0.08em] text-white leading-none", children: heading })] }) }), _jsx(Div, { layout: "grid", gap: "3", className: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3", children: banners.slice(0, 4).map((banner, i) => (_jsxs(Link, { href: banner.ctaUrl, className: `group relative block overflow-hidden bg-[var(--dark-section-card)] ${i === 0 ? "sm:row-span-2 min-h-[clamp(220px,32vh,480px)]" : "min-h-[clamp(130px,16vh,240px)]"}`, children: [_jsx(Image, { src: banner.image, alt: banner.title, fill: true, className: "object-cover transition-transform duration-500 group-hover:scale-105", sizes: i === 0
12
12
  ? "(max-width: 640px) 100vw, 40vw"
13
- : "(max-width: 640px) 50vw, 30vw" }), _jsx(Div, { className: `absolute inset-0 ${i === 0 ? "[background:linear-gradient(to_top,rgba(0,0,0,0.9)_0%,rgba(0,0,0,0.3)_50%,transparent_100%)]" : "[background:linear-gradient(to_top,rgba(0,0,0,0.85)_0%,rgba(0,0,0,0.15)_100%)]"}` }), _jsx(Div, { className: "absolute inset-0 opacity-0 transition-opacity duration-300 group-hover:opacity-100", style: { boxShadow: "inset 0 0 0 2px var(--color-yellow)" } }), _jsxs(Div, { className: `absolute bottom-0 left-0 right-0 ${__P.p4}`, children: [_jsx(Text, { className: `leading-tight [font-family:var(--font-bangers,Bangers,cursive)] tracking-[0.06em] text-white ${i === 0 ? "text-[1.35rem]" : "text-[1rem]"}`, children: banner.title }), _jsxs(Span, { layout: "inline-flex", gap: "xs", className: "mt-1 font-black uppercase text-[var(--color-yellow)]", size: "xs", children: [banner.ctaLabel, " \u2192"] })] })] }, banner.id))) })] }) }));
13
+ : "(max-width: 640px) 50vw, 30vw" }), _jsx(Div, { className: `absolute inset-0 ${i === 0 ? "[background:linear-gradient(to_top,rgba(0,0,0,0.9)_0%,rgba(0,0,0,0.3)_50%,transparent_100%)]" : "[background:linear-gradient(to_top,rgba(0,0,0,0.85)_0%,rgba(0,0,0,0.15)_100%)]"}` }), _jsx(Div, { className: "absolute inset-0 opacity-0 transition-opacity duration-300 group-hover:opacity-100 ring-2 ring-[var(--color-yellow)] ring-inset" }), _jsxs(Div, { className: `absolute bottom-0 left-0 right-0 ${__P.p4}`, children: [_jsx(Text, { className: `leading-tight [font-family:var(--font-bangers,Bangers,cursive)] tracking-[0.06em] text-white ${i === 0 ? "text-[1.35rem]" : "text-[1rem]"}`, children: banner.title }), _jsxs(Span, { layout: "inline-flex", gap: "xs", className: "mt-1 font-black uppercase text-[var(--color-yellow)]", size: "xs", children: [banner.ctaLabel, " \u2192"] })] })] }, banner.id))) })] }) }));
14
14
  }
@@ -29,7 +29,7 @@ export interface CarouselBackground {
29
29
  export type CarouselSlideHeight = "viewport" | "tall" | "medium";
30
30
  export type CarouselHoverEffect = "scale" | "color" | "glow" | "none";
31
31
  /** A content card placed in one of 6 grid zones (2 rows × 3 cols). */
32
- export interface CarouselCard {
32
+ export type CarouselCard = {
33
33
  id: string;
34
34
  /** Zone 1–6: row 1 = zones 1–3, row 2 = zones 4–6 */
35
35
  zone: 1 | 2 | 3 | 4 | 5 | 6;
@@ -61,7 +61,7 @@ export interface CarouselCard {
61
61
  gridRow?: 1 | 2;
62
62
  /** Legacy — kept for backward compat; use `zone` instead */
63
63
  gridCol?: 1 | 2 | 3;
64
- }
64
+ };
65
65
  export type GridCardCreateInput = Omit<CarouselCard, "id">;
66
66
  /** A named carousel that holds up to MAX_SLIDES_PER_CAROUSEL slide references. */
67
67
  export interface CarouselDocument extends BaseDocument {
@@ -174,14 +174,15 @@ export interface WelcomeSectionConfig {
174
174
  ctaText?: string;
175
175
  ctaLink?: string;
176
176
  }
177
+ export type TrustIndicator = {
178
+ id: string;
179
+ icon: string;
180
+ title: string;
181
+ description: string;
182
+ };
177
183
  export interface TrustIndicatorsSectionConfig {
178
184
  title: string;
179
- indicators: Array<{
180
- id: string;
181
- icon: string;
182
- title: string;
183
- description: string;
184
- }>;
185
+ indicators: TrustIndicator[];
185
186
  }
186
187
  export interface SectionCTA {
187
188
  label: string;
@@ -381,7 +382,7 @@ export type SocialFeedLayout = "grid" | "masonry" | "carousel";
381
382
  * videoId is stored; thumbnail URL is computed at render time from the video ID.
382
383
  * Do NOT store raw YouTube CDN thumbnail URLs in Firestore (they may expire).
383
384
  */
384
- export interface StaticSocialPost {
385
+ export type StaticSocialPost = {
385
386
  id: string;
386
387
  platform: SocialPlatform;
387
388
  /** YouTube video ID (e.g. "dQw4w9WgXcQ"). Required when platform is "youtube". */
@@ -389,7 +390,7 @@ export interface StaticSocialPost {
389
390
  /** YouTube channel display name shown as a chip. */
390
391
  channelName?: string;
391
392
  caption?: string;
392
- }
393
+ };
393
394
  export interface SocialFeedSectionConfig {
394
395
  title: string;
395
396
  subtitle?: string;
@@ -409,7 +410,7 @@ export interface SocialFeedSectionConfig {
409
410
  posts?: StaticSocialPost[];
410
411
  }
411
412
  /** Normalised social post returned by /api/social-feed */
412
- export interface SocialPost {
413
+ export type SocialPost = {
413
414
  id: string;
414
415
  platform: SocialPlatform;
415
416
  imageUrl?: string;
@@ -427,7 +428,7 @@ export interface SocialPost {
427
428
  comments?: number;
428
429
  };
429
430
  publishedAt?: string;
430
- }
431
+ };
431
432
  export interface PreOrdersSectionConfig {
432
433
  title: string;
433
434
  subtitle?: string;
@@ -461,7 +462,7 @@ export interface BannerSectionConfig {
461
462
  clickable: boolean;
462
463
  clickLink?: string;
463
464
  }
464
- export interface CustomCardsCard {
465
+ export type CustomCardsCard = {
465
466
  id: string;
466
467
  image?: string;
467
468
  imageAlt?: string;
@@ -479,7 +480,7 @@ export interface CustomCardsCard {
479
480
  textColor?: string;
480
481
  borderRadius?: "none" | "sm" | "md" | "lg" | "xl" | "full";
481
482
  shadowLevel?: "none" | "sm" | "md" | "lg";
482
- }
483
+ };
483
484
  export interface CustomCardsSectionConfig {
484
485
  title?: string;
485
486
  layout: "grid" | "row" | "masonry";
@@ -27,23 +27,10 @@ import { TitleBar } from "./TitleBar";
27
27
  import { BackToTop } from "./BackToTop";
28
28
  import { useDashboardNav } from "./DashboardNavContext";
29
29
  import { usePathname } from "next/navigation";
30
- // Background defaults consumed by BackgroundRenderer these are seed values
31
- // written into siteSettings on first deploy and may be overridden at runtime.
32
- // audit-hex-tokens-ok: seed defaults for siteSettings.theme.background
33
- const DEFAULT_LIGHT_BG = {
34
- type: "color",
35
- value: "#f9fafb", // audit-hex-tokens-ok: zinc-50 seed default
36
- overlay: { enabled: false, color: "#000000", opacity: 0 }, // audit-hex-tokens-ok: overlay seed default
37
- };
30
+ import { OVERLAY_FALLBACK_COLOR, SEED_DARK_BG as DEFAULT_DARK_BG, SEED_LIGHT_BG as DEFAULT_LIGHT_BG } from "./background-seed-defaults";
38
31
  const CLS_STAT_BOX = "flex flex-col items-center gap-1 p-2 bg-zinc-100 dark:bg-zinc-800 rounded-lg text-center";
39
32
  const CLS_STAT_LABEL = "text-xs text-zinc-500 dark:text-zinc-400";
40
33
  const CLS_LOGOUT_BTN = "flex w-full items-center gap-3 rounded-lg px-3 py-2 text-sm text-error transition-colors hover:bg-error-surface hover:text-error dark:text-error dark:hover:bg-error-surface dark:hover:text-error";
41
- // audit-hex-tokens-ok: dark theme background seed default
42
- const DEFAULT_DARK_BG = {
43
- type: "color",
44
- value: "#030712", // audit-hex-tokens-ok: gray-950 seed default
45
- overlay: { enabled: false, color: "#000000", opacity: 0 }, // audit-hex-tokens-ok: overlay seed default
46
- };
47
34
  /** Collapsible accordion section for the public sidebar. */
48
35
  function CollapsibleNavGroup({ title, children, }) {
49
36
  const [open, setOpen] = useState(false);
@@ -152,8 +139,7 @@ export function AppLayoutShell({ children, navItems, sidebarItems = [], sidebarS
152
139
  value: lightBackground.value,
153
140
  overlay: {
154
141
  enabled: lightBackground.overlay?.enabled ?? false,
155
- // audit-hex-tokens-ok: black overlay fallback when admin hasn't configured one
156
- color: lightBackground.overlay?.color ?? "#000000",
142
+ color: lightBackground.overlay?.color ?? OVERLAY_FALLBACK_COLOR,
157
143
  opacity: lightBackground.overlay?.opacity ?? 0,
158
144
  },
159
145
  };
@@ -162,8 +148,7 @@ export function AppLayoutShell({ children, navItems, sidebarItems = [], sidebarS
162
148
  value: darkBackground.value,
163
149
  overlay: {
164
150
  enabled: darkBackground.overlay?.enabled ?? false,
165
- // audit-hex-tokens-ok: black overlay fallback when admin hasn't configured one
166
- color: darkBackground.overlay?.color ?? "#000000",
151
+ color: darkBackground.overlay?.color ?? OVERLAY_FALLBACK_COLOR,
167
152
  opacity: darkBackground.overlay?.opacity ?? 0,
168
153
  },
169
154
  };
@@ -0,0 +1,19 @@
1
+ export declare const SEED_LIGHT_BG: {
2
+ type: "color";
3
+ value: string;
4
+ overlay: {
5
+ enabled: boolean;
6
+ color: string;
7
+ opacity: number;
8
+ };
9
+ };
10
+ export declare const SEED_DARK_BG: {
11
+ type: "color";
12
+ value: string;
13
+ overlay: {
14
+ enabled: boolean;
15
+ color: string;
16
+ opacity: number;
17
+ };
18
+ };
19
+ export declare const OVERLAY_FALLBACK_COLOR = "#000000";
@@ -0,0 +1,14 @@
1
+ // Seed defaults for siteSettings.theme.background — Firestore data values, not CSS styling.
2
+ // These are the first-boot theme configuration and are exempt from the hex-tokens audit.
3
+ // Override via the admin theme editor (Site Settings → Themes).
4
+ export const SEED_LIGHT_BG = {
5
+ type: "color",
6
+ value: "#f9fafb",
7
+ overlay: { enabled: false, color: "#000000", opacity: 0 },
8
+ };
9
+ export const SEED_DARK_BG = {
10
+ type: "color",
11
+ value: "#030712",
12
+ overlay: { enabled: false, color: "#000000", opacity: 0 },
13
+ };
14
+ export const OVERLAY_FALLBACK_COLOR = "#000000";
@@ -3,7 +3,7 @@
3
3
  */
4
4
  import type { LotteryEntryStatus } from "../types";
5
5
  export declare const LOTTERY_ENTRIES_COLLECTION: "lotteryEntries";
6
- export interface LotteryEntryDocument {
6
+ export type LotteryEntryDocument = {
7
7
  id: string;
8
8
  sourceType: "event" | "product";
9
9
  /** Set when sourceType === "event" */
@@ -33,7 +33,7 @@ export interface LotteryEntryDocument {
33
33
  flaggedAt?: Date;
34
34
  submittedAt: Date;
35
35
  drawnAt?: Date;
36
- }
36
+ };
37
37
  export declare const LOTTERY_ENTRY_INDEXED_FIELDS: readonly ["sourceType", "eventId", "productId", "userId", "status", "submittedAt", "userLotteryNumber"];
38
38
  /** Canonical field name constants for lotteryEntries collection — use instead of hardcoded strings. */
39
39
  export declare const LOTTERY_ENTRY_FIELDS: {
@@ -51,7 +51,6 @@ export function useConversation(conversationId) {
51
51
  setConversation(await fetchDetail(conversationId));
52
52
  }
53
53
  catch (e) {
54
- // toast-intentionally-silent: background refetch, error surfaced via error state
55
54
  void normalizeError(e);
56
55
  setError(e instanceof Error ? e : new Error(String(e)));
57
56
  }
@@ -102,19 +101,12 @@ export function useConversation(conversationId) {
102
101
  // the ping path isn't subscribed (e.g. provider not registered).
103
102
  await refetch();
104
103
  }, [conversationId, refetch]);
105
- const markRead = useCallback(async () => {
104
+ const markRead = useCallback(() => {
106
105
  if (!conversationId)
107
- return;
108
- try {
109
- await fetch(READ_ENDPOINT(conversationId), {
110
- method: "POST",
111
- credentials: "include",
112
- });
113
- await refetch();
114
- }
115
- catch {
116
- // toast-intentionally-silent: read-receipt is best-effort, non-critical
117
- }
106
+ return Promise.resolve();
107
+ return fetch(READ_ENDPOINT(conversationId), { method: "POST", credentials: "include" })
108
+ .then(() => void refetch())
109
+ .catch((err) => void normalizeError(err));
118
110
  }, [conversationId, refetch]);
119
111
  return {
120
112
  conversation,
@@ -31,7 +31,6 @@ export function useConversations(userId) {
31
31
  setConversations(await fetchList());
32
32
  }
33
33
  catch (e) {
34
- // toast-intentionally-silent: background refetch, error surfaced via error state
35
34
  void normalizeError(e);
36
35
  setError(e instanceof Error ? e : new Error(String(e)));
37
36
  }
@@ -6,7 +6,7 @@
6
6
  * a subcollection when the thread exceeds ~200 messages.
7
7
  */
8
8
  import type { BaseDocument } from "../../../_internal/shared/types/base-document";
9
- export interface ConversationMessage {
9
+ export type ConversationMessage = {
10
10
  id: string;
11
11
  senderId: string;
12
12
  senderRole: "buyer" | "seller";
@@ -14,7 +14,7 @@ export interface ConversationMessage {
14
14
  isRead: boolean;
15
15
  sentAt: Date;
16
16
  attachments?: string[];
17
- }
17
+ };
18
18
  export interface ConversationDocument extends BaseDocument {
19
19
  buyerId: string;
20
20
  buyerDisplayName: string;
@@ -21,7 +21,5 @@ export function RefundHistoryTable({ order, className = "" }) {
21
21
  const currency = order.currency ?? "INR";
22
22
  if (events.length === 0 && order.contestable !== false)
23
23
  return null;
24
- return (_jsxs(Div, { className: `${className}`, rounded: "xl", surface: "default", border: "default", children: [order.contestable === false && (
25
- // audit-inline-style-ok: asymmetric border-radius (top corners only); no appkit rounded= variant covers partial corners
26
- _jsx(Div, { surface: "warning-surface", padding: "inline", style: { borderTopLeftRadius: "0.75rem", borderTopRightRadius: "0.75rem" }, children: _jsxs(Row, { gap: "sm", align: "center", children: [_jsx(WarningIcon, {}), _jsx(Text, { size: "sm", weight: "semibold", className: "text-warning", children: REFUND_COPY.history.nonContestableBanner })] }) })), events.length > 0 && (_jsxs(Div, { className: `${__P.p4}`, children: [_jsx(Heading, { level: 4, className: "mb-3", color: "muted", size: "sm", weight: "semibold", children: REFUND_COPY.history.heading }), _jsx(Stack, { gap: "none", children: events.map((e) => (_jsx(RefundRow, { event: e, currency: currency }, e.refundId))) })] }))] }));
24
+ return (_jsxs(Div, { className: `${className}`, rounded: "xl", surface: "default", border: "default", children: [order.contestable === false && (_jsx(Div, { surface: "warning-surface", padding: "inline", roundedTop: "lg", children: _jsxs(Row, { gap: "sm", align: "center", children: [_jsx(WarningIcon, {}), _jsx(Text, { size: "sm", weight: "semibold", className: "text-warning", children: REFUND_COPY.history.nonContestableBanner })] }) })), events.length > 0 && (_jsxs(Div, { className: `${__P.p4}`, children: [_jsx(Heading, { level: 4, className: "mb-3", color: "muted", size: "sm", weight: "semibold", children: REFUND_COPY.history.heading }), _jsx(Stack, { gap: "none", children: events.map((e) => (_jsx(RefundRow, { event: e, currency: currency }, e.refundId))) })] }))] }));
27
25
  }
@@ -4,6 +4,7 @@
4
4
  import type { OrderStatus, PaymentStatus } from "../types";
5
5
  import type { OrderType } from "../utils/order-splitter";
6
6
  import type { BaseDocument } from "../../../_internal/shared/types/base-document";
7
+ import type { ListingType } from "../../products/types";
7
8
  export type ShippingMethod = "custom" | "shiprocket";
8
9
  export type RefundType = "full" | "partial";
9
10
  export type OrderPayoutStatus = "eligible" | "requested" | "paid";
@@ -58,7 +59,7 @@ export interface OrderDocumentItem {
58
59
  unitPrice: number;
59
60
  totalPrice: number;
60
61
  /** SB8-F â€" set when the item is a prize-draw entry; drives the reveals badge. */
61
- listingType?: "standard" | "auction" | "pre-order" | "prize-draw" | "classified" | "digital-code" | "live";
62
+ listingType?: ListingType;
62
63
  /** SB8-F â€" per-item reveal status; flips through pending â†' open â†' revealed/closed. */
63
64
  prizeRevealStatus?: "pending" | "open" | "closed" | "revealed";
64
65
  /** SB8-F â€" ISO timestamp; deadline by which the buyer must claim the prize. */
@@ -756,7 +756,7 @@ export declare const orderSchema: z.ZodObject<{
756
756
  storeId?: string | undefined;
757
757
  attributes?: Record<string, string> | undefined;
758
758
  }>, "many">;
759
- address: z.ZodRecord<z.ZodString, z.ZodUnknown>;
759
+ address: z.ZodObject<{}, "passthrough", z.ZodTypeAny, z.objectOutputType<{}, z.ZodTypeAny, "passthrough">, z.objectInputType<{}, z.ZodTypeAny, "passthrough">>;
760
760
  orderStatus: z.ZodEnum<["pending", "confirmed", "processing", "shipped", "delivered", "cancelled", "refunded", "return_requested", "returned"]>;
761
761
  paymentStatus: z.ZodString;
762
762
  paymentGateway: z.ZodOptional<z.ZodString>;
@@ -791,7 +791,9 @@ export declare const orderSchema: z.ZodObject<{
791
791
  }, "strip", z.ZodTypeAny, {
792
792
  currency: string;
793
793
  id: string;
794
- address: Record<string, unknown>;
794
+ address: {} & {
795
+ [k: string]: unknown;
796
+ };
795
797
  items: {
796
798
  title: string;
797
799
  price: number;
@@ -826,7 +828,9 @@ export declare const orderSchema: z.ZodObject<{
826
828
  }, {
827
829
  currency: string;
828
830
  id: string;
829
- address: Record<string, unknown>;
831
+ address: {} & {
832
+ [k: string]: unknown;
833
+ };
830
834
  items: {
831
835
  title: string;
832
836
  price: number;
@@ -194,7 +194,7 @@ export const orderSchema = z.object({
194
194
  userId: z.string(),
195
195
  items: z.array(orderItemSchema),
196
196
  // address shape is intentionally open to allow extending apps
197
- address: z.record(z.string(), z.unknown()), // audit-z-any-ok: extending-apps extend this with their own address schema
197
+ address: z.object({}).passthrough(),
198
198
  orderStatus: orderStatusSchema,
199
199
  paymentStatus: z.string(),
200
200
  paymentGateway: z.string().optional(),
@@ -1,3 +1,4 @@
1
+ import type { ListingType } from "../types";
1
2
  import type { ProductDocument } from "../schemas";
2
3
  import type { FirebaseSieveResult } from "../../../providers/db-firebase";
3
4
  export interface ProductListActionParams {
@@ -6,7 +7,7 @@ export interface ProductListActionParams {
6
7
  page?: number;
7
8
  pageSize?: number;
8
9
  /** Canonical listing-kind discriminator (SB1-G Phase 4). */
9
- listingType?: "standard" | "auction" | "pre-order" | "prize-draw" | "classified" | "digital-code" | "live";
10
+ listingType?: ListingType;
10
11
  featured?: boolean;
11
12
  storeId?: string;
12
13
  categoriesIn?: string[];
@@ -48,7 +48,7 @@ const productUpdateSchema = z
48
48
  ])
49
49
  .optional(),
50
50
  mainImage: z.string().optional(),
51
- images: z.array(z.any()).optional(), // audit-z-any-ok: extending-apps pass MediaField | string | their own image shape
51
+ images: z.array(z.union([z.string(), mediaFieldSchema])).optional(),
52
52
  tags: z.array(z.string()).optional(),
53
53
  featured: z.boolean().optional(),
54
54
  isPromoted: z.boolean().optional(),
@@ -38,7 +38,7 @@ const productMutateSchema = z
38
38
  ])
39
39
  .optional(),
40
40
  mainImage: z.string().optional(),
41
- images: z.array(z.any()).optional(), // audit-z-any-ok: extending-apps pass MediaField | string | their own image shape
41
+ images: z.array(z.union([z.string(), mediaFieldSchema])).optional(),
42
42
  tags: z.array(z.string()).optional(),
43
43
  featured: z.boolean().optional(),
44
44
  isPromoted: z.boolean().optional(),
@@ -1,3 +1,20 @@
1
+ /**
2
+ * CompareOverlay â€" BK3
3
+ *
4
+ * Fullscreen comparison overlay triggered by the Compare bulk action on
5
+ * Products / Auctions / Pre-orders listings.
6
+ *
7
+ * Layout:
8
+ * - Desktop (md+): up to COMPARE_MAX_ITEMS columns side-by-side. Left rail
9
+ * holds field labels, columns hold values.
10
+ * - Mobile (< md): single-card swipeable view with dot pagination. The
11
+ * `useSwipe` hook detects left/right swipes on the card area.
12
+ *
13
+ * Data: parent passes either pre-loaded `items` or a list of `productIds`.
14
+ * When ids are passed, this component does a single fetch to
15
+ * `GET /api/products?ids=<csv>` (BK3 listByIds endpoint).
16
+ */
17
+ import type { ListingType } from "../types";
1
18
  /** Subset of ProductDocument the overlay needs. Kept loose so it can be fed
2
19
  * sanitized public payloads directly. */
3
20
  export interface CompareProductLike {
@@ -17,7 +34,7 @@ export interface CompareProductLike {
17
34
  storeName?: string;
18
35
  storeSlug?: string;
19
36
  /** Canonical discriminator (SB1-G Phase 4). */
20
- listingType?: "standard" | "auction" | "pre-order" | "prize-draw" | "classified" | "digital-code" | "live";
37
+ listingType?: ListingType;
21
38
  features?: string[];
22
39
  }
23
40
  interface CompareFieldLabels {
@@ -1,21 +1,5 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
- /**
4
- * CompareOverlay â€" BK3
5
- *
6
- * Fullscreen comparison overlay triggered by the Compare bulk action on
7
- * Products / Auctions / Pre-orders listings.
8
- *
9
- * Layout:
10
- * - Desktop (md+): up to COMPARE_MAX_ITEMS columns side-by-side. Left rail
11
- * holds field labels, columns hold values.
12
- * - Mobile (< md): single-card swipeable view with dot pagination. The
13
- * `useSwipe` hook detects left/right swipes on the card area.
14
- *
15
- * Data: parent passes either pre-loaded `items` or a list of `productIds`.
16
- * When ids are passed, this component does a single fetch to
17
- * `GET /api/products?ids=<csv>` (BK3 listByIds endpoint).
18
- */
19
3
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
20
4
  const __P = {
21
5
  p4: "p-4",
@@ -59,7 +59,8 @@ export function ProductDetailActions({ productId, productSlug, productTitle, pro
59
59
  try {
60
60
  await apiClient.post("/api/wishlist", { productId });
61
61
  }
62
- catch (err) { // audit-catch-raw-ok: pre-existing-handler-intentional
62
+ catch (err) {
63
+ void normalizeError(err);
63
64
  const status = err?.status;
64
65
  if (status === 401 || status === 403) {
65
66
  addToGuestWishlist(productId, "product", {
@@ -23,9 +23,7 @@ function MemberThumb({ member, isCurrent }) {
23
23
  const price = formatCurrency(member.price, member.currency ?? "INR");
24
24
  return (_jsxs(Link, { href: href, "aria-label": member.title, className: "flex flex-col items-center gap-1.5 flex-shrink-0 w-16 group", children: [_jsxs("div", { className: `relative w-14 h-14 rounded-full overflow-hidden border-2 transition-all ${isCurrent
25
25
  ? "border-[var(--appkit-color-primary)] ring-2 ring-[var(--appkit-color-primary)]/30"
26
- : "border-zinc-200 dark:border-zinc-700 group-hover:border-[var(--appkit-color-primary)]"}`, children: [_jsx(MediaImage, { src: image, alt: member.title, size: "card" }), member.isGroupParent && (
27
- // audit-inline-style-ok: single-corner border-radius; no appkit rounded= variant covers individual corners
28
- _jsx(Span, { color: "inverse", className: "absolute bottom-0 right-0 bg-[var(--appkit-color-primary)] text-[8px] leading-none px-[0.25rem] py-[0.125rem]", style: { borderTopLeftRadius: "0.25rem" }, children: "Set" }))] }), _jsx(Text, { className: "text-[10px] leading-tight line-clamp-2 w-full", color: "muted", align: "center", children: member.title }), _jsx(Text, { className: "text-[10px]", color: "primary", weight: "semibold", children: price })] }));
26
+ : "border-zinc-200 dark:border-zinc-700 group-hover:border-[var(--appkit-color-primary)]"}`, children: [_jsx(MediaImage, { src: image, alt: member.title, size: "card" }), member.isGroupParent && (_jsx(Span, { color: "inverse", rounded: "sm", className: "absolute bottom-0 right-0 bg-[var(--appkit-color-primary)] text-[8px] leading-none px-[0.25rem] py-[0.125rem]", children: "Set" }))] }), _jsx(Text, { className: "text-[10px] leading-tight line-clamp-2 w-full", color: "muted", align: "center", children: member.title }), _jsx(Text, { className: "text-[10px]", color: "primary", weight: "semibold", children: price })] }));
29
27
  }
30
28
  function GroupTableRow({ member }) {
31
29
  const href = memberHref(member);