@mohasinac/appkit 3.9.1 → 4.0.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 (58) hide show
  1. package/dist/_internal/shared/actions/action-registry.js +8 -0
  2. package/dist/client.d.ts +2 -2
  3. package/dist/client.js +1 -4
  4. package/dist/constants/api-endpoints.d.ts +3 -0
  5. package/dist/constants/api-endpoints.js +1 -0
  6. package/dist/features/account/hooks/useDataViewMode.d.ts +17 -0
  7. package/dist/features/account/hooks/useDataViewMode.js +55 -0
  8. package/dist/features/account/hooks/useProfile.d.ts +2 -0
  9. package/dist/features/admin/components/AdminBundlesView.d.ts +3 -4
  10. package/dist/features/admin/components/AdminBundlesView.js +2 -2
  11. package/dist/features/admin/components/AdminCarouselView.js +1 -1
  12. package/dist/features/admin/components/AdminCategoriesView.d.ts +2 -6
  13. package/dist/features/admin/components/AdminCategoriesView.js +1 -1
  14. package/dist/features/admin/components/AdminFaqsView.d.ts +0 -2
  15. package/dist/features/admin/components/AdminPrizeDrawsView.js +1 -1
  16. package/dist/features/admin/components/DataListingView.d.ts +8 -2
  17. package/dist/features/admin/components/DataListingView.js +7 -3
  18. package/dist/features/admin/components/DataTable.d.ts +10 -2
  19. package/dist/features/admin/components/DataTable.js +4 -2
  20. package/dist/features/admin/hooks/useAdminListing.d.ts +2 -2
  21. package/dist/features/admin/hooks/useAdminListing.js +6 -1
  22. package/dist/features/categories/components/BrandDetailTabs.js +2 -12
  23. package/dist/features/categories/components/CategoryDetailTabs.js +2 -12
  24. package/dist/features/events/components/AdminEventsView.d.ts +1 -5
  25. package/dist/features/faq/components/FAQAccordion.js +3 -2
  26. package/dist/features/faq/components/RelatedFAQs.js +1 -1
  27. package/dist/features/homepage/components/FAQSection.js +3 -3
  28. package/dist/features/layout/FooterLayout.js +2 -2
  29. package/dist/features/lottery/schemas/zod.d.ts +4 -4
  30. package/dist/features/payments/schemas/firestore.d.ts +6 -0
  31. package/dist/features/payments/schemas/firestore.js +2 -0
  32. package/dist/features/seller/components/SellerBundlesView.d.ts +3 -4
  33. package/dist/features/seller/components/SellerBundlesView.js +2 -2
  34. package/dist/features/seller/components/SellerPayoutsView.d.ts +2 -5
  35. package/dist/features/seller/components/SellerPayoutsView.js +100 -21
  36. package/dist/features/seller/components/SellerPreOrdersView.js +1 -1
  37. package/dist/features/seller/components/SellerPrizeDrawsView.js +1 -1
  38. package/dist/features/seller/components/SellerProductsView.js +6 -2
  39. package/dist/features/shipments/components/AdminShipmentsView.d.ts +1 -5
  40. package/dist/index.d.ts +3 -5
  41. package/dist/index.js +3 -3
  42. package/dist/react/index.d.ts +0 -2
  43. package/dist/react/index.js +0 -2
  44. package/dist/seed/faq-seed-data.js +11 -11
  45. package/dist/seed/homepage-sections-seed-data.js +1 -1
  46. package/dist/styles.css +30 -99
  47. package/dist/tailwind-utilities.css +1 -1
  48. package/dist/ui/DataTable.js +1 -1
  49. package/dist/ui/DataTable.style.css +10 -0
  50. package/dist/ui/components/DetailPageTabs.d.ts +1 -1
  51. package/dist/ui/components/DetailPageTabs.js +6 -30
  52. package/dist/ui/components/Tabs.d.ts +9 -1
  53. package/dist/ui/components/Tabs.js +13 -8
  54. package/dist/ui/components/Tabs.style.css +19 -0
  55. package/dist/ui/components/index.style.css +0 -1
  56. package/dist/ui/index.d.ts +0 -2
  57. package/dist/ui/index.js +0 -1
  58. package/package.json +1 -1
@@ -8,7 +8,7 @@ export function FooterLayout({ brandName, brandDescription, socialLinks, linkGro
8
8
  const [openGroups, setOpenGroups] = useState({});
9
9
  const toggleGroup = (idx) => setOpenGroups((prev) => ({ ...prev, [idx]: !prev[idx] }));
10
10
  const visibleTrustItems = trustBarItems.filter((item) => item.visible !== false);
11
- return (_jsxs(BlockFooter, { id: id, className: "bg-zinc-50 bg-[var(--appkit-color-bg)] border-t border-[var(--appkit-color-border-subtle)]", children: [showTrustBar && visibleTrustItems.length > 0 && (_jsx(Div, { border: "subtle", className: "border-b", padding: "y-xl", children: _jsx(Div, { paddingX: "x-page", className: "container mx-auto max-w-[1920px]", children: _jsx(Row, { as: "ul", wrap: true, justify: "center", gap: "lg", className: "lg:gap-[var(--appkit-space-10)]", children: visibleTrustItems.map((item, i) => (_jsx(Li, { children: _jsxs(Row, { textSize: "sm", gap: "sm", className: "min-w-[160px]", children: [_jsx(Span, { className: "flex-shrink-0 text-primary-600 dark:text-secondary-400 w-5 h-5", children: item.icon }), _jsxs(Span, { children: [_jsx(Span, { size: "sm", weight: "bold", className: "block", color: "primary", children: item.label }), item.subtitle && (_jsx(Span, { size: "xs", className: "block", color: "muted", children: item.subtitle }))] })] }) }, item.id ?? String(i)))) }) }) })), _jsxs(Div, { paddingX: "x-page", className: "container mx-auto max-w-[1920px] lg:py-[var(--appkit-space-14)]", padding: "y-2xl", children: [_jsxs(Div, { className: "grid grid-cols-1 lg:grid-cols-5 gap-[var(--appkit-space-10)] lg:gap-[var(--appkit-space-16)]", children: [_jsxs(Stack, { className: "lg:col-span-2", gap: "md", children: [_jsx(Heading, { level: 5, size: "xl", weight: "bold", color: "primary", children: brandName }), _jsx(Text, { className: "leading-relaxed max-w-sm", color: "muted", size: "sm", children: brandDescription }), socialLinks.length > 0 && (_jsx(Row, { as: Ul, gap: "sm", children: socialLinks.map((link) => (_jsx(Li, { children: _jsx(Link, { href: link.href, "aria-label": link.ariaLabel, target: "_blank", rel: "noopener noreferrer", className: "flex items-center justify-center w-9 h-9 rounded-full border border-[var(--appkit-color-border)] text-[var(--appkit-color-text-muted)] hover:text-primary dark:hover:text-secondary hover:border-primary/40 dark:hover:border-secondary/40 transition-colors", children: link.icon }) }, link.platform))) })), newsletterEnabled && newsletterSlot && (_jsx(Div, { padding: "t-xs", children: newsletterSlot }))] }), _jsx(Div, { className: `lg:col-span-3 grid grid-cols-1 gap-[var(--appkit-space-0)] lg:gap-[var(--appkit-space-8)] ${linkGroups.length <= 3 ? "lg:grid-cols-3" :
11
+ return (_jsxs(BlockFooter, { id: id, className: "bg-[var(--appkit-color-bg)] border-t border-[var(--appkit-color-border-subtle)]", children: [showTrustBar && visibleTrustItems.length > 0 && (_jsx(Div, { border: "subtle", className: "border-b", padding: "y-xl", children: _jsx(Div, { paddingX: "x-page", className: "container mx-auto max-w-[1920px]", children: _jsx(Row, { as: "ul", wrap: true, justify: "center", gap: "lg", className: "lg:gap-[var(--appkit-space-10)]", children: visibleTrustItems.map((item, i) => (_jsx(Li, { children: _jsxs(Row, { textSize: "sm", gap: "sm", className: "min-w-[160px]", children: [_jsx(Span, { className: "flex-shrink-0 text-primary-600 dark:text-secondary-400 w-5 h-5", children: item.icon }), _jsxs(Span, { children: [_jsx(Span, { size: "sm", weight: "bold", className: "block", color: "primary", children: item.label }), item.subtitle && (_jsx(Span, { size: "xs", className: "block", color: "muted", children: item.subtitle }))] })] }) }, item.id ?? String(i)))) }) }) })), _jsxs(Div, { paddingX: "x-page", className: "container mx-auto max-w-[1920px] lg:py-[var(--appkit-space-14)]", padding: "y-2xl", children: [_jsxs(Div, { className: "grid grid-cols-1 lg:grid-cols-5 gap-[var(--appkit-space-10)] lg:gap-[var(--appkit-space-16)]", children: [_jsxs(Stack, { className: "lg:col-span-2", gap: "md", children: [_jsx(Heading, { level: 5, size: "xl", weight: "bold", color: "primary", children: brandName }), _jsx(Text, { className: "leading-relaxed max-w-sm", color: "muted", size: "sm", children: brandDescription }), socialLinks.length > 0 && (_jsx(Row, { as: Ul, gap: "sm", children: socialLinks.map((link) => (_jsx(Li, { children: _jsx(Link, { href: link.href, "aria-label": link.ariaLabel, target: "_blank", rel: "noopener noreferrer", className: "flex items-center justify-center w-9 h-9 rounded-full border border-[var(--appkit-color-border)] text-[var(--appkit-color-text-muted)] hover:text-primary dark:hover:text-secondary hover:border-primary/40 dark:hover:border-secondary/40 transition-colors", children: link.icon }) }, link.platform))) })), newsletterEnabled && newsletterSlot && (_jsx(Div, { padding: "t-xs", children: newsletterSlot }))] }), _jsx(Div, { className: `lg:col-span-3 grid grid-cols-1 gap-[var(--appkit-space-0)] lg:gap-[var(--appkit-space-8)] ${linkGroups.length <= 3 ? "lg:grid-cols-3" :
12
12
  linkGroups.length === 4 ? "lg:grid-cols-4" :
13
- "lg:grid-cols-5"}`, children: linkGroups.map((group, idx) => (_jsxs(Div, { children: [_jsxs(Button, { type: "button", variant: "ghost", onClick: () => toggleGroup(idx), className: "w-full justify-between border-b border-zinc-200 py-[var(--appkit-space-3)] text-left text-[length:var(--appkit-text-sm)] font-semibold text-zinc-800 transition-colors hover:text-zinc-950 border-[var(--appkit-color-border-subtle)] text-[var(--appkit-color-text)] dark:hover:text-white lg:hidden", "aria-expanded": !!openGroups[idx], children: [group.heading, _jsx(ChevronDown, { className: `w-4 h-4 transition-transform duration-200 ${openGroups[idx] ? "rotate-180" : ""}` })] }), _jsx(Ul, { className: `lg:hidden overflow-hidden transition-all duration-200 ${openGroups[idx] ? "max-h-96 py-[var(--appkit-space-3)]" : "max-h-0"} space-y-2.5`, children: group.links.map((link) => (_jsx(Li, { children: _jsx(Link, { href: link.href, className: "text-[length:var(--appkit-text-sm)] text-[var(--appkit-color-text-muted)] hover:text-primary dark:hover:text-secondary transition-colors", children: link.label }) }, link.href))) }), _jsxs(Div, { className: "hidden lg:block", children: [_jsx(Text, { className: "tracking-wider mb-3", color: "muted", size: "xs", weight: "semibold", transform: "uppercase", children: group.heading }), _jsx(Ul, { className: "space-y-2.5", children: group.links.map((link) => (_jsx(Li, { children: _jsx(Link, { href: link.href, className: "text-[length:var(--appkit-text-sm)] text-[var(--appkit-color-text-muted)] hover:text-primary dark:hover:text-secondary transition-colors", children: link.label }) }, link.href))) })] })] }, idx))) })] }), _jsxs(Stack, { direction: "sm-row", color: "muted", textSize: "xs", justify: "between", border: "default", className: "mt-10 border-t border-[var(--appkit-color-border-subtle)] text-[var(--appkit-color-text-muted)]", padding: "t-lg", align: "center", gap: "3", children: [_jsx(Text, { size: "xs", color: "muted", children: copyrightText }), bottomLinks.length > 0 && (_jsx(Nav, { "aria-label": "Site utilities", children: _jsx(Ul, { className: "flex flex-wrap items-center gap-x-4 gap-y-1 justify-center", children: bottomLinks.map((link) => (_jsx(Li, { children: _jsx(Link, { href: link.href, className: "text-[length:var(--appkit-text-xs)] text-[var(--appkit-color-text-muted)] hover:text-primary dark:hover:text-secondary transition-colors underline-offset-2 hover:underline", children: link.label }) }, link.href))) }) })), madeInText && (_jsx(Text, { size: "xs", color: "muted", children: madeInText }))] })] })] }));
13
+ "lg:grid-cols-5"}`, children: linkGroups.map((group, idx) => (_jsxs(Div, { children: [_jsxs(Button, { type: "button", variant: "ghost", onClick: () => toggleGroup(idx), className: "w-full justify-between border-b py-[var(--appkit-space-3)] text-left text-[length:var(--appkit-text-sm)] font-semibold transition-colors border-[var(--appkit-color-border-subtle)] text-[var(--appkit-color-text)] hover:text-primary dark:hover:text-secondary lg:hidden", "aria-expanded": !!openGroups[idx], children: [group.heading, _jsx(ChevronDown, { className: `w-4 h-4 transition-transform duration-200 ${openGroups[idx] ? "rotate-180" : ""}` })] }), _jsx(Ul, { className: `lg:hidden overflow-hidden transition-all duration-200 ${openGroups[idx] ? "max-h-96 py-[var(--appkit-space-3)]" : "max-h-0"} space-y-2.5`, children: group.links.map((link) => (_jsx(Li, { children: _jsx(Link, { href: link.href, className: "text-[length:var(--appkit-text-sm)] text-[var(--appkit-color-text-muted)] hover:text-primary dark:hover:text-secondary transition-colors", children: link.label }) }, link.href))) }), _jsxs(Div, { className: "hidden lg:block", children: [_jsx(Text, { className: "tracking-wider mb-3", color: "muted", size: "xs", weight: "semibold", transform: "uppercase", children: group.heading }), _jsx(Ul, { className: "space-y-2.5", children: group.links.map((link) => (_jsx(Li, { children: _jsx(Link, { href: link.href, className: "text-[length:var(--appkit-text-sm)] text-[var(--appkit-color-text-muted)] hover:text-primary dark:hover:text-secondary transition-colors", children: link.label }) }, link.href))) })] })] }, idx))) })] }), _jsxs(Stack, { direction: "sm-row", color: "muted", textSize: "xs", justify: "between", border: "default", className: "mt-10 border-t border-[var(--appkit-color-border-subtle)] text-[var(--appkit-color-text-muted)]", padding: "t-lg", align: "center", gap: "3", children: [_jsx(Text, { size: "xs", color: "muted", children: copyrightText }), bottomLinks.length > 0 && (_jsx(Nav, { "aria-label": "Site utilities", children: _jsx(Ul, { className: "flex flex-wrap items-center gap-x-4 gap-y-1 justify-center", children: bottomLinks.map((link) => (_jsx(Li, { children: _jsx(Link, { href: link.href, className: "text-[length:var(--appkit-text-xs)] text-[var(--appkit-color-text-muted)] hover:text-primary dark:hover:text-secondary transition-colors underline-offset-2 hover:underline", children: link.label }) }, link.href))) }) })), madeInText && (_jsx(Text, { size: "xs", color: "muted", children: madeInText }))] })] })] }));
14
14
  }
@@ -66,8 +66,8 @@ export declare const submitLotteryPullSchema: z.ZodEffects<z.ZodObject<{
66
66
  purchasedItemNumber: z.ZodNumber;
67
67
  userPhone: z.ZodString;
68
68
  }, "strip", z.ZodTypeAny, {
69
- userId: string;
70
69
  transactionId: string;
70
+ userId: string;
71
71
  userPhone: string;
72
72
  sourceType: "product" | "event";
73
73
  paymentTime: string;
@@ -77,8 +77,8 @@ export declare const submitLotteryPullSchema: z.ZodEffects<z.ZodObject<{
77
77
  userDisplayName?: string | undefined;
78
78
  eventId?: string | undefined;
79
79
  }, {
80
- userId: string;
81
80
  transactionId: string;
81
+ userId: string;
82
82
  userPhone: string;
83
83
  sourceType: "product" | "event";
84
84
  paymentTime: string;
@@ -88,8 +88,8 @@ export declare const submitLotteryPullSchema: z.ZodEffects<z.ZodObject<{
88
88
  userDisplayName?: string | undefined;
89
89
  eventId?: string | undefined;
90
90
  }>, {
91
- userId: string;
92
91
  transactionId: string;
92
+ userId: string;
93
93
  userPhone: string;
94
94
  sourceType: "product" | "event";
95
95
  paymentTime: string;
@@ -99,8 +99,8 @@ export declare const submitLotteryPullSchema: z.ZodEffects<z.ZodObject<{
99
99
  userDisplayName?: string | undefined;
100
100
  eventId?: string | undefined;
101
101
  }, {
102
- userId: string;
103
102
  transactionId: string;
103
+ userId: string;
104
104
  userPhone: string;
105
105
  sourceType: "product" | "event";
106
106
  paymentTime: string;
@@ -66,6 +66,10 @@ export interface PayoutDocument extends BaseDocument {
66
66
  netAmount?: number;
67
67
  requestedAt: Date;
68
68
  processedAt?: Date;
69
+ /** UTR / UPI ref / bank transfer ID entered by admin when marking paid. */
70
+ transactionId?: string;
71
+ /** Seller-set personal follow-up flag — surfaced only to the owning seller, never mutated by admin. */
72
+ sellerReminderFlag?: boolean;
69
73
  }
70
74
  export declare const PAYOUT_COLLECTION: "payouts";
71
75
  export declare const PAYOUT_INDEXED_FIELDS: readonly ["storeId", "status", "requestedAt", "createdAt"];
@@ -88,6 +92,8 @@ export declare const PAYOUT_FIELDS: {
88
92
  };
89
93
  export declare const PAYOUT_PUBLIC_FIELDS: readonly ["id", "storeId", "amount", "currency", "status", "paymentMethod", "requestedAt", "processedAt", "createdAt"];
90
94
  export declare const PAYOUT_ADMIN_UPDATEABLE_FIELDS: readonly ["status", "adminNote", "processedAt"];
95
+ /** The only field a seller may write on their own payout doc. */
96
+ export declare const PAYOUT_SELLER_UPDATEABLE_FIELDS: readonly ["sellerReminderFlag"];
91
97
  export type PayoutCreateInput = Omit<PayoutDocument, "id" | "createdAt" | "updatedAt" | "status" | "requestedAt">;
92
98
  export type PayoutUpdateInput = Partial<Pick<PayoutDocument, (typeof PAYOUT_ADMIN_UPDATEABLE_FIELDS)[number]>>;
93
99
  export declare const payoutQueryHelpers: {
@@ -52,6 +52,8 @@ export const PAYOUT_ADMIN_UPDATEABLE_FIELDS = [
52
52
  "adminNote",
53
53
  "processedAt",
54
54
  ];
55
+ /** The only field a seller may write on their own payout doc. */
56
+ export const PAYOUT_SELLER_UPDATEABLE_FIELDS = ["sellerReminderFlag"];
55
57
  export const payoutQueryHelpers = {
56
58
  byStore: (storeId) => ["storeId", "==", storeId],
57
59
  byStatus: (status) => ["status", "==", status],
@@ -1,8 +1,7 @@
1
1
  import React from "react";
2
2
  export interface SellerBundlesViewProps {
3
- getEditHref: (row: {
4
- id: string;
5
- }) => string;
3
+ /** Plain string with an `{id}` placeholder — safe to build in a Server Component page.tsx. */
4
+ editHrefTemplate: string;
6
5
  newHref: string;
7
6
  }
8
- export declare function SellerBundlesView({ getEditHref, newHref }: SellerBundlesViewProps): React.JSX.Element;
7
+ export declare function SellerBundlesView({ editHrefTemplate, newHref }: SellerBundlesViewProps): React.JSX.Element;
@@ -37,7 +37,7 @@ const COLUMNS = [
37
37
  },
38
38
  { key: "updatedAt", header: "Updated", className: "w-28" },
39
39
  ];
40
- export function SellerBundlesView({ getEditHref, newHref }) {
40
+ export function SellerBundlesView({ editHrefTemplate, newHref }) {
41
41
  const config = {
42
42
  portal: "seller",
43
43
  title: "Bundles",
@@ -78,7 +78,7 @@ export function SellerBundlesView({ getEditHref, newHref }) {
78
78
  parts.push(sieveFilter("bundleStockStatus", SIEVE_OP.EQ, filterState.bundleStockStatus));
79
79
  return parts.join(",") || undefined;
80
80
  },
81
- getRowHref: getEditHref,
81
+ rowHrefTemplate: editHrefTemplate,
82
82
  toolbarExtra: (_jsx(Button, { asChild: true, size: "sm", variant: "primary", children: _jsx(TextLink, { href: newHref, layout: "flex", align: "center", gap: "xs", children: "+ New Bundle" }) })),
83
83
  // Rule #7: bulk-action array sourced from the SELLER_BULK_ACTIONS preset.
84
84
  buildBulkActions: (selection) => {
@@ -1,7 +1,4 @@
1
1
  import React from "react";
2
2
  import type { ListingLayoutProps } from "../../../ui";
3
- export interface SellerPayoutsViewProps extends ListingLayoutProps {
4
- onViewClick?: (id: string) => void;
5
- onExportClick?: (id: string) => void;
6
- }
7
- export declare function SellerPayoutsView({ children, onViewClick, onExportClick, ...props }: SellerPayoutsViewProps): React.JSX.Element;
3
+ export type SellerPayoutsViewProps = ListingLayoutProps;
4
+ export declare function SellerPayoutsView({ children, ...props }: SellerPayoutsViewProps): React.JSX.Element;
@@ -1,18 +1,77 @@
1
1
  "use client";
2
- import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { normalizeError } from "../../../errors/normalize";
3
4
  import { sieveFilter, SIEVE_OP } from "@mohasinac/appkit";
4
5
  import { sortBy } from "@mohasinac/appkit";
5
- import React from "react";
6
- import { FilterChipGroup, ListingLayout, RowActionMenu } from "../../../ui";
6
+ import React, { useState } from "react";
7
+ import { Badge, Div, FilterChipGroup, ListingLayout, Row, RowActionMenu, SideDrawer, Span, Stack, Text, Toggle, useToast } from "../../../ui";
7
8
  import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
8
9
  import { ADMIN_PAYOUT_STATUS_TABS } from "../../admin/constants/filter-tabs";
9
10
  import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
11
+ import { buildBulkAction } from "../../../_internal/shared/actions/bulk-helpers";
12
+ import { apiClient } from "../../../http";
10
13
  import { toRecordArray, toRelativeDate, toCurrency, toStringValue, } from "../../admin/hooks/useAdminListingData";
11
14
  import { DataListingView } from "../../admin/components/DataListingView";
12
- export function SellerPayoutsView({ children, onViewClick, onExportClick, ...props }) {
15
+ const PAYOUT_STAGES = ["pending", "processing", "paid"];
16
+ const PAYOUT_CYCLE_DAYS = 7;
17
+ function formatDate(value) {
18
+ if (!value)
19
+ return "—";
20
+ const d = new Date(value);
21
+ return Number.isNaN(d.getTime()) ? "—" : d.toLocaleDateString("en-IN", { day: "numeric", month: "short", year: "numeric" });
22
+ }
23
+ function StatusProgress({ status }) {
24
+ const normalized = status.toLowerCase();
25
+ if (normalized === "failed") {
26
+ return _jsx(Badge, { variant: "danger", children: "Failed" });
27
+ }
28
+ const currentIndex = PAYOUT_STAGES.indexOf(normalized);
29
+ return (_jsx(Row, { gap: "xs", align: "center", children: PAYOUT_STAGES.map((stage, i) => (_jsxs(React.Fragment, { children: [i > 0 && _jsx(Div, { className: `h-0.5 w-6 ${i <= currentIndex ? "bg-primary" : "bg-[var(--appkit-color-border)]"}` }), _jsx(Span, { size: "xs", weight: i === currentIndex ? "bold" : "medium", className: i <= currentIndex ? "text-primary" : "text-[var(--appkit-color-text-muted)]", children: stage[0].toUpperCase() + stage.slice(1) })] }, stage))) }));
30
+ }
31
+ export function SellerPayoutsView({ children, ...props }) {
32
+ const { showToast } = useToast();
33
+ const [selectedPayout, setSelectedPayout] = useState(null);
34
+ const [reminderPending, setReminderPending] = useState(false);
35
+ // Mirrors the raw API record per row id so the detail panel can open
36
+ // instantly from data already in memory (list already fetched it) —
37
+ // matches AdminProductsView's rawByIdRef pattern.
38
+ const rawByIdRef = React.useRef({});
13
39
  if (React.Children.count(children) > 0) {
14
40
  return (_jsx(ListingLayout, { portal: "seller", ...props, children: children }));
15
41
  }
42
+ const handleExport = (rows) => {
43
+ const header = ["id", "amount", "currency", "status", "requestedAt", "processedAt", "transactionId"];
44
+ const lines = [header.join(",")];
45
+ for (const p of rows) {
46
+ lines.push(header.map((k) => JSON.stringify(p[k] ?? "")).join(","));
47
+ }
48
+ const blob = new Blob([lines.join("\n")], { type: "text/csv" });
49
+ const url = URL.createObjectURL(blob);
50
+ const a = document.createElement("a");
51
+ a.href = url;
52
+ a.download = `payouts-${new Date().toISOString().slice(0, 10)}.csv`;
53
+ a.click();
54
+ URL.revokeObjectURL(url);
55
+ };
56
+ const handleToggleReminder = async (next) => {
57
+ if (!selectedPayout)
58
+ return;
59
+ setReminderPending(true);
60
+ try {
61
+ await apiClient.patch(SELLER_ENDPOINTS.PAYOUT_BY_ID(String(selectedPayout.id)), {
62
+ sellerReminderFlag: next,
63
+ });
64
+ setSelectedPayout((prev) => (prev ? { ...prev, sellerReminderFlag: next } : prev));
65
+ showToast(next ? "Reminder set." : "Reminder cleared.", "success");
66
+ }
67
+ catch (err) {
68
+ void normalizeError(err);
69
+ showToast("Failed to update reminder.", "error");
70
+ }
71
+ finally {
72
+ setReminderPending(false);
73
+ }
74
+ };
16
75
  const config = {
17
76
  portal: "seller",
18
77
  title: "Payouts",
@@ -26,29 +85,49 @@ export function SellerPayoutsView({ children, onViewClick, onExportClick, ...pro
26
85
  { value: sortBy("createdAt", "DESC"), label: "Newest" },
27
86
  { value: sortBy("createdAt", "ASC"), label: "Oldest" },
28
87
  ],
29
- mapRows: (response) => toRecordArray(response.payouts).map((item, index) => ({
30
- id: toStringValue(item.id, `payout-${index}`),
31
- primary: `Payout #${toStringValue(item.payoutNumber ?? item.id, "Unknown")}`,
32
- secondary: [
33
- toCurrency(item.amount ?? item.totalAmount),
34
- `Requested: ${toRelativeDate(item.requestedAt ?? item.createdAt)}`,
35
- ].join(" · "),
36
- status: toStringValue(item.status, "Pending"),
37
- updatedAt: toRelativeDate(item.processedAt ?? item.updatedAt ?? item.createdAt),
38
- })),
88
+ mapRows: (response) => toRecordArray(response.payouts).map((item, index) => {
89
+ const id = toStringValue(item.id, `payout-${index}`);
90
+ rawByIdRef.current[id] = item;
91
+ return {
92
+ id,
93
+ primary: `Payout #${toStringValue(item.payoutNumber ?? item.id, "Unknown")}`,
94
+ secondary: [
95
+ toCurrency(item.amount ?? item.totalAmount),
96
+ `Requested: ${toRelativeDate(item.requestedAt ?? item.createdAt)}`,
97
+ ].join(" · "),
98
+ status: toStringValue(item.status, "Pending"),
99
+ updatedAt: toRelativeDate(item.processedAt ?? item.updatedAt ?? item.createdAt),
100
+ };
101
+ }),
39
102
  getTotal: (response, mappedRows) => typeof response.meta?.total === "number"
40
103
  ? response.meta.total
41
104
  : mappedRows.length,
42
105
  buildFilters: (state) => state.status && state.status !== "All" ? sieveFilter("status", SIEVE_OP.EQ, state.status) : undefined,
106
+ // Rule #7: bulk-action array sourced from the CTA registry, not inline objects.
107
+ buildBulkActions: (selection) => [
108
+ buildBulkAction(ACTIONS.STORE["export-payout"], () => {
109
+ const rows = selection.selectedIds
110
+ .map((id) => rawByIdRef.current[id])
111
+ .filter((r) => Boolean(r));
112
+ handleExport(rows);
113
+ selection.clearSelection();
114
+ }),
115
+ ],
43
116
  renderRowActions: (row) => (_jsx(RowActionMenu, { actions: [
44
- ...(onViewClick
45
- ? [{ label: ACTIONS.STORE["view-payout"].label, onClick: () => onViewClick(row.id) }]
46
- : []),
47
- ...(onExportClick
48
- ? [{ label: ACTIONS.STORE["export-payout"].label, onClick: () => onExportClick(row.id) }]
49
- : []),
117
+ {
118
+ label: ACTIONS.STORE["view-payout"].label,
119
+ onClick: () => setSelectedPayout(rawByIdRef.current[row.id] ?? { id: row.id }),
120
+ },
121
+ {
122
+ label: ACTIONS.STORE["export-payout"].label,
123
+ onClick: () => handleExport([rawByIdRef.current[row.id] ?? { id: row.id }]),
124
+ },
50
125
  ] })),
51
126
  renderFilterPanel: ({ pendingFilters, setPendingFilters }) => (_jsx(FilterChipGroup, { label: "Status", tabs: ADMIN_PAYOUT_STATUS_TABS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) })),
52
127
  };
53
- return _jsx(DataListingView, { config: config });
128
+ const expectedByDate = selectedPayout?.requestedAt
129
+ ? new Date(new Date(selectedPayout.requestedAt).getTime() + PAYOUT_CYCLE_DAYS * 24 * 60 * 60 * 1000)
130
+ : null;
131
+ const orderIds = Array.isArray(selectedPayout?.orderIds) ? selectedPayout.orderIds : [];
132
+ return (_jsxs(_Fragment, { children: [_jsx(DataListingView, { config: config }), _jsx(SideDrawer, { isOpen: Boolean(selectedPayout), onClose: () => setSelectedPayout(null), title: selectedPayout ? `Payout #${toStringValue(selectedPayout.payoutNumber ?? selectedPayout.id, "")}` : "Payout", mode: "view", children: selectedPayout && (_jsxs(Stack, { gap: "lg", children: [_jsx(StatusProgress, { status: toStringValue(selectedPayout.status, "pending") }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "xs", color: "muted", children: "Amount" }), _jsx(Text, { size: "xl", weight: "bold", children: toCurrency(selectedPayout.amount ?? selectedPayout.totalAmount) })] }), selectedPayout.transactionId ? (_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "xs", color: "muted", children: "Transaction / reference ID" }), _jsx(Text, { size: "sm", className: "font-mono", children: String(selectedPayout.transactionId) })] })) : null, _jsxs(Row, { gap: "lg", wrap: true, children: [_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "xs", color: "muted", children: "Requested" }), _jsx(Text, { size: "sm", children: formatDate(selectedPayout.requestedAt ?? selectedPayout.createdAt) })] }), selectedPayout.status === "paid" ? (_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "xs", color: "muted", children: "Paid on" }), _jsx(Text, { size: "sm", children: formatDate(selectedPayout.processedAt) })] })) : (_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "xs", color: "muted", children: "Expected by" }), _jsx(Text, { size: "sm", children: expectedByDate ? formatDate(expectedByDate.toISOString()) : "—" })] })), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "xs", color: "muted", children: "Payment method" }), _jsx(Text, { size: "sm", className: "capitalize", children: toStringValue(selectedPayout.paymentMethod, "—").replace("_", " ") })] })] }), orderIds.length > 0 && (_jsxs(Stack, { gap: "xs", children: [_jsxs(Text, { size: "xs", color: "muted", children: ["Orders included (", orderIds.length, ")"] }), _jsx(Div, { className: "max-h-40", overflow: "y-auto", children: _jsx(Stack, { gap: "xs", children: orderIds.map((oid) => (_jsx(Text, { size: "xs", className: "font-mono", color: "muted", children: oid }, oid))) }) })] })), _jsxs(Row, { align: "center", justify: "between", border: "default", rounded: "lg", padding: "inline", children: [_jsxs(Stack, { gap: "none", children: [_jsx(Text, { size: "sm", weight: "medium", children: "Remind me" }), _jsx(Text, { size: "xs", color: "muted", children: "Flag this payout for a personal follow-up" })] }), _jsx(Toggle, { checked: Boolean(selectedPayout.sellerReminderFlag), onChange: handleToggleReminder, disabled: reminderPending, size: "md", "aria-label": ACTIONS.STORE["set-payout-reminder"].ariaLabel })] })] })) })] }));
54
133
  }
@@ -111,7 +111,7 @@ export function SellerPreOrdersView({ children, onDelete, ...props }) {
111
111
  const status = state.status && state.status !== "All" ? sieveFilter("status", SIEVE_OP.EQ, state.status) : null;
112
112
  return ["listingType==pre-order", status].filter(Boolean).join(",");
113
113
  },
114
- getRowHref: (row) => String(ROUTES.STORE.PRE_ORDERS_EDIT(row.id)),
114
+ rowHrefTemplate: String(ROUTES.STORE.PRE_ORDERS_EDIT("{id}")),
115
115
  renderRowActions: (row) => (_jsx(RowActionMenu, { actions: [
116
116
  {
117
117
  label: ACTIONS.STORE["edit-listing"].label,
@@ -108,7 +108,7 @@ export function SellerPrizeDrawsView({ children, onDelete, ...props }) {
108
108
  const status = state.status && state.status !== "All" ? sieveFilter("status", SIEVE_OP.EQ, state.status) : null;
109
109
  return ["listingType==prize-draw", status].filter(Boolean).join(",");
110
110
  },
111
- getRowHref: (row) => String(ROUTES.STORE.PRIZE_DRAWS_EDIT(row.id)),
111
+ rowHrefTemplate: String(ROUTES.STORE.PRIZE_DRAWS_EDIT("{id}")),
112
112
  renderRowActions: (row) => (_jsx(RowActionMenu, { actions: [
113
113
  {
114
114
  label: ACTIONS.STORE["edit-listing"].label,
@@ -18,6 +18,7 @@ import { ROUTES } from "../../../constants";
18
18
  import { normalizeListingType } from "../../products/utils/listing-type";
19
19
  import { toRecordArray, toRelativeDate, toStringValue, useSellerListingData, } from "../hooks/useSellerListingData";
20
20
  import { DataTable } from "../../admin/components/DataTable";
21
+ import { useDataViewMode } from "../../account/hooks/useDataViewMode";
21
22
  import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
22
23
  import { buildBulkAction } from "../../../_internal/shared/actions/bulk-helpers";
23
24
  import { useListingTypeFlags } from "../../../react/hooks/useListingTypeFlags";
@@ -114,7 +115,7 @@ const PRODUCT_COLUMNS = [
114
115
  ];
115
116
  export function SellerProductsView({ onDeleteProduct, onCreateClick, children, ...props }) {
116
117
  const hasChildren = React.Children.count(children) > 0;
117
- const [view, setView] = useState("table");
118
+ const { view, setView } = useDataViewMode("table");
118
119
  const dispatch = useActionDispatch();
119
120
  const { showToast } = useToast();
120
121
  const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
@@ -302,8 +303,11 @@ export function SellerProductsView({ onDeleteProduct, onCreateClick, children, .
302
303
  { label: "Show sold", active: showSold, onChange: (next) => table.set("showSold", next ? "true" : "") },
303
304
  ], extra: onCreateClick ? (_jsx(Button, { variant: "primary", size: "sm", onClick: onCreateClick, children: "+ New Listing" })) : null }), _jsx(TypeDropdown, { active: listingKind, onChange: handleKindChange }), totalPages > 1 && (_jsx(StickyToolbar, { offset: "header+pagination", tone: "default", border: true, padding: "sm", children: _jsx(Row, { justify: "center", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) }) })), selection.selectedIds.length > 0 && (_jsx(StickyToolbar, { offset: "header+bulk-actions", tone: "default", border: true, padding: "sm", z: "above-toolbar", children: _jsx(BulkActionBar, { selectedCount: selection.selectedIds.length, onClearSelection: selection.clearSelection, actions: bulkActions }) })), _jsxs(Div, { paddingX: "x-sm-lg-md", padding: "y-md", children: [errorMessage && (_jsx(Alert, { variant: "error", className: "mb-4", children: errorMessage })), view !== "table" && (_jsx(SellerProductsCards, { view: view, rows: visibleRows, isLoading: isLoading, listingKind: listingKind, selectedIds: selection.selectedIdSet, toggle: selection.toggle, onEdit: handleEdit, onDuplicate: (row) => void handleDuplicate(row), onDelete: onDeleteProduct ? (row) => void handleDelete(row) : undefined })), view === "table" && (_jsx(DataTable, { columns: PRODUCT_COLUMNS, rows: visibleRows, isLoading: isLoading, emptyLabel: listingKind !== "all"
304
305
  ? `No ${listingKind} listings found`
305
- : "No products listed yet", selectedIds: selection.selectedIdSet, onToggleSelect: selection.toggle, onToggleSelectAll: () => selection.toggleAll(), getRowHref: (row) =>
306
+ : "No products listed yet", selectedIds: selection.selectedIdSet, onToggleSelect: selection.toggle, onToggleSelectAll: () => selection.toggleAll(), rowHrefTemplate: (row) =>
306
307
  // S-STORE-2-D — row click → public detail/preview, not edit.
308
+ // A function is safe here: both this view and DataTable are
309
+ // already client components, and the target varies per row
310
+ // (not expressible as a single {id} template string).
307
311
  row.listingKind === "auction"
308
312
  ? `/auctions/${row.id}`
309
313
  : row.listingKind === "pre-order"
@@ -1,8 +1,4 @@
1
1
  import React from "react";
2
2
  import type { ListingLayoutProps } from "../../../ui";
3
- export interface AdminShipmentsViewProps extends ListingLayoutProps {
4
- getRowHref?: (row: {
5
- id: string;
6
- }) => string;
7
- }
3
+ export type AdminShipmentsViewProps = ListingLayoutProps;
8
4
  export declare function AdminShipmentsView({ children, ...props }: AdminShipmentsViewProps): React.JSX.Element;
package/dist/index.d.ts CHANGED
@@ -132,8 +132,6 @@ export type { UseGestureOptions } from "./react/index";
132
132
  export type { UseKeyPressOptions } from "./react/index";
133
133
  export type { UsePullToRefreshOptions } from "./react/index";
134
134
  export type { UsePullToRefreshReturn } from "./react/index";
135
- export type { UseVisibleItemsOptions } from "./react/index";
136
- export type { UseVisibleItemsResult } from "./react/index";
137
135
  export { CONFIRMATION_MESSAGES } from "./values/index";
138
136
  export { INFO_MESSAGES } from "./values/index";
139
137
  export { deepClone } from "./utils/index";
@@ -3051,9 +3049,9 @@ export { Button } from "./ui/index";
3051
3049
  export { Heading, Spinner, IconButton, Progress, IndeterminateProgress, Pagination, PageLoader } from "./ui/index";
3052
3050
  export { ModalFooter, UnsavedChangesModal, ResponsiveView, Textarea, Checkbox } from "./ui/index";
3053
3051
  export { ToastProvider, StarRating, StepperNav, ViewToggle, PriceDisplay, SummaryCard } from "./ui/index";
3054
- export { CountdownDisplay, ItemRow, HorizontalScroller, TabStrip } from "./ui/index";
3052
+ export { CountdownDisplay, ItemRow, HorizontalScroller } from "./ui/index";
3055
3053
  export { CardBody, MenuTrigger, MenuContent, MenuSeparator, RoleBadge, SkipToMain } from "./ui/index";
3056
- export { TabsList, TabsContent, PasswordStrengthIndicator, TablePagination } from "./ui/index";
3054
+ export { Tabs, TabsList, TabsTrigger, TabsContent, PasswordStrengthIndicator, TablePagination } from "./ui/index";
3057
3055
  export { Stack, classNames, mergeTailwindClasses, SideModal, SideDrawer, RichText, RichTextRenderer } from "./ui/index";
3058
3056
  export type { RichTextRendererProps } from "./ui/index";
3059
3057
  export { SlottedListingView, DetailViewShell, StackedViewShell } from "./ui/index";
@@ -3067,7 +3065,7 @@ export { API_ENDPOINTS, API_ROUTES, LOGS_ENDPOINTS, AUTH_ENDPOINTS, ACCOUNT_ENDP
3067
3065
  export { WishlistFullError } from "./features/wishlist/server";
3068
3066
  export { useHistory, useHistoryMergeOnLogin, getGuestHistory, trackGuestHistory, removeGuestHistoryItem, clearGuestHistory, getGuestHistoryCount, type GuestHistoryItem, type GuestHistoryType, type UserHistoryItem, type HistoryProductType, type HistoryItemSnapshot, type TrackArgs as TrackHistoryArgs, } from "./features/history";
3069
3067
  export { historyRepository, UserHistoryRepository, trackHistoryView, getHistoryForUser, removeHistoryItem, clearHistory, mergeGuestHistory, } from "./features/history/server";
3070
- export { useMediaQuery, useBreakpoint, useClickOutside, useKeyPress, useLongPress, useGesture, useSwipe, useSession, useAuth, useCountdown, useCamera, useBulkSelection, useUrlTable, usePendingFilters, usePendingTable, useUnsavedChanges, UNSAVED_CHANGES_EVENT, useBulkAction, useEntityDelete, useContainerGrid, useMessage, useVisibleItems, useModalStack, RTDBPayloadStatus, } from "./react/index";
3068
+ export { useMediaQuery, useBreakpoint, useClickOutside, useKeyPress, useLongPress, useGesture, useSwipe, useSession, useAuth, useCountdown, useCamera, useBulkSelection, useUrlTable, usePendingFilters, usePendingTable, useUnsavedChanges, UNSAVED_CHANGES_EVENT, useBulkAction, useEntityDelete, useContainerGrid, useMessage, useModalStack, RTDBPayloadStatus, } from "./react/index";
3071
3069
  export { RoleGate, ProtectedRoute, SocialAuthButtons, AuthStatusPanel, LoginForm, RegisterForm, ForgotPasswordView, ResetPasswordView, VerifyEmailView, OAuthLoadingView, } from "./features/auth/index";
3072
3070
  export type { RoleGateProps, ProtectedRouteProps, AuthGuardUser, AuthRouteConfig, LoginFormProps, LoginFormValues, RegisterFormProps, RegisterFormValues, ForgotPasswordViewProps, ResetPasswordViewProps, VerifyEmailViewProps, } from "./features/auth/index";
3073
3071
  export { CartItemRow, CartDrawer, CartView, CartSummary, ShippingPicker } from "./features/cart/index";
package/dist/index.js CHANGED
@@ -5468,9 +5468,9 @@ export { Button } from "./ui/index";
5468
5468
  export { Heading, Spinner, IconButton, Progress, IndeterminateProgress, Pagination, PageLoader } from "./ui/index";
5469
5469
  export { ModalFooter, UnsavedChangesModal, ResponsiveView, Textarea, Checkbox } from "./ui/index";
5470
5470
  export { ToastProvider, StarRating, StepperNav, ViewToggle, PriceDisplay, SummaryCard } from "./ui/index";
5471
- export { CountdownDisplay, ItemRow, HorizontalScroller, TabStrip } from "./ui/index";
5471
+ export { CountdownDisplay, ItemRow, HorizontalScroller } from "./ui/index";
5472
5472
  export { CardBody, MenuTrigger, MenuContent, MenuSeparator, RoleBadge, SkipToMain } from "./ui/index";
5473
- export { TabsList, TabsContent, PasswordStrengthIndicator, TablePagination } from "./ui/index";
5473
+ export { Tabs, TabsList, TabsTrigger, TabsContent, PasswordStrengthIndicator, TablePagination } from "./ui/index";
5474
5474
  export { Stack, classNames, mergeTailwindClasses, SideModal, SideDrawer, RichText, RichTextRenderer } from "./ui/index";
5475
5475
  export { SlottedListingView, DetailViewShell, StackedViewShell } from "./ui/index";
5476
5476
  export { buildColumns, createColumnBuilder } from "./ui/index";
@@ -5486,7 +5486,7 @@ export { WishlistFullError } from "./features/wishlist/server";
5486
5486
  export { useHistory, useHistoryMergeOnLogin, getGuestHistory, trackGuestHistory, removeGuestHistoryItem, clearGuestHistory, getGuestHistoryCount, } from "./features/history";
5487
5487
  export { historyRepository, UserHistoryRepository, trackHistoryView, getHistoryForUser, removeHistoryItem, clearHistory, mergeGuestHistory, } from "./features/history/server";
5488
5488
  // Missing React hooks and providers
5489
- export { useMediaQuery, useBreakpoint, useClickOutside, useKeyPress, useLongPress, useGesture, useSwipe, useSession, useAuth, useCountdown, useCamera, useBulkSelection, useUrlTable, usePendingFilters, usePendingTable, useUnsavedChanges, UNSAVED_CHANGES_EVENT, useBulkAction, useEntityDelete, useContainerGrid, useMessage, useVisibleItems, useModalStack, RTDBPayloadStatus, } from "./react/index";
5489
+ export { useMediaQuery, useBreakpoint, useClickOutside, useKeyPress, useLongPress, useGesture, useSwipe, useSession, useAuth, useCountdown, useCamera, useBulkSelection, useUrlTable, usePendingFilters, usePendingTable, useUnsavedChanges, UNSAVED_CHANGES_EVENT, useBulkAction, useEntityDelete, useContainerGrid, useMessage, useModalStack, RTDBPayloadStatus, } from "./react/index";
5490
5490
  // Missing core types (already exported above; skipped to avoid duplicates)
5491
5491
  // Missing auth feature components
5492
5492
  export { RoleGate, ProtectedRoute, SocialAuthButtons, AuthStatusPanel, LoginForm, RegisterForm, ForgotPasswordView, ResetPasswordView, VerifyEmailView, OAuthLoadingView, } from "./features/auth/index";
@@ -46,8 +46,6 @@ export type { BulkActionPayload, BulkActionResult, BulkActionSummary, BulkAction
46
46
  export { useContainerGrid } from "./hooks/useContainerGrid";
47
47
  export { useMessage } from "./hooks/useMessage";
48
48
  export type { UseContainerGridOptions, UseContainerGridResult, } from "./hooks/useContainerGrid";
49
- export { useVisibleItems } from "./hooks/useVisibleItems";
50
- export type { UseVisibleItemsOptions, UseVisibleItemsResult, } from "./hooks/useVisibleItems";
51
49
  export { useModalStack } from "./useModalStack";
52
50
  export type { ModalEntry } from "./useModalStack";
53
51
  export { ErrorBoundary } from "./ErrorBoundary";
@@ -46,8 +46,6 @@ export { useBulkAction } from "./hooks/useBulkAction";
46
46
  export { useContainerGrid } from "./hooks/useContainerGrid";
47
47
  // Success/error message management
48
48
  export { useMessage } from "./hooks/useMessage";
49
- // Visible-items counter for tab strips and overflow scrollers
50
- export { useVisibleItems } from "./hooks/useVisibleItems";
51
49
  // Modal stack management
52
50
  export { useModalStack } from "./useModalStack";
53
51
  // Error boundary
@@ -1,5 +1,5 @@
1
1
  /*
2
- * WHY: Seeds 61 FAQs for LetItRip collectibles marketplace — covers general platform, shipping,
2
+ * WHY: Seeds 63 FAQs for LetItRip collectibles marketplace — covers general platform, shipping,
3
3
  * returns, payments, auctions, pre-orders, product info, account/security, scam awareness.
4
4
  * WHAT: Mix of broad collectibles marketplace FAQs + category-specific FAQs (trading cards, action figures,
5
5
  * diecast, Beyblade, model kits, grading, authenticity, bundles, prize draws), plus (Phase 17,
@@ -86,7 +86,7 @@ export const faqSeedData = [
86
86
  question: "How do I become a seller on LetItRip?",
87
87
  answer: html(`<p>Go to your Account → Become a Seller. Fill in your store details, upload verification documents, and submit for review. Our team typically approves sellers within 48 hours. Once approved, you can start listing immediately.</p>`),
88
88
  category: "general",
89
- showOnHomepage: false,
89
+ showOnHomepage: true,
90
90
  showInFooter: true,
91
91
  isPinned: false,
92
92
  order: 4,
@@ -258,7 +258,7 @@ export const faqSeedData = [
258
258
  question: "Is free shipping available?",
259
259
  answer: html(`<p>Many sellers offer free shipping on orders above a certain amount. Look for the "Free Shipping" badge on product listings. You can also use the FREESHIP999 coupon for free shipping on orders ₹999+.</p>`),
260
260
  category: "shipping_delivery",
261
- showOnHomepage: false,
261
+ showOnHomepage: true,
262
262
  showInFooter: false,
263
263
  isPinned: false,
264
264
  order: 13,
@@ -296,7 +296,7 @@ export const faqSeedData = [
296
296
  question: "How do I track my order?",
297
297
  answer: html(`<p>Once your seller ships the order, you'll receive a carrier name and tracking number via email and in-app notification. Go to Account → My Orders → click the order to see it. Paste the tracking number into the carrier's own website (India Post, DTDC, BlueDart, Delhivery, etc.) for real-time updates — sellers ship manually, so tracking is via the carrier's site, not an in-app live map.</p>`),
298
298
  category: "shipping_delivery",
299
- showOnHomepage: false,
299
+ showOnHomepage: true,
300
300
  showInFooter: false,
301
301
  isPinned: false,
302
302
  order: 15,
@@ -373,7 +373,7 @@ export const faqSeedData = [
373
373
  question: "How do I request a refund?",
374
374
  answer: html(`<p>Go to Account → My Orders → select the order → Request Refund. Describe the issue and upload photos if the item is damaged or not as described. The seller has 48 hours to respond. If they don't, LetItRip support will step in.</p>`),
375
375
  category: "returns_refunds",
376
- showOnHomepage: false,
376
+ showOnHomepage: true,
377
377
  showInFooter: false,
378
378
  isPinned: false,
379
379
  order: 19,
@@ -449,7 +449,7 @@ export const faqSeedData = [
449
449
  question: "What if I receive a counterfeit item?",
450
450
  answer: html(`<p>LetItRip has a <strong>zero-tolerance counterfeit policy</strong>. If you receive a counterfeit item, open a dispute with photo evidence. You'll receive a full refund and the seller's account will be reviewed for suspension. For high-value items, we recommend buying graded/certified products which come with third-party authentication.</p>`),
451
451
  category: "returns_refunds",
452
- showOnHomepage: false,
452
+ showOnHomepage: true,
453
453
  showInFooter: true,
454
454
  isPinned: false,
455
455
  order: 23,
@@ -488,7 +488,7 @@ export const faqSeedData = [
488
488
  question: "What payment methods are accepted?",
489
489
  answer: html(`<p>LetItRip's default payment method is <strong>Manual UPI / Bank Transfer</strong> — you pay directly and submit your payment reference, which the seller or LetItRip confirms before shipping. Where enabled, you can also pay instantly via <strong>Razorpay</strong> (cards, UPI, net banking), choose <strong>Cash on Delivery</strong> (a small deposit online plus cash on arrival), or spread the cost with <strong>EMI</strong> on eligible orders. Sellers only receive payout funds after you confirm delivery (or after the dispute window closes).</p>`),
490
490
  category: "orders_payment",
491
- showOnHomepage: false,
491
+ showOnHomepage: true,
492
492
  showInFooter: true,
493
493
  isPinned: false,
494
494
  order: 25,
@@ -660,7 +660,7 @@ export const faqSeedData = [
660
660
  question: "How can I verify if a collectible is authentic?",
661
661
  answer: html(`<p>Look for these trust signals on LetItRip: <strong>Verified Seller</strong> badge (store has passed manual review), <strong>Graded</strong> badge (PSA/BGS/CGC certified with certificate number), <strong>Sealed</strong> badge (factory-sealed product). For ungraded items, check the seller's photo quality and description detail. If in doubt, message the seller before purchasing.</p>`),
662
662
  category: "product_information",
663
- showOnHomepage: false,
663
+ showOnHomepage: true,
664
664
  showInFooter: false,
665
665
  isPinned: false,
666
666
  order: 32,
@@ -717,7 +717,7 @@ export const faqSeedData = [
717
717
  question: "What do condition grades like NM, LP, MP mean?",
718
718
  answer: html(`<p>Sellers use standard condition grades for ungraded items: <strong>NM</strong> (Near Mint) — virtually perfect, minimal handling wear. <strong>LP</strong> (Lightly Played) — minor edge wear, small scratches. <strong>MP</strong> (Moderately Played) — visible wear, light creasing. <strong>HP</strong> (Heavily Played) — significant wear, creases, whitening. <strong>Sealed</strong> — factory sealed, never opened. Always check the seller's photos for the actual condition.</p>`),
719
719
  category: "product_information",
720
- showOnHomepage: false,
720
+ showOnHomepage: true,
721
721
  showInFooter: false,
722
722
  isPinned: false,
723
723
  order: 35,
@@ -813,7 +813,7 @@ export const faqSeedData = [
813
813
  question: "How does bidding work on LetItRip?",
814
814
  answer: html(`<p>Place a bid on any active auction listing. You'll be notified if you're outbid. When the auction ends, the highest bidder wins. Some auctions have a Buy It Now (BIN) price — you can skip bidding and purchase immediately at the BIN price. Auctions auto-extend by 2 minutes if a bid is placed in the final 2 minutes to prevent sniping.</p>`),
815
815
  category: "general",
816
- showOnHomepage: false,
816
+ showOnHomepage: true,
817
817
  showInFooter: true,
818
818
  isPinned: false,
819
819
  order: 40,
@@ -909,7 +909,7 @@ export const faqSeedData = [
909
909
  question: "How do pre-orders work?",
910
910
  answer: html(`<p>Pre-orders let you reserve upcoming products with a deposit (typically 20–30% of the retail price). When the product releases and ships, you pay the remaining balance. If the seller cancels the pre-order, you receive a full refund of your deposit. Pre-orders are available for upcoming trading card sets, action figure releases, and model kit drops.</p>`),
911
911
  category: "general",
912
- showOnHomepage: false,
912
+ showOnHomepage: true,
913
913
  showInFooter: false,
914
914
  isPinned: false,
915
915
  order: 45,
@@ -301,7 +301,7 @@ export const homepageSectionsSeedData = [
301
301
  title: "Frequently Asked Questions",
302
302
  subtitle: "Quick answers about buying, selling, and collecting on LetItRip",
303
303
  showOnHomepage: true,
304
- displayCount: 10,
304
+ displayCount: 16,
305
305
  linkToFullPage: true,
306
306
  showCategoryTabs: true,
307
307
  visibleTabs: ["general", "shipping_delivery", "returns_refunds", "orders_payment", "product_information", "account_security"],