@mohasinac/appkit 2.6.6 → 2.6.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/_internal/server/jobs/core/onSupportTicketCreate.d.ts +10 -0
  2. package/dist/_internal/server/jobs/core/onSupportTicketCreate.js +26 -0
  3. package/dist/_internal/server/jobs/core/onSupportTicketUpdate.d.ts +15 -0
  4. package/dist/_internal/server/jobs/core/onSupportTicketUpdate.js +50 -0
  5. package/dist/_internal/server/jobs/core/onUserBanChange.d.ts +17 -0
  6. package/dist/_internal/server/jobs/core/onUserBanChange.js +59 -0
  7. package/dist/_internal/server/jobs/handlers/index.d.ts +3 -0
  8. package/dist/_internal/server/jobs/handlers/index.js +4 -0
  9. package/dist/_internal/server/jobs/handlers/onSupportTicketCreate.d.ts +2 -0
  10. package/dist/_internal/server/jobs/handlers/onSupportTicketCreate.js +7 -0
  11. package/dist/_internal/server/jobs/handlers/onSupportTicketUpdate.d.ts +2 -0
  12. package/dist/_internal/server/jobs/handlers/onSupportTicketUpdate.js +8 -0
  13. package/dist/_internal/server/jobs/handlers/onUserBanChange.d.ts +2 -0
  14. package/dist/_internal/server/jobs/handlers/onUserBanChange.js +10 -0
  15. package/dist/client.d.ts +2 -2
  16. package/dist/client.js +1 -1
  17. package/dist/configs/next.js +12 -0
  18. package/dist/features/account/components/UserSupportView.d.ts +3 -0
  19. package/dist/features/account/components/UserSupportView.js +90 -0
  20. package/dist/features/account/components/index.d.ts +2 -0
  21. package/dist/features/account/components/index.js +1 -0
  22. package/dist/features/admin/components/AdminScammerEditorView.d.ts +15 -0
  23. package/dist/features/admin/components/AdminScammerEditorView.js +61 -0
  24. package/dist/features/admin/components/AdminScammersView.d.ts +4 -0
  25. package/dist/features/admin/components/AdminScammersView.js +147 -0
  26. package/dist/features/admin/components/index.d.ts +4 -0
  27. package/dist/features/admin/components/index.js +2 -0
  28. package/dist/features/admin/constants/filter-tabs.d.ts +17 -0
  29. package/dist/features/admin/constants/filter-tabs.js +8 -0
  30. package/dist/features/faq/schemas/firestore.js +1 -0
  31. package/dist/features/faq/types/index.d.ts +1 -1
  32. package/dist/features/layout/AppLayoutShell.d.ts +2 -1
  33. package/dist/features/layout/AppLayoutShell.js +10 -4
  34. package/dist/features/layout/TitleBarLayout.d.ts +3 -1
  35. package/dist/features/layout/TitleBarLayout.js +8 -4
  36. package/dist/index.d.ts +4 -0
  37. package/dist/index.js +4 -0
  38. package/dist/jobs.d.ts +1 -1
  39. package/dist/jobs.js +2 -0
  40. package/dist/next/routing/route-map.d.ts +14 -0
  41. package/dist/next/routing/route-map.js +7 -0
  42. package/dist/seed/faq-seed-data.js +303 -0
  43. package/dist/seed/users-seed-data.js +81 -3
  44. package/dist/tailwind-utilities.css +1 -1
  45. package/dist/ui/components/Badge.d.ts +1 -0
  46. package/dist/ui/components/Badge.js +1 -0
  47. package/dist/ui/components/Badge.style.css +11 -0
  48. package/dist/ui/components/RoleBadge.js +2 -0
  49. package/package.json +1 -1
@@ -0,0 +1,147 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import React, { useState, useCallback } from "react";
4
+ import { X } from "lucide-react";
5
+ import { useUrlTable } from "../../../react/hooks/useUrlTable";
6
+ import { FilterChipGroup, ListingToolbar, ListingViewShell, Pagination, RowActionMenu, } from "../../../ui";
7
+ import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
8
+ import { ADMIN_SCAMMER_STATUS_TABS } from "../constants/filter-tabs";
9
+ import { toRecordArray, toRelativeDate, toStringValue, useAdminListingData, } from "../hooks/useAdminListingData";
10
+ import { DataTable } from "./DataTable";
11
+ import { AdminScammerEditorView } from "./AdminScammerEditorView";
12
+ const PAGE_SIZE = 25;
13
+ const FILTER_KEYS = ["status"];
14
+ const DEFAULT_SORT = "-createdAt";
15
+ const SORT_OPTIONS = [
16
+ { value: "-createdAt", label: "Newest" },
17
+ { value: "createdAt", label: "Oldest" },
18
+ { value: "-views", label: "Most viewed" },
19
+ { value: "-incidentCount", label: "Most incidents" },
20
+ ];
21
+ const STATUS_BADGE = {
22
+ pending_review: "bg-yellow-100 text-yellow-700 dark:bg-yellow-900/40 dark:text-yellow-300",
23
+ verified: "bg-green-100 text-green-700 dark:bg-green-900/40 dark:text-green-300",
24
+ rejected: "bg-red-100 text-red-700 dark:bg-red-900/40 dark:text-red-300",
25
+ removed: "bg-zinc-100 text-zinc-600 dark:bg-zinc-800 dark:text-zinc-400",
26
+ };
27
+ export function AdminScammersView({ children, ...props }) {
28
+ const hasChildren = React.Children.count(children) > 0;
29
+ const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
30
+ const [searchInput, setSearchInput] = useState(table.get("q") || "");
31
+ const [filterOpen, setFilterOpen] = useState(false);
32
+ const [pendingFilters, setPendingFilters] = useState(() => Object.fromEntries(FILTER_KEYS.map((k) => [k, table.get(k)])));
33
+ const [drawerOpen, setDrawerOpen] = useState(false);
34
+ const [selectedRow, setSelectedRow] = useState(null);
35
+ const openFilters = useCallback(() => {
36
+ setPendingFilters(Object.fromEntries(FILTER_KEYS.map((k) => [k, table.get(k)])));
37
+ setFilterOpen(true);
38
+ }, [table]);
39
+ const applyFilters = useCallback(() => {
40
+ const updates = { page: "1" };
41
+ for (const k of FILTER_KEYS)
42
+ updates[k] = pendingFilters[k] ?? "";
43
+ table.setMany(updates);
44
+ setFilterOpen(false);
45
+ }, [pendingFilters, table]);
46
+ const clearFilters = useCallback(() => {
47
+ setPendingFilters(Object.fromEntries(FILTER_KEYS.map((k) => [k, ""])));
48
+ }, []);
49
+ const resetAll = useCallback(() => {
50
+ const updates = { q: "", sort: "" };
51
+ for (const k of FILTER_KEYS)
52
+ updates[k] = "";
53
+ table.setMany(updates);
54
+ setSearchInput("");
55
+ }, [table]);
56
+ const commitSearch = useCallback(() => {
57
+ table.set("q", searchInput.trim());
58
+ }, [searchInput, table]);
59
+ const activeFilterCount = FILTER_KEYS.filter((k) => !!table.get(k)).length;
60
+ const hasActiveState = !!table.get("q") || table.get("sort") !== DEFAULT_SORT || activeFilterCount > 0;
61
+ const filterParts = [];
62
+ const statusRaw = table.get("status");
63
+ if (statusRaw && statusRaw !== "All")
64
+ filterParts.push(`status==${statusRaw}`);
65
+ const filters = filterParts.join(",") || undefined;
66
+ const { rows, total, isLoading, errorMessage } = useAdminListingData({
67
+ queryKey: ["admin", "scammers", "listing"],
68
+ endpoint: ADMIN_ENDPOINTS.SCAMMERS,
69
+ page: table.getNumber("page", 1),
70
+ pageSize: PAGE_SIZE,
71
+ sorts: table.get("sort") || DEFAULT_SORT,
72
+ filters,
73
+ q: table.get("q") || undefined,
74
+ mapRows: (response) => toRecordArray(response.scammers).map((item, index) => ({
75
+ id: toStringValue(item.id, `scammer-${index}`),
76
+ primary: Array.isArray(item.displayNames)
77
+ ? item.displayNames.join(", ")
78
+ : toStringValue(item.displayNames, "Unknown"),
79
+ secondary: [
80
+ toStringValue(item.scamType, ""),
81
+ Array.isArray(item.phones) ? `${item.phones.length} phone(s)` : null,
82
+ `${Number(item.incidentCount ?? 0)} incident(s)`,
83
+ ]
84
+ .filter(Boolean)
85
+ .join(" · "),
86
+ status: toStringValue(item.status, "pending_review"),
87
+ updatedAt: toRelativeDate(item.updatedAt ?? item.createdAt),
88
+ _raw: item,
89
+ })),
90
+ getTotal: (response, mappedRows) => {
91
+ if (typeof response.meta?.filteredTotal === "number")
92
+ return response.meta.filteredTotal;
93
+ if (typeof response.meta?.total === "number")
94
+ return response.meta.total;
95
+ if (typeof response.total === "number")
96
+ return response.total;
97
+ return mappedRows.length;
98
+ },
99
+ });
100
+ const currentPage = table.getNumber("page", 1);
101
+ const totalPages = Math.ceil(total / PAGE_SIZE);
102
+ if (hasChildren) {
103
+ return (_jsx(ListingViewShell, { portal: "admin", ...props, children: children }));
104
+ }
105
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search by name, phone, UPI ID", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => {
106
+ table.set("sort", v);
107
+ table.setPage(1);
108
+ }, hideViewToggle: true, onResetAll: resetAll, hasActiveState: hasActiveState }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), _jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No scammer profiles found", renderRowActions: (row) => (_jsx(RowActionMenu, { actions: [
109
+ {
110
+ label: "Review",
111
+ onClick: () => {
112
+ setSelectedRow(row);
113
+ setDrawerOpen(true);
114
+ },
115
+ },
116
+ ] })), columns: [
117
+ {
118
+ key: "primary",
119
+ header: "Name / Aliases",
120
+ render: (row) => {
121
+ const r = row;
122
+ return (_jsxs("div", { className: "space-y-0.5", children: [_jsx("p", { className: "font-medium text-zinc-900 dark:text-zinc-100", children: r.primary }), r.secondary ? (_jsx("p", { className: "text-xs text-zinc-500 dark:text-zinc-400", children: r.secondary })) : null] }));
123
+ },
124
+ },
125
+ {
126
+ key: "status",
127
+ header: "Status",
128
+ className: "w-36",
129
+ render: (row) => {
130
+ const r = row;
131
+ return (_jsx("span", { className: `inline-flex rounded-full px-2.5 py-1 text-xs font-medium ${STATUS_BADGE[r.status] ?? STATUS_BADGE.pending_review}`, children: r.status.replace(/_/g, " ") }));
132
+ },
133
+ },
134
+ {
135
+ key: "updatedAt",
136
+ header: "Updated",
137
+ className: "w-32",
138
+ render: (row) => (_jsx("span", { className: "text-sm text-zinc-500 dark:text-zinc-400", children: row.updatedAt })),
139
+ },
140
+ ] })] }), filterOpen && (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-40 bg-black/40", "aria-hidden": "true", onClick: () => setFilterOpen(false) }), _jsxs("div", { className: "fixed inset-y-0 left-0 z-50 flex w-80 flex-col bg-white dark:bg-slate-900 shadow-2xl", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: [_jsx("span", { className: "text-base font-semibold text-zinc-900 dark:text-zinc-100", children: "Filters" }), _jsxs("div", { className: "flex items-center gap-2", children: [activeFilterCount > 0 && (_jsx("button", { type: "button", onClick: clearFilters, className: "text-xs text-zinc-500 hover:text-rose-500 dark:text-zinc-400 transition-colors", children: "Clear all" })), _jsx("button", { type: "button", onClick: () => setFilterOpen(false), "aria-label": "Close", className: "rounded-lg p-1.5 text-zinc-500 hover:bg-zinc-100 dark:hover:bg-slate-800 transition-colors", children: _jsx(X, { className: "h-5 w-5" }) })] })] }), _jsx("div", { className: "flex-1 overflow-y-auto px-4 py-4 space-y-5", children: _jsx(FilterChipGroup, { label: "Status", tabs: ADMIN_SCAMMER_STATUS_TABS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) }) }), _jsx("div", { className: "border-t border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: _jsxs("button", { type: "button", onClick: applyFilters, className: "w-full rounded-lg bg-primary py-2.5 text-sm font-semibold text-white hover:bg-primary-600 transition-colors active:scale-[0.98]", children: ["Apply Filters", activeFilterCount > 0 ? ` (${activeFilterCount})` : ""] }) })] })] }))] }), _jsx(AdminScammerEditorView, { open: drawerOpen, onClose: () => setDrawerOpen(false), scammerId: selectedRow?.id, displayNames: Array.isArray(selectedRow?._raw?.displayNames)
141
+ ? selectedRow._raw.displayNames
142
+ : [selectedRow?.primary ?? ""], scamType: toStringValue(selectedRow?._raw?.scamType, ""), description: toStringValue(selectedRow?._raw?.description, ""), phones: Array.isArray(selectedRow?._raw?.phones)
143
+ ? selectedRow._raw.phones
144
+ : [], upiIds: Array.isArray(selectedRow?._raw?.upiIds)
145
+ ? selectedRow._raw.upiIds
146
+ : [], currentStatus: selectedRow?.status, verificationNote: toStringValue(selectedRow?._raw?.verificationNote, "") || undefined, reportedBy: toStringValue(selectedRow?._raw?.reportedBy, ""), reportedByAnon: Boolean(selectedRow?._raw?.reportedByAnon) })] }));
147
+ }
@@ -141,3 +141,7 @@ export { AdminSupportTicketsView } from "./AdminSupportTicketsView";
141
141
  export type { AdminSupportTicketsViewProps } from "./AdminSupportTicketsView";
142
142
  export { AdminSupportTicketDetailView } from "./AdminSupportTicketDetailView";
143
143
  export type { AdminSupportTicketDetailViewProps } from "./AdminSupportTicketDetailView";
144
+ export { AdminScammersView } from "./AdminScammersView";
145
+ export type { AdminScammersViewProps } from "./AdminScammersView";
146
+ export { AdminScammerEditorView } from "./AdminScammerEditorView";
147
+ export type { AdminScammerEditorViewProps } from "./AdminScammerEditorView";
@@ -73,3 +73,5 @@ export { AdminTeamView } from "./AdminTeamView";
73
73
  export { AdminEmployeeEditorView } from "./AdminEmployeeEditorView";
74
74
  export { AdminSupportTicketsView } from "./AdminSupportTicketsView";
75
75
  export { AdminSupportTicketDetailView } from "./AdminSupportTicketDetailView";
76
+ export { AdminScammersView } from "./AdminScammersView";
77
+ export { AdminScammerEditorView } from "./AdminScammerEditorView";
@@ -266,6 +266,23 @@ export declare const ADMIN_EVENT_STATUS_TABS: readonly [{
266
266
  readonly id: "ended";
267
267
  readonly label: "Ended";
268
268
  }];
269
+ /** Admin > Scammers — scammer profile status filter chip set. */
270
+ export declare const ADMIN_SCAMMER_STATUS_TABS: readonly [{
271
+ readonly id: "All";
272
+ readonly label: "All";
273
+ }, {
274
+ readonly id: "pending_review";
275
+ readonly label: "Pending";
276
+ }, {
277
+ readonly id: "verified";
278
+ readonly label: "Verified";
279
+ }, {
280
+ readonly id: "rejected";
281
+ readonly label: "Rejected";
282
+ }, {
283
+ readonly id: "removed";
284
+ readonly label: "Removed";
285
+ }];
269
286
  /** Admin > Support Tickets — ticket-status filter chip set. */
270
287
  export declare const ADMIN_SUPPORT_TICKET_STATUS_TABS: readonly [{
271
288
  readonly id: "All";
@@ -130,6 +130,14 @@ export const ADMIN_EVENT_STATUS_TABS = [
130
130
  { id: "active", label: "Active" },
131
131
  { id: "ended", label: "Ended" },
132
132
  ];
133
+ /** Admin > Scammers — scammer profile status filter chip set. */
134
+ export const ADMIN_SCAMMER_STATUS_TABS = [
135
+ ALL_TAB,
136
+ { id: "pending_review", label: "Pending" },
137
+ { id: "verified", label: "Verified" },
138
+ { id: "rejected", label: "Rejected" },
139
+ { id: "removed", label: "Removed" },
140
+ ];
133
141
  /** Admin > Support Tickets — ticket-status filter chip set. */
134
142
  export const ADMIN_SUPPORT_TICKET_STATUS_TABS = [
135
143
  ALL_TAB,
@@ -27,6 +27,7 @@ export const FAQ_CATEGORY_LABELS = {
27
27
  account_security: "Account & Security",
28
28
  technical_support: "Technical Support",
29
29
  general: "General Questions",
30
+ scam_awareness: "Scam Awareness",
30
31
  };
31
32
  export const DEFAULT_FAQ_DATA = {
32
33
  showOnHomepage: false,
@@ -1,5 +1,5 @@
1
1
  export type FAQAnswerFormat = "plain" | "markdown" | "html";
2
- export type FAQCategory = "orders_payment" | "shipping_delivery" | "returns_refunds" | "product_information" | "account_security" | "technical_support" | "general";
2
+ export type FAQCategory = "orders_payment" | "shipping_delivery" | "returns_refunds" | "product_information" | "account_security" | "technical_support" | "general" | "scam_awareness";
3
3
  export interface FAQAnswer {
4
4
  text: string;
5
5
  format: FAQAnswerFormat;
@@ -41,6 +41,7 @@ export interface AppLayoutShellProps {
41
41
  userId?: string | null;
42
42
  profileHref: string;
43
43
  loginHref: string;
44
+ registerHref?: string;
44
45
  homeHref: string;
45
46
  shopHref: string;
46
47
  footer: FooterLayoutProps;
@@ -106,4 +107,4 @@ export interface AppLayoutShellProps {
106
107
  };
107
108
  };
108
109
  }
109
- export declare function AppLayoutShell({ children, navItems, sidebarItems, sidebarSections, sidebarPrimaryActions, sidebarTitle, hiddenNavItems, user, brandName, brandShortName, siteLogoUrl, logoHref, promotionsHref, cartHref, wishlistHref, userId, profileHref, loginHref, homeHref, shopHref, footer, searchSlot, searchSlotRenderer, titleBarNavSlot, titleBarNotificationSlot, titleBarDevSlot, titleBarPromoStripText, showThemeToggle, suppressDashboardNav, hideSidebarToggle, onLogout, adminHref, storeHref, sellerHref, userOrdersHref, userWishlistHref, userSettingsHref, sidebarLocaleSlot, showThemeToggleInSidebar, sidebarProfileLabels, eventBannerSlot, lightBackground, darkBackground, }: AppLayoutShellProps): import("react/jsx-runtime").JSX.Element;
110
+ export declare function AppLayoutShell({ children, navItems, sidebarItems, sidebarSections, sidebarPrimaryActions, sidebarTitle, hiddenNavItems, user, brandName, brandShortName, siteLogoUrl, logoHref, promotionsHref, cartHref, wishlistHref, userId, profileHref, loginHref, registerHref, homeHref, shopHref, footer, searchSlot, searchSlotRenderer, titleBarNavSlot, titleBarNotificationSlot, titleBarDevSlot, titleBarPromoStripText, showThemeToggle, suppressDashboardNav, hideSidebarToggle, onLogout, adminHref, storeHref, sellerHref, userOrdersHref, userWishlistHref, userSettingsHref, sidebarLocaleSlot, showThemeToggleInSidebar, sidebarProfileLabels, eventBannerSlot, lightBackground, darkBackground, }: AppLayoutShellProps): import("react/jsx-runtime").JSX.Element;
@@ -2,7 +2,13 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
4
4
  import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
5
- import { Main, Div, Text, TextLink, Ul, Li, AvatarDisplay, RoleBadge, BackgroundRenderer, UnsavedChangesModal, } from "../../ui";
5
+ import { Main, Div, Text, TextLink, Ul, Li, AvatarDisplay, BackgroundRenderer, UnsavedChangesModal, } from "../../ui";
6
+ const ROLE_DOT_COLORS = {
7
+ admin: "#9333ea",
8
+ moderator: "#0ea5e9",
9
+ seller: "#0d9488",
10
+ employee: "#f59e0b",
11
+ };
6
12
  import { useTheme } from "../../react";
7
13
  import { useBottomActionsContext } from "./BottomActionsContext";
8
14
  import BottomActions from "./BottomActions";
@@ -37,7 +43,7 @@ function CollapsibleSidebarSection({ section, navItemClass, }) {
37
43
  }
38
44
  return (_jsxs(Div, { className: "space-y-0.5", children: [_jsxs("button", { type: "button", onClick: () => setOpen((v) => !v), className: "flex w-full items-center justify-between px-1 py-1 text-xs font-semibold uppercase tracking-wider text-zinc-500 dark:text-zinc-400 hover:text-zinc-700 dark:hover:text-zinc-300 transition-colors", children: [_jsx("span", { children: section.title }), _jsx("svg", { className: `w-3.5 h-3.5 transition-transform duration-200 ${open ? "rotate-180" : ""}`, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 9l-7 7-7-7" }) })] }), open && (_jsx(Ul, { className: "space-y-0.5", children: section.items.map((item) => (_jsx(Li, { children: _jsxs(TextLink, { href: item.href, variant: "none", className: navItemClass, children: [item.icon && (_jsx("span", { className: "flex-shrink-0 w-5 text-center", "aria-hidden": "true", children: item.icon })), item.label] }) }, `${item.href}-${item.label}`))) }))] }));
39
45
  }
40
- export function AppLayoutShell({ children, navItems, sidebarItems = [], sidebarSections, sidebarPrimaryActions, sidebarTitle = "Navigation", hiddenNavItems, user, brandName, brandShortName, siteLogoUrl, logoHref, promotionsHref, cartHref, wishlistHref, userId, profileHref, loginHref, homeHref, shopHref, footer, searchSlot, searchSlotRenderer, titleBarNavSlot, titleBarNotificationSlot, titleBarDevSlot, titleBarPromoStripText, showThemeToggle = false, suppressDashboardNav = false, hideSidebarToggle = false, onLogout, adminHref, storeHref, sellerHref, userOrdersHref, userWishlistHref, userSettingsHref, sidebarLocaleSlot, showThemeToggleInSidebar = false, sidebarProfileLabels, eventBannerSlot, lightBackground = DEFAULT_LIGHT_BG, darkBackground = DEFAULT_DARK_BG, }) {
46
+ export function AppLayoutShell({ children, navItems, sidebarItems = [], sidebarSections, sidebarPrimaryActions, sidebarTitle = "Navigation", hiddenNavItems, user, brandName, brandShortName, siteLogoUrl, logoHref, promotionsHref, cartHref, wishlistHref, userId, profileHref, loginHref, registerHref, homeHref, shopHref, footer, searchSlot, searchSlotRenderer, titleBarNavSlot, titleBarNotificationSlot, titleBarDevSlot, titleBarPromoStripText, showThemeToggle = false, suppressDashboardNav = false, hideSidebarToggle = false, onLogout, adminHref, storeHref, sellerHref, userOrdersHref, userWishlistHref, userSettingsHref, sidebarLocaleSlot, showThemeToggleInSidebar = false, sidebarProfileLabels, eventBannerSlot, lightBackground = DEFAULT_LIGHT_BG, darkBackground = DEFAULT_DARK_BG, }) {
41
47
  const [queryClient] = useState(() => new QueryClient());
42
48
  const [sidebarOpen, setSidebarOpen] = useState(false);
43
49
  const [searchOpen, setSearchOpen] = useState(false);
@@ -148,7 +154,7 @@ export function AppLayoutShell({ children, navItems, sidebarItems = [], sidebarS
148
154
  opacity: darkBackground.overlay?.opacity ?? 0,
149
155
  },
150
156
  };
151
- return (_jsx(QueryClientProvider, { client: queryClient, children: _jsxs(Div, { className: "flex min-h-screen w-full flex-col overflow-x-clip transition-colors duration-300", children: [_jsx(BackgroundRenderer, { mode: theme === "dark" ? "dark" : "light", lightMode: normalizedLightBackground, darkMode: normalizedDarkBackground }), _jsxs(Div, { ref: headerRef, className: "sticky top-0 z-50 w-full", children: [_jsx(TitleBar, { onToggleSidebar: handleTogglePublicSidebar, sidebarOpen: sidebarOpen, onSearchToggle: () => setSearchOpen((prev) => !prev), searchOpen: searchOpen, brandName: brandName, brandShortName: brandShortName, siteLogoUrl: siteLogoUrl, logoHref: logoHref, promotionsHref: promotionsHref, cartHref: cartHref, wishlistHref: wishlistHref, userId: userId, profileHref: profileHref, user: user, navSlot: titleBarNavSlot, notificationSlot: titleBarNotificationSlot, devSlot: titleBarDevSlot, promoStripText: titleBarPromoStripText, isDark: theme === "dark", onToggleTheme: showThemeToggle ? toggleTheme : undefined, onBeforeToggleDashboardNav: handleBeforeDashboardNavToggle, suppressDashboardNav: suppressDashboardNav, hideSidebarToggle: hideSidebarToggle }), _jsx(MainNavbar, { navItems: navItems, hiddenNavItems: hiddenNavItems }), searchOpen && (searchSlotRenderer ? searchSlotRenderer(() => setSearchOpen(false)) : searchSlot)] }), eventBannerSlot, _jsx(AutoBreadcrumbs, {}), _jsxs(Div, { className: "relative flex w-full flex-1 overflow-x-clip", children: [_jsx(SidebarLayout, { isOpen: sidebarOpen, ariaLabel: "Secondary navigation", header: user ? (_jsxs(Div, { className: "flex items-center justify-between gap-3", children: [_jsxs(Div, { className: "flex items-center gap-3 flex-1 min-w-0", children: [_jsxs(Div, { className: "flex-shrink-0 relative", children: [_jsx(AvatarDisplay, { cropData: user.avatarMetadata
157
+ return (_jsx(QueryClientProvider, { client: queryClient, children: _jsxs(Div, { className: "flex min-h-screen w-full flex-col overflow-x-clip transition-colors duration-300", children: [_jsx(BackgroundRenderer, { mode: theme === "dark" ? "dark" : "light", lightMode: normalizedLightBackground, darkMode: normalizedDarkBackground }), _jsxs(Div, { ref: headerRef, className: "sticky top-0 z-50 w-full", children: [_jsx(TitleBar, { onToggleSidebar: handleTogglePublicSidebar, sidebarOpen: sidebarOpen, onSearchToggle: () => setSearchOpen((prev) => !prev), searchOpen: searchOpen, brandName: brandName, brandShortName: brandShortName, siteLogoUrl: siteLogoUrl, logoHref: logoHref, promotionsHref: promotionsHref, cartHref: cartHref, wishlistHref: wishlistHref, userId: userId, profileHref: profileHref, loginHref: loginHref, registerHref: registerHref, user: user, navSlot: titleBarNavSlot, notificationSlot: titleBarNotificationSlot, devSlot: titleBarDevSlot, promoStripText: titleBarPromoStripText, isDark: theme === "dark", onToggleTheme: showThemeToggle ? toggleTheme : undefined, onBeforeToggleDashboardNav: handleBeforeDashboardNavToggle, suppressDashboardNav: suppressDashboardNav, hideSidebarToggle: hideSidebarToggle }), _jsx(MainNavbar, { navItems: navItems, hiddenNavItems: hiddenNavItems }), searchOpen && (searchSlotRenderer ? searchSlotRenderer(() => setSearchOpen(false)) : searchSlot)] }), eventBannerSlot, _jsx(AutoBreadcrumbs, {}), _jsxs(Div, { className: "relative flex w-full flex-1 overflow-x-clip", children: [_jsx(SidebarLayout, { isOpen: sidebarOpen, ariaLabel: "Secondary navigation", header: user ? (_jsxs(Div, { className: "flex items-center justify-between gap-3", children: [_jsxs(Div, { className: "flex items-center gap-3 flex-1 min-w-0", children: [_jsxs(Div, { className: "flex-shrink-0 relative", children: [_jsx(AvatarDisplay, { cropData: user.avatarMetadata
152
158
  ? {
153
159
  url: user.avatarMetadata.url,
154
160
  position: user.avatarMetadata.position ?? {
@@ -163,5 +169,5 @@ export function AppLayoutShell({ children, navItems, sidebarItems = [], sidebarS
163
169
  position: { x: 50, y: 50 },
164
170
  zoom: 1,
165
171
  }
166
- : null, size: "md", alt: user.displayName || "User", displayName: user.displayName, email: user.email }), user.role && (_jsx(Div, { className: "absolute -bottom-1 -right-1", children: _jsx(RoleBadge, { role: user.role }) }))] }), _jsxs(Div, { className: "flex-1 min-w-0", children: [_jsx(Text, { className: "text-sm font-medium text-zinc-900 dark:text-zinc-100 truncate", children: user.displayName || "User" }), _jsx(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400 truncate", children: user.email || "" })] })] }), _jsx("button", { type: "button", "aria-label": "Close menu", onClick: () => setSidebarOpen(false), className: "flex-shrink-0 rounded-full p-2 text-zinc-600 hover:bg-zinc-200 hover:text-zinc-900 dark:text-zinc-300 dark:hover:bg-slate-800 dark:hover:text-zinc-100 transition-all hover:rotate-90", children: _jsx("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) })] })) : (_jsxs(Div, { className: "flex items-center justify-between", children: [_jsx(Div, { className: "text-sm font-semibold text-zinc-900 dark:text-zinc-100", children: sidebarTitle }), _jsx("button", { type: "button", "aria-label": "Close menu", onClick: () => setSidebarOpen(false), className: "rounded-full p-2 text-zinc-600 hover:bg-zinc-200 hover:text-zinc-900 dark:text-zinc-300 dark:hover:bg-slate-800 dark:hover:text-zinc-100 transition-all hover:rotate-90", children: _jsx("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) })] })), onClose: () => setSidebarOpen(false), children: sidebarContent }), _jsx(Main, { id: "main-content", className: `w-full flex-1 ${hasBottomActions ? "mb-28" : "mb-16"} md:mb-0`, children: _jsx(Div, { className: "container mx-auto w-full max-w-screen-2xl px-4 py-6 md:px-6 lg:px-8", children: children }) })] }), _jsx(BackToTop, {}), _jsx(FooterLayout, { ...footer }), _jsx(BottomActions, {}), _jsx(BottomNavbar, { user: user, homeHref: homeHref, shopHref: shopHref, cartHref: cartHref, profileHref: profileHref, loginHref: loginHref, onSearchToggle: () => setSearchOpen((prev) => !prev), navItems: navItems, onMoreToggle: hasDashboardNav ? toggleDashboardNav : handleTogglePublicSidebar }), _jsx(UnsavedChangesModal, {})] }) }));
172
+ : null, size: "md", alt: user.displayName || "User", displayName: user.displayName, email: user.email }), user.role && user.role !== "user" && (_jsx(Div, { className: "absolute -bottom-0.5 -right-0.5 flex items-center justify-center w-4 h-4 rounded-full border-2 border-white dark:border-slate-900 text-white text-[9px] font-bold leading-none select-none", style: { background: ROLE_DOT_COLORS[user.role] ?? "#6b7280" }, title: user.role.charAt(0).toUpperCase() + user.role.slice(1), "aria-label": user.role, children: user.role.charAt(0).toUpperCase() }))] }), _jsxs(Div, { className: "flex-1 min-w-0", children: [_jsx(Text, { className: "text-sm font-medium text-zinc-900 dark:text-zinc-100 truncate", children: user.displayName || "User" }), _jsx(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400 truncate", children: user.email || "" })] })] }), _jsx("button", { type: "button", "aria-label": "Close menu", onClick: () => setSidebarOpen(false), className: "flex-shrink-0 rounded-full p-2 text-zinc-600 hover:bg-zinc-200 hover:text-zinc-900 dark:text-zinc-300 dark:hover:bg-slate-800 dark:hover:text-zinc-100 transition-all hover:rotate-90", children: _jsx("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) })] })) : (_jsxs(Div, { className: "flex items-center justify-between", children: [_jsx(Div, { className: "text-sm font-semibold text-zinc-900 dark:text-zinc-100", children: sidebarTitle }), _jsx("button", { type: "button", "aria-label": "Close menu", onClick: () => setSidebarOpen(false), className: "rounded-full p-2 text-zinc-600 hover:bg-zinc-200 hover:text-zinc-900 dark:text-zinc-300 dark:hover:bg-slate-800 dark:hover:text-zinc-100 transition-all hover:rotate-90", children: _jsx("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) })] })), onClose: () => setSidebarOpen(false), children: sidebarContent }), _jsx(Main, { id: "main-content", className: `w-full flex-1 ${hasBottomActions ? "mb-28" : "mb-16"} md:mb-0`, children: _jsx(Div, { className: "container mx-auto w-full max-w-screen-2xl px-4 py-6 md:px-6 lg:px-8", children: children }) })] }), _jsx(BackToTop, {}), _jsx(FooterLayout, { ...footer }), _jsx(BottomActions, {}), _jsx(BottomNavbar, { user: user, homeHref: homeHref, shopHref: shopHref, cartHref: cartHref, profileHref: profileHref, loginHref: loginHref, onSearchToggle: () => setSearchOpen((prev) => !prev), navItems: navItems, onMoreToggle: hasDashboardNav ? toggleDashboardNav : handleTogglePublicSidebar }), _jsx(UnsavedChangesModal, {})] }) }));
167
173
  }
@@ -25,6 +25,8 @@ export interface TitleBarLayoutProps {
25
25
  cartHref?: string;
26
26
  cartCount?: number;
27
27
  profileHref?: string;
28
+ loginHref?: string;
29
+ registerHref?: string;
28
30
  user?: TitleBarUser | null;
29
31
  /** Slot rendered beside the profile link (e.g. NotificationBell). */
30
32
  notificationSlot?: React.ReactNode;
@@ -54,4 +56,4 @@ export interface TitleBarLayoutProps {
54
56
  *
55
57
  * Receives all domain data as props — zero domain imports.
56
58
  */
57
- export declare function TitleBarLayout({ onToggleSidebar, sidebarOpen, onSearchToggle, searchOpen: _searchOpen, brandName, brandShortName: _brandShortName, siteLogoUrl, logoHref, promotionsHref, compareHref, wishlistHref, wishlistCount, cartHref, cartCount, profileHref, user, notificationSlot, devSlot, navSlot, promoStripText, isDark, onToggleTheme, hasDashboardNav: _hasDashboardNav, onToggleDashboardNav: _onToggleDashboardNav, hideSidebarToggle, id, className, }: TitleBarLayoutProps): import("react/jsx-runtime").JSX.Element;
59
+ export declare function TitleBarLayout({ onToggleSidebar, sidebarOpen, onSearchToggle, searchOpen: _searchOpen, brandName, brandShortName: _brandShortName, siteLogoUrl, logoHref, promotionsHref, compareHref, wishlistHref, wishlistCount, cartHref, cartCount, profileHref, loginHref, registerHref, user, notificationSlot, devSlot, navSlot, promoStripText, isDark, onToggleTheme, hasDashboardNav: _hasDashboardNav, onToggleDashboardNav: _onToggleDashboardNav, hideSidebarToggle, id, className, }: TitleBarLayoutProps): import("react/jsx-runtime").JSX.Element;
@@ -1,5 +1,4 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import Image from "next/image";
3
2
  import Link from "next/link";
4
3
  import { BlockHeader, Button, Div, Row, SiteLogo, Span } from "../../ui";
5
4
  /** Shared icon-button class for all action buttons in the title bar. */
@@ -15,7 +14,7 @@ const countBadge = "absolute -top-0.5 -right-0.5 flex items-center justify-cente
15
14
  *
16
15
  * Receives all domain data as props — zero domain imports.
17
16
  */
18
- export function TitleBarLayout({ onToggleSidebar, sidebarOpen, onSearchToggle, searchOpen: _searchOpen, brandName, brandShortName: _brandShortName, siteLogoUrl, logoHref, promotionsHref, compareHref, wishlistHref, wishlistCount = 0, cartHref, cartCount = 0, profileHref, user, notificationSlot, devSlot, navSlot, promoStripText, isDark = false, onToggleTheme, hasDashboardNav: _hasDashboardNav, onToggleDashboardNav: _onToggleDashboardNav, hideSidebarToggle = false, id = "titlebar", className = "", }) {
17
+ export function TitleBarLayout({ onToggleSidebar, sidebarOpen, onSearchToggle, searchOpen: _searchOpen, brandName, brandShortName: _brandShortName, siteLogoUrl, logoHref, promotionsHref, compareHref, wishlistHref, wishlistCount = 0, cartHref, cartCount = 0, profileHref, loginHref, registerHref, user, notificationSlot, devSlot, navSlot, promoStripText, isDark = false, onToggleTheme, hasDashboardNav: _hasDashboardNav, onToggleDashboardNav: _onToggleDashboardNav, hideSidebarToggle = false, id = "titlebar", className = "", }) {
19
18
  // ── Element builders ────────────────────────────────────────────────────────
20
19
  const promotionsEl = promotionsHref ? (_jsxs(Link, { href: promotionsHref, "aria-label": "Today's deals", className: "flex items-center gap-1 px-3 py-1 rounded-full text-xs font-bold bg-primary-100 text-primary-700 dark:bg-secondary-900/40 dark:text-secondary-400 hover:bg-primary-200 dark:hover:bg-secondary-900/60 transition-colors border border-primary-200/60 dark:border-secondary-700/40", children: [_jsx("svg", { className: "w-3 h-3", viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true", children: _jsx("path", { d: "M21.41 11.58l-9-9C12.05 2.22 11.55 2 11 2H4c-1.1 0-2 .9-2 2v7c0 .55.22 1.05.59 1.42l9 9c.36.37.86.58 1.41.58.55 0 1.05-.21 1.41-.59l7-7c.37-.36.59-.86.59-1.41 0-.55-.23-1.06-.59-1.42zM5.5 7C4.67 7 4 6.33 4 5.5S4.67 4 5.5 4 7 4.67 7 5.5 6.33 7 5.5 7z" }) }), _jsx(Span, { className: "hidden sm:inline", children: "Today's Deals" })] })) : null;
21
20
  const themeBtn = onToggleTheme ? (_jsx(Button, { type: "button", variant: "ghost", size: "sm", "aria-label": isDark ? "Switch to light mode" : "Switch to dark mode", onClick: onToggleTheme, className: iconBtn, children: isDark ? (_jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 3v1m0 16v1m8.66-9h-1M4.34 12h-1m15.07-6.07-.71.71M6.34 17.66l-.71.71m12.73 0-.71-.71M6.34 6.34l-.71-.71M12 5a7 7 0 1 0 0 14A7 7 0 0 0 12 5z" }) })) : (_jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M21 12.79A9 9 0 1 1 11.21 3a7 7 0 1 0 9.79 9.79z" }) })) })) : null;
@@ -25,8 +24,13 @@ export function TitleBarLayout({ onToggleSidebar, sidebarOpen, onSearchToggle, s
25
24
  const searchBtn = (_jsx(Button, { type: "button", variant: "ghost", size: "sm", "aria-label": "Search", onClick: onSearchToggle, className: iconBtn, children: _jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M21 21l-4.35-4.35M17 11A6 6 0 1 1 5 11a6 6 0 0 1 12 0z" }) }) }));
26
25
  const wishlistEl = wishlistHref ? (_jsxs(Link, { href: wishlistHref, "aria-label": `Wishlist${wishlistCount > 0 ? `, ${wishlistCount} items` : ""}`, className: `relative ${iconBtn}`, children: [_jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4.318 6.318a4.5 4.5 0 0 0 0 6.364L12 20.364l7.682-7.682a4.5 4.5 0 0 0-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 0 0-6.364 0z" }) }), wishlistCount > 0 && (_jsx(Span, { className: countBadge, children: wishlistCount > 9 ? "9+" : wishlistCount }))] })) : null;
27
26
  const cartEl = cartHref ? (_jsxs(Link, { href: cartHref, "aria-label": `Cart${cartCount > 0 ? `, ${cartCount} items` : ""}`, className: `relative ${iconBtn}`, children: [_jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M2.25 3h1.386c.51 0 .955.343 1.087.835l.383 1.437M7.5 14.25a3 3 0 0 0-3 3h15.75m-12.75-3h11.218c1.121-2.3 2.1-4.684 2.924-7.138a60.114 60.114 0 0 0-16.536-1.84M7.5 14.25 5.106 5.272M6 20.25a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0zm12.75 0a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0z" }) }), cartCount > 0 && (_jsx(Span, { className: countBadge, children: cartCount > 9 ? "9+" : cartCount }))] })) : null;
28
- const profileEl = profileHref ? (_jsx(Link, { href: profileHref, "aria-label": user ? `Profile — ${user.displayName ?? user.email}` : "Sign in", className: iconBtn, children: user?.photoURL ? (_jsx(Image, { src: user.photoURL, alt: user.displayName ?? "Profile", width: 28, height: 28, className: "w-7 h-7 rounded-full object-cover" })) : (_jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M15.75 6a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0zM4.501 20.118a7.5 7.5 0 0 1 14.998 0A17.933 17.933 0 0 1 12 21.75c-2.676 0-5.216-.584-7.499-1.632z" }) })) })) : null;
27
+ const profileEl = profileHref ? (_jsx(Link, { href: profileHref, "aria-label": user ? `Profile — ${user.displayName ?? user.email}` : "Sign in", className: iconBtn, children: user?.photoURL ? (
28
+ // eslint-disable-next-line @next/next/no-img-element
29
+ _jsx("img", { src: user.photoURL, alt: user.displayName ?? "Profile", width: 28, height: 28, className: "w-7 h-7 rounded-full object-cover" })) : (_jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M15.75 6a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0zM4.501 20.118a7.5 7.5 0 0 1 14.998 0A17.933 17.933 0 0 1 12 21.75c-2.676 0-5.216-.584-7.499-1.632z" }) })) })) : null;
30
+ const authButtonsEl = !user && (loginHref || registerHref) ? (_jsxs(Row, { gap: "xs", className: "hidden lg:flex items-center", children: [loginHref && (_jsx(Link, { href: loginHref, className: "px-3 py-1.5 text-sm font-medium text-zinc-700 dark:text-zinc-300 hover:text-primary-700 dark:hover:text-secondary-400 transition-colors rounded-lg hover:bg-primary-50 dark:hover:bg-slate-800", children: "Sign in" })), registerHref && (_jsx(Link, { href: registerHref, className: "px-3 py-1.5 text-sm font-semibold rounded-lg bg-primary text-white hover:bg-primary/90 transition-colors btn-glow shadow-sm", children: "Register" }))] })) : null;
29
31
  const hasTb2 = !!(wishlistEl || cartEl || profileEl);
30
32
  // ── Render ───────────────────────────────────────────────────────────────────
31
- return (_jsxs(BlockHeader, { id: id, className: `sticky top-0 z-50 bg-white/95 dark:bg-slate-950/95 backdrop-blur-md border-b border-zinc-100 dark:border-slate-800 shadow-sm ${className}`, children: [promoStripText && (_jsx(Div, { className: "bg-gradient-to-r from-primary-700 to-secondary-600 dark:from-primary-700 dark:to-cobalt-700 text-white text-xs py-1 text-center font-medium", children: promoStripText })), _jsxs(Div, { className: "container mx-auto px-4 sm:px-6 lg:px-8 max-w-[1920px]", children: [_jsxs(Row, { justify: "between", gap: "none", className: "h-14", children: [_jsx(Row, { gap: "3", children: _jsx(Link, { href: logoHref, "aria-label": brandName, className: "flex items-center transition-opacity hover:opacity-80", children: _jsx(SiteLogo, { title: brandName, src: siteLogoUrl, className: "h-7 md:h-9 lg:h-10" }) }) }), navSlot && _jsx(Div, { className: "hidden md:flex", children: navSlot }), _jsxs(Row, { gap: "xs", children: [devSlot, compareEl, notificationSlot, wishlistEl && _jsx(Div, { className: "hidden lg:flex", children: wishlistEl }), cartEl && _jsx(Div, { className: "hidden lg:flex", children: cartEl }), profileEl && _jsx(Div, { className: "hidden lg:flex", children: profileEl }), searchBtn, promotionsEl, themeBtn, hamburgerBtn] })] }), hasTb2 && (_jsxs(Row, { as: "nav", "aria-label": "Account actions", justify: "end", gap: "xs", className: "flex lg:hidden h-10 border-t border-zinc-100 dark:border-slate-800 px-1", children: [wishlistEl, cartEl, profileEl] }))] })] }));
33
+ return (_jsxs(BlockHeader, { id: id, className: `sticky top-0 z-50 bg-white/95 dark:bg-slate-950/95 backdrop-blur-md border-b border-zinc-100 dark:border-slate-800 shadow-sm ${className}`, children: [promoStripText && (_jsx(Div, { className: "bg-gradient-to-r from-primary-700 to-secondary-600 dark:from-primary-700 dark:to-cobalt-700 text-white text-xs py-1 text-center font-medium", children: promoStripText })), _jsxs(Div, { className: "container mx-auto px-4 sm:px-6 lg:px-8 max-w-[1920px]", children: [_jsxs(Row, { justify: "between", gap: "none", className: "h-14", children: [_jsx(Row, { gap: "3", children: _jsx(Link, { href: logoHref, "aria-label": brandName, className: "flex items-center transition-opacity hover:opacity-80", children: _jsx(SiteLogo, { title: brandName, src: siteLogoUrl, className: "h-7 md:h-9 lg:h-10" }) }) }), navSlot && _jsx(Div, { className: "hidden md:flex", children: navSlot }), _jsxs(Row, { gap: "xs", children: [devSlot, compareEl, notificationSlot, wishlistEl && _jsx(Div, { className: "hidden lg:flex", children: wishlistEl }), cartEl && _jsx(Div, { className: "hidden lg:flex", children: cartEl }), user
34
+ ? profileEl && _jsx(Div, { className: "hidden lg:flex", children: profileEl })
35
+ : authButtonsEl ?? (profileEl && _jsx(Div, { className: "hidden lg:flex", children: profileEl })), searchBtn, promotionsEl, themeBtn, hamburgerBtn] })] }), hasTb2 && (_jsxs(Row, { as: "nav", "aria-label": "Account actions", justify: "end", gap: "xs", className: "flex lg:hidden h-10 border-t border-zinc-100 dark:border-slate-800 px-1", children: [wishlistEl, cartEl, profileEl] }))] })] }));
32
36
  }
package/dist/index.d.ts CHANGED
@@ -1219,6 +1219,10 @@ export type { AdminTeamViewProps } from "./features/admin/index";
1219
1219
  export type { AdminEmployeeEditorViewProps } from "./features/admin/index";
1220
1220
  export type { AdminSupportTicketsViewProps } from "./features/admin/index";
1221
1221
  export type { AdminSupportTicketDetailViewProps } from "./features/admin/index";
1222
+ export { AdminScammersView } from "./features/admin/index";
1223
+ export { AdminScammerEditorView } from "./features/admin/index";
1224
+ export type { AdminScammersViewProps } from "./features/admin/index";
1225
+ export type { AdminScammerEditorViewProps } from "./features/admin/index";
1222
1226
  export type { DrawerFormFooterProps } from "./features/admin/index";
1223
1227
  export type { FeatureFlagKey } from "./features/admin/index";
1224
1228
  export type { FeatureFlagMeta } from "./features/admin/index";
package/dist/index.js CHANGED
@@ -2311,6 +2311,10 @@ export { toStringValue } from "./features/admin/index";
2311
2311
  // [CLIENT-SSR]-Utility function for converting date values to relative date string.
2312
2312
  // toRelativeDate - Helper for converting date to relative date string.
2313
2313
  export { toRelativeDate } from "./features/admin/index";
2314
+ // AdminScammersView - Admin scammer profile list view.
2315
+ export { AdminScammersView } from "./features/admin/index";
2316
+ // AdminScammerEditorView - Scammer profile review/status SideDrawer.
2317
+ export { AdminScammerEditorView } from "./features/admin/index";
2314
2318
  // ./features/admin/server
2315
2319
  // [SERVER-ONLY]-Server-only — uses Node.js, Next.js server internals, or third-party server SDKs (auth, email, payment, shipping).
2316
2320
  // adminBidsGET - Shared export for admin bids get.
package/dist/jobs.d.ts CHANGED
@@ -20,5 +20,5 @@
20
20
  * listingProcessorHandler,
21
21
  * } from "@mohasinac/appkit/jobs";
22
22
  */
23
- export { couponExpiryHandler, offerExpiryHandler, cartPruneHandler, notificationPruneHandler, dailyDataCleanupHandler, cleanupRtdbEventsHandler, auctionSettlementHandler, autoPayoutEligibilityHandler, countersReconcileHandler, onOrderCreateHandler, onOrderStatusChangeHandler, onBidPlacedHandler, onReviewWriteHandler, promotionsHandler, mediaTmpCleanupHandler, pendingOrderTimeoutHandler, productStatsSyncHandler, positionsReconcileHandler, payoutBatchHandler, weeklyPayoutEligibilityHandler, onCategoryWriteHandler, onProductWriteHandler, onStoreWriteHandler, adminAnalyticsHandler, storeAnalyticsHandler, listingProcessorHandler, supportedListingCollections, prizeRevealOpenHandler, prizeRevealCloseHandler, prizeRevealExpiryHandler, prizeRevealReminderHandler, bundleStockSyncHandler, onProductStockChangeHandler, triggerEventRaffleHandler, assignSpinPrizeHandler, bindSchedule, bindDocumentWritten, bindDocumentCreated, bindDocumentUpdated, bindCallable, bindHttps, bindToFirebase, } from "./_internal/server/jobs/index.js";
23
+ export { couponExpiryHandler, offerExpiryHandler, cartPruneHandler, notificationPruneHandler, dailyDataCleanupHandler, cleanupRtdbEventsHandler, auctionSettlementHandler, autoPayoutEligibilityHandler, countersReconcileHandler, onOrderCreateHandler, onOrderStatusChangeHandler, onBidPlacedHandler, onReviewWriteHandler, promotionsHandler, mediaTmpCleanupHandler, pendingOrderTimeoutHandler, productStatsSyncHandler, positionsReconcileHandler, payoutBatchHandler, weeklyPayoutEligibilityHandler, onCategoryWriteHandler, onProductWriteHandler, onStoreWriteHandler, adminAnalyticsHandler, storeAnalyticsHandler, listingProcessorHandler, supportedListingCollections, prizeRevealOpenHandler, prizeRevealCloseHandler, prizeRevealExpiryHandler, prizeRevealReminderHandler, bundleStockSyncHandler, onProductStockChangeHandler, triggerEventRaffleHandler, assignSpinPrizeHandler, onSupportTicketCreateHandler, onSupportTicketUpdateHandler, onUserBanChangeHandler, bindSchedule, bindDocumentWritten, bindDocumentCreated, bindDocumentUpdated, bindCallable, bindHttps, bindToFirebase, } from "./_internal/server/jobs/index.js";
24
24
  export type { PromotionsCallableResult, AdminAnalyticsResult, StoreAnalyticsInput, StoreAnalyticsResult, ListingRequestBody, ListingResponseBody, JobContext, JobLogger, JobHandlers, ScheduleHandler, FirestoreTriggerHandler, FirestoreTriggerEvent, CallableHandler, BindHttpsOptions, } from "./_internal/server/jobs/index.js";
package/dist/jobs.js CHANGED
@@ -27,5 +27,7 @@ couponExpiryHandler, offerExpiryHandler, cartPruneHandler, notificationPruneHand
27
27
  prizeRevealOpenHandler, prizeRevealCloseHandler, prizeRevealExpiryHandler, prizeRevealReminderHandler, bundleStockSyncHandler,
28
28
  // SB-UNI-V — Firestore onWrite trigger for product stock changes.
29
29
  onProductStockChangeHandler, triggerEventRaffleHandler, assignSpinPrizeHandler,
30
+ // BAN9 — support ticket lifecycle + ban audit trail
31
+ onSupportTicketCreateHandler, onSupportTicketUpdateHandler, onUserBanChangeHandler,
30
32
  // Firebase binder adapter
31
33
  bindSchedule, bindDocumentWritten, bindDocumentCreated, bindDocumentUpdated, bindCallable, bindHttps, bindToFirebase, } from "./_internal/server/jobs/index.js";
@@ -81,6 +81,7 @@ export declare const DEFAULT_ROUTE_MAP: {
81
81
  readonly SCAM_DETAIL: (id: string) => string;
82
82
  readonly SCAM_REPORT: "/scams/report";
83
83
  readonly SCAM_TYPES: "/scams/types";
84
+ readonly SCAM_FAQS: "/scams/faqs";
84
85
  };
85
86
  readonly ERRORS: {
86
87
  readonly UNAUTHORIZED: "/unauthorized";
@@ -117,6 +118,8 @@ export declare const DEFAULT_ROUTE_MAP: {
117
118
  readonly REVIEWS: "/user/reviews";
118
119
  readonly BIDS: "/user/bids";
119
120
  readonly RETURNS: "/user/returns";
121
+ readonly SUPPORT: "/user/support";
122
+ readonly SUPPORT_TICKET: (id: string) => string;
120
123
  };
121
124
  readonly STORE: {
122
125
  readonly DASHBOARD: "/store";
@@ -231,6 +234,10 @@ export declare const DEFAULT_ROUTE_MAP: {
231
234
  readonly PRIZE_DRAWS: "/admin/prize-draws";
232
235
  readonly PRIZE_DRAWS_EDIT: (id: string) => string;
233
236
  readonly TEAM: "/admin/team";
237
+ readonly SUPPORT_TICKETS: "/admin/support-tickets";
238
+ readonly SUPPORT_TICKET_BY_ID: (id: string) => string;
239
+ readonly SCAMMERS: "/admin/scammers";
240
+ readonly SCAMMER_BY_ID: (id: string) => string;
234
241
  };
235
242
  readonly DEMO: {
236
243
  readonly SEED: "/demo/seed";
@@ -311,6 +318,7 @@ export declare const ROUTES: {
311
318
  readonly SCAM_DETAIL: (id: string) => string;
312
319
  readonly SCAM_REPORT: "/scams/report";
313
320
  readonly SCAM_TYPES: "/scams/types";
321
+ readonly SCAM_FAQS: "/scams/faqs";
314
322
  };
315
323
  readonly ERRORS: {
316
324
  readonly UNAUTHORIZED: "/unauthorized";
@@ -347,6 +355,8 @@ export declare const ROUTES: {
347
355
  readonly REVIEWS: "/user/reviews";
348
356
  readonly BIDS: "/user/bids";
349
357
  readonly RETURNS: "/user/returns";
358
+ readonly SUPPORT: "/user/support";
359
+ readonly SUPPORT_TICKET: (id: string) => string;
350
360
  };
351
361
  readonly STORE: {
352
362
  readonly DASHBOARD: "/store";
@@ -461,6 +471,10 @@ export declare const ROUTES: {
461
471
  readonly PRIZE_DRAWS: "/admin/prize-draws";
462
472
  readonly PRIZE_DRAWS_EDIT: (id: string) => string;
463
473
  readonly TEAM: "/admin/team";
474
+ readonly SUPPORT_TICKETS: "/admin/support-tickets";
475
+ readonly SUPPORT_TICKET_BY_ID: (id: string) => string;
476
+ readonly SCAMMERS: "/admin/scammers";
477
+ readonly SCAMMER_BY_ID: (id: string) => string;
464
478
  };
465
479
  readonly DEMO: {
466
480
  readonly SEED: "/demo/seed";
@@ -69,6 +69,7 @@ export const DEFAULT_ROUTE_MAP = {
69
69
  SCAM_DETAIL: (id) => `/scams/${id}`,
70
70
  SCAM_REPORT: "/scams/report",
71
71
  SCAM_TYPES: "/scams/types",
72
+ SCAM_FAQS: "/scams/faqs",
72
73
  },
73
74
  ERRORS: {
74
75
  UNAUTHORIZED: "/unauthorized",
@@ -105,6 +106,8 @@ export const DEFAULT_ROUTE_MAP = {
105
106
  REVIEWS: "/user/reviews",
106
107
  BIDS: "/user/bids",
107
108
  RETURNS: "/user/returns",
109
+ SUPPORT: "/user/support",
110
+ SUPPORT_TICKET: (id) => `/user/support/${id}`,
108
111
  },
109
112
  STORE: {
110
113
  DASHBOARD: "/store",
@@ -222,6 +225,10 @@ export const DEFAULT_ROUTE_MAP = {
222
225
  PRIZE_DRAWS: "/admin/prize-draws",
223
226
  PRIZE_DRAWS_EDIT: (id) => `/admin/prize-draws/${id}/edit`,
224
227
  TEAM: "/admin/team",
228
+ SUPPORT_TICKETS: "/admin/support-tickets",
229
+ SUPPORT_TICKET_BY_ID: (id) => `/admin/support-tickets/${id}`,
230
+ SCAMMERS: "/admin/scammers",
231
+ SCAMMER_BY_ID: (id) => `/admin/scammers/${id}`,
225
232
  },
226
233
  DEMO: {
227
234
  SEED: "/demo/seed",