@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.
- package/dist/_internal/client/features/layout/DashboardLayoutClient.d.ts +8 -1
- package/dist/_internal/client/features/layout/DashboardLayoutClient.js +18 -2
- package/dist/_internal/server/features/auth/capabilities.d.ts +17 -0
- package/dist/_internal/server/features/auth/capabilities.js +31 -0
- package/dist/_internal/server/features/auth/index.d.ts +2 -0
- package/dist/_internal/server/features/auth/index.js +2 -0
- package/dist/_internal/server/features/auth/permissions.d.ts +49 -0
- package/dist/_internal/server/features/auth/permissions.js +76 -0
- package/dist/configs/next.d.ts +6 -1
- package/dist/configs/next.js +45 -18
- package/dist/constants/api-endpoints.d.ts +69 -0
- package/dist/constants/api-endpoints.js +34 -0
- package/dist/contracts/registry.d.ts +7 -0
- package/dist/features/admin/components/AdminEmployeeEditorView.d.ts +10 -0
- package/dist/features/admin/components/AdminEmployeeEditorView.js +168 -0
- package/dist/features/admin/components/AdminSidebar.d.ts +2 -0
- package/dist/features/admin/components/AdminStoreEditorView.d.ts +2 -1
- package/dist/features/admin/components/AdminStoreEditorView.js +55 -3
- package/dist/features/admin/components/AdminStoresView.js +3 -1
- package/dist/features/admin/components/AdminSupportTicketDetailView.d.ts +23 -0
- package/dist/features/admin/components/AdminSupportTicketDetailView.js +83 -0
- package/dist/features/admin/components/AdminSupportTicketsView.d.ts +4 -0
- package/dist/features/admin/components/AdminSupportTicketsView.js +151 -0
- package/dist/features/admin/components/AdminTeamView.d.ts +4 -0
- package/dist/features/admin/components/AdminTeamView.js +139 -0
- package/dist/features/admin/components/AdminUserEditorView.d.ts +14 -1
- package/dist/features/admin/components/AdminUserEditorView.js +116 -14
- package/dist/features/admin/components/AdminUsersView.js +39 -12
- package/dist/features/admin/components/index.d.ts +8 -0
- package/dist/features/admin/components/index.js +4 -0
- package/dist/features/admin/constants/filter-tabs.d.ts +37 -0
- package/dist/features/admin/constants/filter-tabs.js +17 -0
- package/dist/features/auth/server/checkSoftBan.d.ts +15 -0
- package/dist/features/auth/server/checkSoftBan.js +36 -0
- package/dist/features/auth/server.d.ts +1 -0
- package/dist/features/auth/server.js +1 -0
- package/dist/features/media/types/index.js +2 -1
- package/dist/features/stores/repository/store.repository.d.ts +5 -1
- package/dist/features/stores/repository/store.repository.js +27 -2
- package/dist/features/support/repository/support.repository.d.ts +18 -0
- package/dist/features/support/repository/support.repository.js +117 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +12 -0
- package/dist/next/api/routeHandler.d.ts +8 -0
- package/dist/next/api/routeHandler.js +24 -3
- package/dist/next/routing/route-map.d.ts +2 -0
- package/dist/next/routing/route-map.js +1 -0
- package/dist/repositories/index.d.ts +2 -0
- package/dist/repositories/index.js +1 -0
- package/dist/security/pii-schemas.d.ts +2 -0
- package/dist/security/pii-schemas.js +2 -0
- package/dist/seed/stores-seed-data.js +8 -0
- package/dist/server.d.ts +6 -0
- package/dist/server.js +6 -0
- package/dist/tailwind-utilities.css +1 -1
- package/dist/utils/media-url.d.ts +9 -0
- package/dist/utils/media-url.js +29 -0
- 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
|
-
|
|
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
|
-
|
|
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
|
+
}
|
|
@@ -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
|
+
}
|
package/dist/configs/next.d.ts
CHANGED
|
@@ -6,7 +6,12 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Notable defaults:
|
|
8
8
|
* - `serverExternalPackages` includes firebase-admin + GCP deps
|
|
9
|
-
* - `outputFileTracingIncludes`
|
|
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
|
*
|
package/dist/configs/next.js
CHANGED
|
@@ -6,7 +6,12 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Notable defaults:
|
|
8
8
|
* - `serverExternalPackages` includes firebase-admin + GCP deps
|
|
9
|
-
* - `outputFileTracingIncludes`
|
|
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.
|
|
76
|
-
//
|
|
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
|
-
|
|
80
|
-
"./node_modules/firebase-admin
|
|
81
|
-
|
|
82
|
-
"./node_modules
|
|
83
|
-
|
|
84
|
-
"./node_modules/
|
|
85
|
-
"./node_modules/
|
|
86
|
-
"./node_modules/
|
|
87
|
-
"./node_modules
|
|
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
|
-
|
|
118
|
-
|
|
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;
|