@mohasinac/appkit 2.6.5 → 2.6.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/_internal/client/features/layout/DashboardLayoutClient.d.ts +8 -1
  2. package/dist/_internal/client/features/layout/DashboardLayoutClient.js +18 -2
  3. package/dist/_internal/server/features/auth/capabilities.d.ts +17 -0
  4. package/dist/_internal/server/features/auth/capabilities.js +31 -0
  5. package/dist/_internal/server/features/auth/index.d.ts +2 -0
  6. package/dist/_internal/server/features/auth/index.js +2 -0
  7. package/dist/_internal/server/features/auth/permissions.d.ts +49 -0
  8. package/dist/_internal/server/features/auth/permissions.js +76 -0
  9. package/dist/configs/next.d.ts +6 -1
  10. package/dist/configs/next.js +45 -18
  11. package/dist/constants/api-endpoints.d.ts +69 -0
  12. package/dist/constants/api-endpoints.js +34 -0
  13. package/dist/contracts/registry.d.ts +7 -0
  14. package/dist/features/admin/components/AdminEmployeeEditorView.d.ts +10 -0
  15. package/dist/features/admin/components/AdminEmployeeEditorView.js +168 -0
  16. package/dist/features/admin/components/AdminSidebar.d.ts +2 -0
  17. package/dist/features/admin/components/AdminStoreEditorView.d.ts +2 -1
  18. package/dist/features/admin/components/AdminStoreEditorView.js +55 -3
  19. package/dist/features/admin/components/AdminStoresView.js +3 -1
  20. package/dist/features/admin/components/AdminSupportTicketDetailView.d.ts +23 -0
  21. package/dist/features/admin/components/AdminSupportTicketDetailView.js +83 -0
  22. package/dist/features/admin/components/AdminSupportTicketsView.d.ts +4 -0
  23. package/dist/features/admin/components/AdminSupportTicketsView.js +151 -0
  24. package/dist/features/admin/components/AdminTeamView.d.ts +4 -0
  25. package/dist/features/admin/components/AdminTeamView.js +139 -0
  26. package/dist/features/admin/components/AdminUserEditorView.d.ts +14 -1
  27. package/dist/features/admin/components/AdminUserEditorView.js +116 -14
  28. package/dist/features/admin/components/AdminUsersView.js +39 -12
  29. package/dist/features/admin/components/index.d.ts +8 -0
  30. package/dist/features/admin/components/index.js +4 -0
  31. package/dist/features/admin/constants/filter-tabs.d.ts +37 -0
  32. package/dist/features/admin/constants/filter-tabs.js +17 -0
  33. package/dist/features/auth/server/checkSoftBan.d.ts +15 -0
  34. package/dist/features/auth/server/checkSoftBan.js +36 -0
  35. package/dist/features/auth/server.d.ts +1 -0
  36. package/dist/features/auth/server.js +1 -0
  37. package/dist/features/media/types/index.js +2 -1
  38. package/dist/features/stores/repository/store.repository.d.ts +5 -1
  39. package/dist/features/stores/repository/store.repository.js +27 -2
  40. package/dist/features/support/repository/support.repository.d.ts +18 -0
  41. package/dist/features/support/repository/support.repository.js +117 -0
  42. package/dist/index.d.ts +12 -0
  43. package/dist/index.js +12 -0
  44. package/dist/next/api/routeHandler.d.ts +8 -0
  45. package/dist/next/api/routeHandler.js +24 -3
  46. package/dist/next/routing/route-map.d.ts +2 -0
  47. package/dist/next/routing/route-map.js +1 -0
  48. package/dist/repositories/index.d.ts +2 -0
  49. package/dist/repositories/index.js +1 -0
  50. package/dist/security/pii-schemas.d.ts +2 -0
  51. package/dist/security/pii-schemas.js +2 -0
  52. package/dist/seed/stores-seed-data.js +8 -0
  53. package/dist/server.d.ts +6 -0
  54. package/dist/server.js +6 -0
  55. package/dist/tailwind-utilities.css +1 -1
  56. package/dist/utils/media-url.d.ts +9 -0
  57. package/dist/utils/media-url.js +29 -0
  58. package/package.json +1 -1
@@ -25,6 +25,13 @@ export interface DashboardLayoutClientProps {
25
25
  variant: DashboardVariant;
26
26
  /** Grouped nav items. Shape matches all three sidebar components. */
27
27
  groups: SidebarNavGroup[];
28
+ /**
29
+ * Resolved permissions for the current user (serialised from RSC layout).
30
+ * When provided, nav items with `requiredPermission` are filtered server-side
31
+ * before reaching this component; pass `null` to skip (admin sees everything).
32
+ * When absent, all items are shown (backwards-compatible).
33
+ */
34
+ permissions?: string[] | null;
28
35
  /** Override active-link highlight. Defaults to usePathname(). */
29
36
  activeHref?: string;
30
37
  /** Responsive controls — currently only hideAt is honoured. */
@@ -35,4 +42,4 @@ export interface DashboardLayoutClientProps {
35
42
  className?: string;
36
43
  children: ReactNode;
37
44
  }
38
- export declare function DashboardLayoutClient({ variant, groups, activeHref: explicitActiveHref, responsive: _responsive, className, children, }: DashboardLayoutClientProps): import("react/jsx-runtime").JSX.Element;
45
+ export declare function DashboardLayoutClient({ variant, groups, permissions, activeHref: explicitActiveHref, responsive: _responsive, className, children, }: DashboardLayoutClientProps): import("react/jsx-runtime").JSX.Element;
@@ -67,9 +67,25 @@ function useResponsiveDrawer() {
67
67
  }, [registerNav, unregisterNav, open, close, toggle]);
68
68
  return { desktopOpen, mobileOpen, close, toggle };
69
69
  }
70
- export function DashboardLayoutClient({ variant, groups, activeHref: explicitActiveHref, responsive: _responsive, className, children, }) {
70
+ /** Filter admin nav groups to only show items the user has permission to see. */
71
+ function filterAdminGroups(groups, permissions) {
72
+ // null = admin (show everything); undefined = no filtering (backwards compat)
73
+ if (permissions === null || permissions === undefined)
74
+ return groups;
75
+ return groups
76
+ .map((group) => ({
77
+ ...group,
78
+ items: group.items.filter((item) => !item.requiredPermission ||
79
+ permissions.includes(item.requiredPermission)),
80
+ }))
81
+ .filter((group) => group.items.length > 0);
82
+ }
83
+ export function DashboardLayoutClient({ variant, groups, permissions, activeHref: explicitActiveHref, responsive: _responsive, className, children, }) {
71
84
  const pathname = usePathname();
72
85
  const activeHref = explicitActiveHref ?? pathname ?? "";
73
86
  const { desktopOpen, mobileOpen, close, toggle } = useResponsiveDrawer();
74
- return (_jsxs(_Fragment, { children: [variant === "admin" && (_jsx(AdminSidebar, { variant: "sidebar", desktopOpen: desktopOpen, mobileOpen: mobileOpen, activePath: activeHref, groups: groups, onCloseMobile: close, onToggle: toggle, className: className })), variant === "store" && (_jsx(StoreSidebar, { variant: "sidebar", desktopOpen: desktopOpen, mobileOpen: mobileOpen, activeHref: activeHref, items: [], groups: groups, onCloseMobile: close, onToggle: toggle, className: className })), variant === "user" && (_jsx(UserSidebar, { variant: "sidebar", desktopOpen: desktopOpen, mobileOpen: mobileOpen, items: groups.flatMap((g) => g.items), groups: groups, onCloseMobile: close, onToggle: toggle, className: className })), children] }));
87
+ const adminGroups = variant === "admin"
88
+ ? filterAdminGroups(groups, permissions)
89
+ : groups;
90
+ return (_jsxs(_Fragment, { children: [variant === "admin" && (_jsx(AdminSidebar, { variant: "sidebar", desktopOpen: desktopOpen, mobileOpen: mobileOpen, activePath: activeHref, groups: adminGroups, onCloseMobile: close, onToggle: toggle, className: className })), variant === "store" && (_jsx(StoreSidebar, { variant: "sidebar", desktopOpen: desktopOpen, mobileOpen: mobileOpen, activeHref: activeHref, items: [], groups: groups, onCloseMobile: close, onToggle: toggle, className: className })), variant === "user" && (_jsx(UserSidebar, { variant: "sidebar", desktopOpen: desktopOpen, mobileOpen: mobileOpen, items: groups.flatMap((g) => g.items), groups: groups, onCloseMobile: close, onToggle: toggle, className: className })), children] }));
75
91
  }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Store capabilities server helper.
3
+ *
4
+ * Reads the capabilities[] array from the store document and merges with defaults.
5
+ * Call this server-side only — never trust client-provided capability arrays.
6
+ */
7
+ import { type StoreCapability } from "../../../../features/auth/permissions/constants";
8
+ /**
9
+ * Return the resolved capability set for a store.
10
+ * Falls back to DEFAULT_STORE_CAPABILITIES when the store has no capabilities field.
11
+ */
12
+ export declare function getStoreCapabilities(storeId: string): Promise<StoreCapability[]>;
13
+ /**
14
+ * Check whether a store has a specific capability.
15
+ * Convenience wrapper — prefer calling `getStoreCapabilities` once when checking multiple.
16
+ */
17
+ export declare function storeHasCapability(storeId: string, required: StoreCapability): Promise<boolean>;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Store capabilities server helper.
3
+ *
4
+ * Reads the capabilities[] array from the store document and merges with defaults.
5
+ * Call this server-side only — never trust client-provided capability arrays.
6
+ */
7
+ import { storeRepository } from "../../../../repositories";
8
+ import { DEFAULT_STORE_CAPABILITIES, } from "../../../../features/auth/permissions/constants";
9
+ /**
10
+ * Return the resolved capability set for a store.
11
+ * Falls back to DEFAULT_STORE_CAPABILITIES when the store has no capabilities field.
12
+ */
13
+ export async function getStoreCapabilities(storeId) {
14
+ if (!storeId)
15
+ return [...DEFAULT_STORE_CAPABILITIES];
16
+ const store = await storeRepository.findById(storeId);
17
+ if (!store)
18
+ return [...DEFAULT_STORE_CAPABILITIES];
19
+ const raw = store.capabilities;
20
+ if (Array.isArray(raw) && raw.length > 0)
21
+ return raw;
22
+ return [...DEFAULT_STORE_CAPABILITIES];
23
+ }
24
+ /**
25
+ * Check whether a store has a specific capability.
26
+ * Convenience wrapper — prefer calling `getStoreCapabilities` once when checking multiple.
27
+ */
28
+ export async function storeHasCapability(storeId, required) {
29
+ const caps = await getStoreCapabilities(storeId);
30
+ return caps.includes(required);
31
+ }
@@ -1 +1,3 @@
1
1
  export * from "./actions";
2
+ export * from "./permissions";
3
+ export * from "./capabilities";
@@ -1 +1,3 @@
1
1
  export * from "./actions";
2
+ export * from "./permissions";
3
+ export * from "./capabilities";
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Server-side RBAC permission resolver.
3
+ *
4
+ * Logic lives here; consumers inject config via opts parameters.
5
+ *
6
+ * Design:
7
+ * - admin role bypasses all permission checks (isAdmin: true)
8
+ * - employee role is gated by permissions[] on UserDocument
9
+ * - all other roles get empty permissions (no admin access)
10
+ */
11
+ import type { Permission } from "../../../../features/auth/permissions/constants";
12
+ export interface ResolvedPermissions {
13
+ /** true when role === "admin" — bypasses all permission checks */
14
+ isAdmin: boolean;
15
+ /** explicit permissions array; only meaningful when role === "employee" */
16
+ permissions: Permission[];
17
+ }
18
+ /**
19
+ * Fetch and resolve permissions for a user.
20
+ * Returns { isAdmin: true } for admins, explicit permissions for employees,
21
+ * empty for all other roles.
22
+ */
23
+ export declare function getServerPermissions(uid: string): Promise<ResolvedPermissions>;
24
+ export declare function checkPermission(resolved: ResolvedPermissions, required: Permission): boolean;
25
+ export declare function checkAnyPermission(resolved: ResolvedPermissions, required: Permission[]): boolean;
26
+ type GetUser = () => Promise<{
27
+ uid: string;
28
+ role: string;
29
+ } | null>;
30
+ export interface AdminSectionLayoutOpts {
31
+ getUser: GetUser;
32
+ loginPath?: string;
33
+ unauthorizedPath?: string;
34
+ }
35
+ /**
36
+ * Factory that returns a Next.js RSC layout component guarding an admin section.
37
+ *
38
+ * Usage in consumer:
39
+ * ```ts
40
+ * // src/app/[locale]/admin/blog/layout.tsx
41
+ * import { makeAdminSectionLayout } from "@mohasinac/appkit/server";
42
+ * import { getServerSessionUser } from "@/lib/firebase/auth-server";
43
+ * export default makeAdminSectionLayout("admin:blog:read", { getUser: getServerSessionUser });
44
+ * ```
45
+ */
46
+ export declare function makeAdminSectionLayout(permission: Permission, opts: AdminSectionLayoutOpts): ({ children, }: {
47
+ children: React.ReactNode;
48
+ }) => Promise<React.ReactNode>;
49
+ export {};
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Server-side RBAC permission resolver.
3
+ *
4
+ * Logic lives here; consumers inject config via opts parameters.
5
+ *
6
+ * Design:
7
+ * - admin role bypasses all permission checks (isAdmin: true)
8
+ * - employee role is gated by permissions[] on UserDocument
9
+ * - all other roles get empty permissions (no admin access)
10
+ */
11
+ import { userRepository } from "../../../../repositories";
12
+ // ── Core resolver ─────────────────────────────────────────────────────────────
13
+ /**
14
+ * Fetch and resolve permissions for a user.
15
+ * Returns { isAdmin: true } for admins, explicit permissions for employees,
16
+ * empty for all other roles.
17
+ */
18
+ export async function getServerPermissions(uid) {
19
+ if (!uid)
20
+ return { isAdmin: false, permissions: [] };
21
+ // userRepository.findById is cached per request in BaseRepository
22
+ const user = await userRepository.findById(uid);
23
+ if (!user)
24
+ return { isAdmin: false, permissions: [] };
25
+ if (user.role === "admin")
26
+ return { isAdmin: true, permissions: [] };
27
+ if (user.role === "employee") {
28
+ return {
29
+ isAdmin: false,
30
+ permissions: (user.permissions ?? []),
31
+ };
32
+ }
33
+ return { isAdmin: false, permissions: [] };
34
+ }
35
+ // ── Permission check helpers ──────────────────────────────────────────────────
36
+ export function checkPermission(resolved, required) {
37
+ return resolved.isAdmin || resolved.permissions.includes(required);
38
+ }
39
+ export function checkAnyPermission(resolved, required) {
40
+ return resolved.isAdmin || required.some((p) => resolved.permissions.includes(p));
41
+ }
42
+ /**
43
+ * Factory that returns a Next.js RSC layout component guarding an admin section.
44
+ *
45
+ * Usage in consumer:
46
+ * ```ts
47
+ * // src/app/[locale]/admin/blog/layout.tsx
48
+ * import { makeAdminSectionLayout } from "@mohasinac/appkit/server";
49
+ * import { getServerSessionUser } from "@/lib/firebase/auth-server";
50
+ * export default makeAdminSectionLayout("admin:blog:read", { getUser: getServerSessionUser });
51
+ * ```
52
+ */
53
+ export function makeAdminSectionLayout(permission, opts) {
54
+ return async function AdminSectionLayout({ children, }) {
55
+ const { redirect } = await import("next/navigation");
56
+ const user = await opts.getUser();
57
+ if (!user) {
58
+ redirect(opts.loginPath ?? "/auth/login");
59
+ return null;
60
+ }
61
+ // admin role passes without a permission check
62
+ if (user.role === "admin")
63
+ return children;
64
+ // non-employee non-admin roles have no business in /admin
65
+ if (user.role !== "employee") {
66
+ redirect(opts.unauthorizedPath ?? "/unauthorized");
67
+ return null;
68
+ }
69
+ const resolved = await getServerPermissions(user.uid);
70
+ if (!checkPermission(resolved, permission)) {
71
+ redirect(opts.unauthorizedPath ?? "/unauthorized");
72
+ return null;
73
+ }
74
+ return children;
75
+ };
76
+ }
@@ -6,7 +6,12 @@
6
6
  *
7
7
  * Notable defaults:
8
8
  * - `serverExternalPackages` includes firebase-admin + GCP deps
9
- * - `outputFileTracingIncludes` forces firebase-admin/lib/database/** into Lambda bundles
9
+ * - `outputFileTracingIncludes` uses broad org-level globs (**) so every
10
+ * current and future @google-cloud/* subpackage (incl. build/protos/**) is
11
+ * included in Vercel Lambda bundles without needing per-package entries.
12
+ * - Consumer additions to the same route key are MERGED (union), not replaced.
13
+ * - `images.remotePatterns` restricts to Firebase Storage + localhost; consumer
14
+ * can append additional patterns via `images.remotePatterns`.
10
15
  * - `experimental.serverActions.bodySizeLimit` set to "4mb"
11
16
  * - Optional `IgnorePlugin` for optional native deps (request, fast-crc32c)
12
17
  *
@@ -6,7 +6,12 @@
6
6
  *
7
7
  * Notable defaults:
8
8
  * - `serverExternalPackages` includes firebase-admin + GCP deps
9
- * - `outputFileTracingIncludes` forces firebase-admin/lib/database/** into Lambda bundles
9
+ * - `outputFileTracingIncludes` uses broad org-level globs (**) so every
10
+ * current and future @google-cloud/* subpackage (incl. build/protos/**) is
11
+ * included in Vercel Lambda bundles without needing per-package entries.
12
+ * - Consumer additions to the same route key are MERGED (union), not replaced.
13
+ * - `images.remotePatterns` restricts to Firebase Storage + localhost; consumer
14
+ * can append additional patterns via `images.remotePatterns`.
10
15
  * - `experimental.serverActions.bodySizeLimit` set to "4mb"
11
16
  * - Optional `IgnorePlugin` for optional native deps (request, fast-crc32c)
12
17
  *
@@ -72,25 +77,34 @@ export function defineNextConfig(override = {}) {
72
77
  ...consumerExperimental,
73
78
  };
74
79
  // Next 16 moved `outputFileTracingIncludes` out of `experimental` to the
75
- // top-level config. Keep the same firebase-admin/lib/database forcing the
76
- // old position used, and merge consumer overrides if provided.
80
+ // top-level config. Use broad org-level globs (**) so protos/, build/cjs/,
81
+ // build/esm/, and any future subpackages are always included — no more
82
+ // per-subdirectory entries that silently miss e.g. build/protos/admin_v1.json.
83
+ // Consumer additions for the same route key are MERGED (array union), not replaced.
77
84
  const defaultOutputFileTracingIncludes = {
78
85
  "/api/**": [
79
- "./node_modules/firebase-admin/lib/database/**",
80
- "./node_modules/firebase-admin/lib/esm/database/**",
81
- "./node_modules/firebase-admin/lib/firestore/**",
82
- "./node_modules/firebase-admin/lib/esm/firestore/**",
83
- "./node_modules/firebase-admin/lib/auth/**",
84
- "./node_modules/firebase-admin/lib/esm/auth/**",
85
- "./node_modules/firebase-admin/lib/app/**",
86
- "./node_modules/firebase-admin/lib/esm/app/**",
87
- "./node_modules/@google-cloud/firestore/build/src/**",
86
+ // Firebase Admin — entire package (lib/ + esm/, all sub-SDKs)
87
+ "./node_modules/firebase-admin/**",
88
+ // All @google-cloud packages: firestore (incl. protos/**), storage, paginator, etc.
89
+ "./node_modules/@google-cloud/**",
90
+ // Auth libraries used by Firebase Admin token verification
91
+ "./node_modules/google-auth-library/**",
92
+ "./node_modules/google-gax/**",
93
+ "./node_modules/gtoken/**",
94
+ "./node_modules/jws/**",
95
+ "./node_modules/gaxios/**",
88
96
  ],
89
97
  };
90
98
  const mergedOutputFileTracingIncludes = {
91
99
  ...defaultOutputFileTracingIncludes,
92
- ...consumerOutputFileTracingIncludes,
93
100
  };
101
+ for (const [route, patterns] of Object.entries(consumerOutputFileTracingIncludes)) {
102
+ const existing = mergedOutputFileTracingIncludes[route] ?? [];
103
+ mergedOutputFileTracingIncludes[route] = [
104
+ ...existing,
105
+ ...patterns.filter((p) => !existing.includes(p)),
106
+ ];
107
+ }
94
108
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
95
109
  function mergedWebpack(config, ctx) {
96
110
  const { isServer, webpack } = ctx;
@@ -112,13 +126,26 @@ export function defineNextConfig(override = {}) {
112
126
  }
113
127
  return consumerWebpack ? consumerWebpack(config, ctx) : config;
114
128
  }
129
+ // Default remotePatterns: Firebase Storage + localhost only.
130
+ // External URLs are watermarked via /api/media/ext — they must never be
131
+ // loaded directly by next/image. Consumer can append additional patterns.
132
+ const defaultRemotePatterns = [
133
+ { protocol: "https", hostname: "firebasestorage.googleapis.com" },
134
+ { protocol: "https", hostname: "*.firebasestorage.googleapis.com" },
135
+ { protocol: "http", hostname: "localhost" },
136
+ { protocol: "http", hostname: "127.0.0.1" },
137
+ ];
138
+ const consumerImages = override.images ?? {};
139
+ const consumerRemotePatterns = consumerImages.remotePatterns ?? [];
140
+ const mergedRemotePatterns = [
141
+ ...defaultRemotePatterns,
142
+ ...consumerRemotePatterns.filter((p) => !defaultRemotePatterns.some((d) => d.hostname ===
143
+ p.hostname)),
144
+ ];
115
145
  return {
116
146
  images: {
117
- remotePatterns: [
118
- { protocol: "https", hostname: "**" },
119
- { protocol: "http", hostname: "**" },
120
- ],
121
- ...(override.images ?? {}),
147
+ ...consumerImages,
148
+ remotePatterns: mergedRemotePatterns,
122
149
  },
123
150
  ...rest,
124
151
  serverExternalPackages: mergedExternal,
@@ -122,6 +122,16 @@ export declare const ADMIN_ENDPOINTS: {
122
122
  readonly SUBLISTING_CATEGORY_BY_ID: (id: string) => string;
123
123
  readonly PRODUCT_FEATURES: "/api/admin/features";
124
124
  readonly PRODUCT_FEATURE_BY_ID: (id: string) => string;
125
+ readonly TEAM: "/api/admin/team";
126
+ readonly TEAM_MEMBER: (uid: string) => string;
127
+ readonly USER_HARD_BAN: (uid: string) => string;
128
+ readonly USER_UNBAN: (uid: string) => string;
129
+ readonly USER_SOFT_BAN: (uid: string) => string;
130
+ readonly USER_SOFT_BAN_LIFT: (uid: string, action: string) => string;
131
+ readonly SUPPORT_TICKETS: "/api/admin/support-tickets";
132
+ readonly SUPPORT_TICKET_BY_ID: (id: string) => string;
133
+ readonly SCAMMERS: "/api/admin/scammers";
134
+ readonly SCAMMER_BY_ID: (id: string) => string;
125
135
  };
126
136
  export declare const CHAT_ENDPOINTS: {
127
137
  readonly LIST: "/api/chat";
@@ -200,6 +210,9 @@ export declare const MEDIA_ENDPOINTS: {
200
210
  readonly CROP: "/api/media/crop";
201
211
  readonly TRIM: "/api/media/trim";
202
212
  readonly DELETE: (url: string) => string;
213
+ /** Watermark-proxy for external (non-Firebase-Storage) image URLs. */
214
+ readonly EXT: "/api/media/ext";
215
+ readonly EXT_URL: (url: string) => string;
203
216
  };
204
217
  export declare const ORDER_ENDPOINTS: {
205
218
  readonly LIST: "/api/orders";
@@ -270,6 +283,16 @@ export declare const PROFILE_STATS_ENDPOINTS: {
270
283
  readonly PRODUCTS: (sellerId: string) => string;
271
284
  readonly REVIEWS: (userId: string) => string;
272
285
  };
286
+ export declare const SUPPORT_ENDPOINTS: {
287
+ readonly TICKETS: "/api/support/tickets";
288
+ readonly TICKET_BY_ID: (id: string) => string;
289
+ readonly TICKET_MESSAGES: (id: string) => string;
290
+ };
291
+ export declare const SCAMMER_ENDPOINTS: {
292
+ readonly LIST: "/api/scammers";
293
+ readonly BY_SLUG: (slug: string) => string;
294
+ readonly REPORT: "/api/scammers/report";
295
+ };
273
296
  export declare const BEFORE_AFTER_ENDPOINTS: {
274
297
  readonly LIST: "/api/before-after";
275
298
  };
@@ -397,6 +420,16 @@ export declare const API_ENDPOINTS: {
397
420
  readonly SUBLISTING_CATEGORY_BY_ID: (id: string) => string;
398
421
  readonly PRODUCT_FEATURES: "/api/admin/features";
399
422
  readonly PRODUCT_FEATURE_BY_ID: (id: string) => string;
423
+ readonly TEAM: "/api/admin/team";
424
+ readonly TEAM_MEMBER: (uid: string) => string;
425
+ readonly USER_HARD_BAN: (uid: string) => string;
426
+ readonly USER_UNBAN: (uid: string) => string;
427
+ readonly USER_SOFT_BAN: (uid: string) => string;
428
+ readonly USER_SOFT_BAN_LIFT: (uid: string, action: string) => string;
429
+ readonly SUPPORT_TICKETS: "/api/admin/support-tickets";
430
+ readonly SUPPORT_TICKET_BY_ID: (id: string) => string;
431
+ readonly SCAMMERS: "/api/admin/scammers";
432
+ readonly SCAMMER_BY_ID: (id: string) => string;
400
433
  };
401
434
  readonly CHAT: {
402
435
  readonly LIST: "/api/chat";
@@ -478,6 +511,9 @@ export declare const API_ENDPOINTS: {
478
511
  readonly CROP: "/api/media/crop";
479
512
  readonly TRIM: "/api/media/trim";
480
513
  readonly DELETE: (url: string) => string;
514
+ /** Watermark-proxy for external (non-Firebase-Storage) image URLs. */
515
+ readonly EXT: "/api/media/ext";
516
+ readonly EXT_URL: (url: string) => string;
481
517
  };
482
518
  readonly ORDERS: {
483
519
  readonly LIST: "/api/orders";
@@ -555,6 +591,16 @@ export declare const API_ENDPOINTS: {
555
591
  readonly SETTINGS: "/api/store/whatsapp-settings";
556
592
  readonly CATALOG_SYNC: "/api/store/whatsapp-settings/catalog-sync";
557
593
  };
594
+ readonly SUPPORT: {
595
+ readonly TICKETS: "/api/support/tickets";
596
+ readonly TICKET_BY_ID: (id: string) => string;
597
+ readonly TICKET_MESSAGES: (id: string) => string;
598
+ };
599
+ readonly SCAMMERS: {
600
+ readonly LIST: "/api/scammers";
601
+ readonly BY_SLUG: (slug: string) => string;
602
+ readonly REPORT: "/api/scammers/report";
603
+ };
558
604
  };
559
605
  /** Canonical alias — prefer API_ROUTES over API_ENDPOINTS in new code. */
560
606
  export declare const API_ROUTES: {
@@ -674,6 +720,16 @@ export declare const API_ROUTES: {
674
720
  readonly SUBLISTING_CATEGORY_BY_ID: (id: string) => string;
675
721
  readonly PRODUCT_FEATURES: "/api/admin/features";
676
722
  readonly PRODUCT_FEATURE_BY_ID: (id: string) => string;
723
+ readonly TEAM: "/api/admin/team";
724
+ readonly TEAM_MEMBER: (uid: string) => string;
725
+ readonly USER_HARD_BAN: (uid: string) => string;
726
+ readonly USER_UNBAN: (uid: string) => string;
727
+ readonly USER_SOFT_BAN: (uid: string) => string;
728
+ readonly USER_SOFT_BAN_LIFT: (uid: string, action: string) => string;
729
+ readonly SUPPORT_TICKETS: "/api/admin/support-tickets";
730
+ readonly SUPPORT_TICKET_BY_ID: (id: string) => string;
731
+ readonly SCAMMERS: "/api/admin/scammers";
732
+ readonly SCAMMER_BY_ID: (id: string) => string;
677
733
  };
678
734
  readonly CHAT: {
679
735
  readonly LIST: "/api/chat";
@@ -755,6 +811,9 @@ export declare const API_ROUTES: {
755
811
  readonly CROP: "/api/media/crop";
756
812
  readonly TRIM: "/api/media/trim";
757
813
  readonly DELETE: (url: string) => string;
814
+ /** Watermark-proxy for external (non-Firebase-Storage) image URLs. */
815
+ readonly EXT: "/api/media/ext";
816
+ readonly EXT_URL: (url: string) => string;
758
817
  };
759
818
  readonly ORDERS: {
760
819
  readonly LIST: "/api/orders";
@@ -832,4 +891,14 @@ export declare const API_ROUTES: {
832
891
  readonly SETTINGS: "/api/store/whatsapp-settings";
833
892
  readonly CATALOG_SYNC: "/api/store/whatsapp-settings/catalog-sync";
834
893
  };
894
+ readonly SUPPORT: {
895
+ readonly TICKETS: "/api/support/tickets";
896
+ readonly TICKET_BY_ID: (id: string) => string;
897
+ readonly TICKET_MESSAGES: (id: string) => string;
898
+ };
899
+ readonly SCAMMERS: {
900
+ readonly LIST: "/api/scammers";
901
+ readonly BY_SLUG: (slug: string) => string;
902
+ readonly REPORT: "/api/scammers/report";
903
+ };
835
904
  };
@@ -141,6 +141,16 @@ export const ADMIN_ENDPOINTS = {
141
141
  SUBLISTING_CATEGORY_BY_ID: (id) => `/api/admin/sublisting-categories/${id}`,
142
142
  PRODUCT_FEATURES: "/api/admin/features",
143
143
  PRODUCT_FEATURE_BY_ID: (id) => `/api/admin/features/${id}`,
144
+ TEAM: "/api/admin/team",
145
+ TEAM_MEMBER: (uid) => `/api/admin/team/${uid}`,
146
+ USER_HARD_BAN: (uid) => `/api/admin/users/${uid}/hard-ban`,
147
+ USER_UNBAN: (uid) => `/api/admin/users/${uid}/unban`,
148
+ USER_SOFT_BAN: (uid) => `/api/admin/users/${uid}/soft-ban`,
149
+ USER_SOFT_BAN_LIFT: (uid, action) => `/api/admin/users/${uid}/soft-ban/${encodeURIComponent(action)}`,
150
+ SUPPORT_TICKETS: "/api/admin/support-tickets",
151
+ SUPPORT_TICKET_BY_ID: (id) => `/api/admin/support-tickets/${id}`,
152
+ SCAMMERS: "/api/admin/scammers",
153
+ SCAMMER_BY_ID: (id) => `/api/admin/scammers/${id}`,
144
154
  };
145
155
  // ---------------------------------------------------------------------------
146
156
  // Chat
@@ -268,6 +278,9 @@ export const MEDIA_ENDPOINTS = {
268
278
  CROP: "/api/media/crop",
269
279
  TRIM: "/api/media/trim",
270
280
  DELETE: (url) => `/api/media?url=${encodeURIComponent(url)}`,
281
+ /** Watermark-proxy for external (non-Firebase-Storage) image URLs. */
282
+ EXT: "/api/media/ext",
283
+ EXT_URL: (url) => `/api/media/ext?url=${encodeURIComponent(url)}`,
271
284
  };
272
285
  // ---------------------------------------------------------------------------
273
286
  // Orders
@@ -374,6 +387,25 @@ export const PROFILE_STATS_ENDPOINTS = {
374
387
  // ---------------------------------------------------------------------------
375
388
  // Before / After
376
389
  // ---------------------------------------------------------------------------
390
+ // ---------------------------------------------------------------------------
391
+ // Support Tickets (user-facing)
392
+ // ---------------------------------------------------------------------------
393
+ export const SUPPORT_ENDPOINTS = {
394
+ TICKETS: "/api/support/tickets",
395
+ TICKET_BY_ID: (id) => `/api/support/tickets/${id}`,
396
+ TICKET_MESSAGES: (id) => `/api/support/tickets/${id}/messages`,
397
+ };
398
+ // ---------------------------------------------------------------------------
399
+ // Scammers (public)
400
+ // ---------------------------------------------------------------------------
401
+ export const SCAMMER_ENDPOINTS = {
402
+ LIST: "/api/scammers",
403
+ BY_SLUG: (slug) => `/api/scammers/${slug}`,
404
+ REPORT: "/api/scammers/report",
405
+ };
406
+ // ---------------------------------------------------------------------------
407
+ // Before / After
408
+ // ---------------------------------------------------------------------------
377
409
  export const BEFORE_AFTER_ENDPOINTS = {
378
410
  LIST: "/api/before-after",
379
411
  };
@@ -430,6 +462,8 @@ export const API_ENDPOINTS = {
430
462
  PROFILE_STATS: PROFILE_STATS_ENDPOINTS,
431
463
  DEMO: DEMO_ENDPOINTS,
432
464
  WHATSAPP_SELLER: WHATSAPP_SELLER_ENDPOINTS,
465
+ SUPPORT: SUPPORT_ENDPOINTS,
466
+ SCAMMERS: SCAMMER_ENDPOINTS,
433
467
  };
434
468
  /** Canonical alias — prefer API_ROUTES over API_ENDPOINTS in new code. */
435
469
  export const API_ROUTES = API_ENDPOINTS;
@@ -7,6 +7,12 @@ import type { ISearchProvider } from "./search";
7
7
  import type { ICacheProvider, IQueueProvider, IEventBus } from "./infra";
8
8
  import type { IStyleAdapter } from "./style";
9
9
  import type { IDbProvider } from "./repository";
10
+ export interface IRbacProvider {
11
+ /** Resolve the permission list for a user UID. Admin role returns []. */
12
+ getPermissions: (uid: string) => Promise<string[]>;
13
+ /** True when the user has the admin role (bypasses all permission checks). */
14
+ isAdmin: (uid: string) => Promise<boolean>;
15
+ }
10
16
  export interface ProviderRegistry {
11
17
  auth: IAuthProvider;
12
18
  session: ISessionProvider;
@@ -20,6 +26,7 @@ export interface ProviderRegistry {
20
26
  cache?: ICacheProvider;
21
27
  queue?: IQueueProvider;
22
28
  eventBus?: IEventBus;
29
+ rbac?: IRbacProvider;
23
30
  }
24
31
  export declare function registerProviders(registry: ProviderRegistry): void;
25
32
  export declare function getProviders(): ProviderRegistry;
@@ -0,0 +1,10 @@
1
+ export interface AdminEmployeeEditorViewProps {
2
+ open: boolean;
3
+ onClose: () => void;
4
+ mode: "invite" | "edit";
5
+ userId?: string;
6
+ displayName?: string;
7
+ currentPermissionGroup?: string;
8
+ currentPermissions?: string[];
9
+ }
10
+ export declare function AdminEmployeeEditorView({ open, onClose, mode, userId, displayName, currentPermissionGroup, currentPermissions, }: AdminEmployeeEditorViewProps): import("react/jsx-runtime").JSX.Element;