@carlonicora/nextjs-jsonapi 1.77.3 → 1.79.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/AssistantInterface-BYgI5z1-.d.mts +12 -0
- package/dist/AssistantInterface-DfDcz0gJ.d.ts +12 -0
- package/dist/AssistantMessageInterface-DWnbd6J7.d.ts +36 -0
- package/dist/AssistantMessageInterface-Mla6kgPe.d.mts +36 -0
- package/dist/{AuthComponent-Blbs06ud.d.ts → AuthComponent-B6DIk8Vf.d.ts} +1 -1
- package/dist/{AuthComponent-huIaK5rm.d.mts → AuthComponent-BKI0ZbtD.d.mts} +1 -1
- package/dist/{BlockNoteEditor-7HAAXN3H.mjs → BlockNoteEditor-6CBDTVKV.mjs} +4 -4
- package/dist/{BlockNoteEditor-UB7T7V67.js → BlockNoteEditor-EH4HWI7H.js} +14 -14
- package/dist/{BlockNoteEditor-UB7T7V67.js.map → BlockNoteEditor-EH4HWI7H.js.map} +1 -1
- package/dist/RbacTypes-BTbr27Ew.d.mts +43 -0
- package/dist/RbacTypes-BTbr27Ew.d.ts +43 -0
- package/dist/{auth.interface-CQJ6A2Cj.d.ts → auth.interface-BBUgMZzs.d.ts} +1 -1
- package/dist/{auth.interface-Bdq7-8iV.d.mts → auth.interface-XYEREOD6.d.mts} +1 -1
- package/dist/billing/index.js +346 -346
- package/dist/billing/index.mjs +3 -3
- package/dist/{chunk-FKLP4NED.js → chunk-5IEWLLLD.js} +379 -18
- package/dist/chunk-5IEWLLLD.js.map +1 -0
- package/dist/{chunk-XI35ALWY.mjs → chunk-BKM5U3DE.mjs} +362 -1
- package/dist/chunk-BKM5U3DE.mjs.map +1 -0
- package/dist/{chunk-F44ET4AC.mjs → chunk-ENRSFVOS.mjs} +2657 -2264
- package/dist/chunk-ENRSFVOS.mjs.map +1 -0
- package/dist/{chunk-JOJZRGZL.mjs → chunk-MEWXQEVE.mjs} +38 -29
- package/dist/{chunk-JOJZRGZL.mjs.map → chunk-MEWXQEVE.mjs.map} +1 -1
- package/dist/{chunk-OTZEXASK.js → chunk-TWDSDTHU.js} +39 -30
- package/dist/chunk-TWDSDTHU.js.map +1 -0
- package/dist/{chunk-CV7UOUKQ.js → chunk-ZDP3MBUI.js} +1813 -1420
- package/dist/chunk-ZDP3MBUI.js.map +1 -0
- package/dist/client/index.d.mts +6 -24
- package/dist/client/index.d.ts +6 -24
- package/dist/client/index.js +4 -10
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +3 -9
- package/dist/components/index.d.mts +51 -34
- package/dist/components/index.d.ts +51 -34
- package/dist/components/index.js +4 -4
- package/dist/components/index.js.map +1 -1
- package/dist/components/index.mjs +9 -9
- package/dist/{config-B3jKt9P7.d.ts → config-B5oBQVEA.d.ts} +1 -1
- package/dist/{config-DkHF61xA.d.mts → config-Bx_uh22h.d.mts} +1 -1
- package/dist/contexts/index.d.mts +65 -4
- package/dist/contexts/index.d.ts +65 -4
- package/dist/contexts/index.js +12 -4
- package/dist/contexts/index.js.map +1 -1
- package/dist/contexts/index.mjs +11 -3
- package/dist/core/index.d.mts +126 -11
- package/dist/core/index.d.ts +126 -11
- package/dist/core/index.js +16 -2
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +15 -1
- package/dist/index.d.mts +118 -20
- package/dist/index.d.ts +118 -20
- package/dist/index.js +19 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +18 -2
- package/dist/{notification.interface-DG6obXUH.d.mts → notification.interface-DLZGtV7Z.d.mts} +1 -1
- package/dist/{notification.interface-DcSuc9CL.d.ts → notification.interface-aLEJbA_g.d.ts} +1 -1
- package/dist/{s3.service-DGilbikH.d.mts → s3.service-CVgLWaDc.d.mts} +2 -2
- package/dist/{s3.service-DjwEQJPe.d.ts → s3.service-SLlX0Zbz.d.ts} +2 -2
- package/dist/server/index.d.mts +3 -3
- package/dist/server/index.d.ts +3 -3
- package/dist/server/index.js +3 -3
- package/dist/server/index.mjs +1 -1
- package/dist/useDataListRetriever-BqJSFBck.d.mts +33 -0
- package/dist/useDataListRetriever-BqJSFBck.d.ts +33 -0
- package/dist/{useSocket-CmzVtg32.d.mts → useSocket-BkxHHujj.d.mts} +1 -1
- package/dist/{useSocket-8eUtnL7J.d.ts → useSocket-CMDjWFYm.d.ts} +1 -1
- package/package.json +1 -1
- package/src/client/index.ts +0 -4
- package/src/components/index.ts +2 -3
- package/src/contexts/index.ts +2 -0
- package/src/core/index.ts +4 -0
- package/src/core/registry/ModuleRegistry.ts +10 -0
- package/src/features/assistant/AssistantModule.ts +19 -0
- package/src/features/assistant/components/containers/AssistantContainer.tsx +56 -0
- package/src/features/assistant/components/containers/__tests__/AssistantContainer.spec.tsx +101 -0
- package/src/features/assistant/components/index.ts +1 -0
- package/src/features/assistant/components/parts/AssistantComposer.tsx +56 -0
- package/src/features/assistant/components/parts/AssistantEmptyState.tsx +47 -0
- package/src/features/assistant/components/parts/AssistantSidebar.tsx +64 -0
- package/src/features/assistant/components/parts/AssistantStatusLine.tsx +19 -0
- package/src/features/assistant/components/parts/AssistantThread.tsx +36 -0
- package/src/features/assistant/components/parts/AssistantThreadHeader.tsx +91 -0
- package/src/features/assistant/components/parts/__tests__/AssistantComposer.spec.tsx +32 -0
- package/src/features/assistant/components/parts/__tests__/AssistantEmptyState.spec.tsx +27 -0
- package/src/features/assistant/components/parts/__tests__/AssistantSidebar.spec.tsx +58 -0
- package/src/features/assistant/components/parts/__tests__/AssistantStatusLine.spec.tsx +19 -0
- package/src/features/assistant/components/parts/__tests__/AssistantThread.spec.tsx +39 -0
- package/src/features/assistant/components/parts/__tests__/AssistantThreadHeader.spec.tsx +67 -0
- package/src/features/assistant/contexts/AssistantContext.tsx +255 -0
- package/src/features/assistant/contexts/__tests__/AssistantContext.spec.tsx +375 -0
- package/src/features/assistant/data/Assistant.ts +37 -0
- package/src/features/assistant/data/AssistantInterface.ts +11 -0
- package/src/features/assistant/data/AssistantService.ts +79 -0
- package/src/features/assistant/data/index.ts +3 -0
- package/src/features/assistant/index.ts +2 -0
- package/src/features/assistant/utils/__tests__/groupThreadsByBucket.spec.ts +24 -0
- package/src/features/assistant/utils/__tests__/resolveReferenceableModules.spec.ts +92 -0
- package/src/features/assistant/utils/groupThreadsByBucket.ts +26 -0
- package/src/features/assistant/utils/resolveReferenceableModules.ts +14 -0
- package/src/features/assistant-message/AssistantMessageModule.ts +28 -0
- package/src/features/assistant-message/components/MessageItem.tsx +60 -0
- package/src/features/assistant-message/components/MessageList.tsx +38 -0
- package/src/features/assistant-message/components/__tests__/MessageItem.spec.tsx +108 -0
- package/src/features/assistant-message/components/index.ts +2 -0
- package/src/features/assistant-message/components/parts/ReferenceBadges.tsx +46 -0
- package/src/features/assistant-message/components/parts/SuggestedFollowUps.tsx +52 -0
- package/src/features/assistant-message/components/parts/__tests__/ReferenceBadges.spec.tsx +59 -0
- package/src/features/assistant-message/components/parts/__tests__/SuggestedFollowUps.spec.tsx +29 -0
- package/src/features/assistant-message/data/AssistantMessage.ts +95 -0
- package/src/features/assistant-message/data/AssistantMessageInterface.ts +21 -0
- package/src/features/assistant-message/data/AssistantMessageService.ts +40 -0
- package/src/features/assistant-message/data/__tests__/AssistantMessage.spec.ts +158 -0
- package/src/features/assistant-message/data/index.ts +3 -0
- package/src/features/assistant-message/index.ts +2 -0
- package/src/features/rbac/components/RbacContainer.tsx +318 -49
- package/src/features/rbac/components/RbacPermissionPicker.tsx +144 -121
- package/src/features/rbac/contexts/RbacContext.tsx +209 -0
- package/src/features/rbac/contexts/index.ts +1 -0
- package/src/features/rbac/data/RbacMatrixModel.ts +84 -0
- package/src/features/rbac/data/RbacService.ts +61 -33
- package/src/features/rbac/data/RbacTypes.ts +28 -0
- package/src/features/rbac/data/index.ts +1 -0
- package/src/features/rbac/index.ts +1 -10
- package/src/features/rbac/rbac.module.ts +13 -0
- package/src/features/user/contexts/CurrentUserContext.tsx +5 -13
- package/src/features/user/contexts/__tests__/CurrentUserContext.spec.tsx +141 -0
- package/src/index.ts +4 -0
- package/dist/HowToInterface-BKhnkzBp.d.ts +0 -17
- package/dist/HowToInterface-Cj8OuQFf.d.mts +0 -17
- package/dist/ModulePathsInterface-BrdqgteS.d.mts +0 -31
- package/dist/ModulePathsInterface-DJKs7s_s.d.ts +0 -31
- package/dist/chunk-CV7UOUKQ.js.map +0 -1
- package/dist/chunk-F44ET4AC.mjs.map +0 -1
- package/dist/chunk-FKLP4NED.js.map +0 -1
- package/dist/chunk-OTZEXASK.js.map +0 -1
- package/dist/chunk-XI35ALWY.mjs.map +0 -1
- package/dist/useRbacState-C88O-5L8.d.ts +0 -77
- package/dist/useRbacState-mqYiRp3J.d.mts +0 -77
- package/src/features/rbac/components/RbacFeatureSection.tsx +0 -66
- package/src/features/rbac/components/RbacModuleTable.tsx +0 -121
- package/src/features/rbac/components/RbacToolbar.tsx +0 -40
- package/src/features/rbac/hooks/useRbacState.test.ts +0 -180
- package/src/features/rbac/hooks/useRbacState.ts +0 -319
- package/src/features/rbac/utils/RbacMigrationGenerator.test.ts +0 -124
- package/src/features/rbac/utils/RbacMigrationGenerator.ts +0 -184
- /package/dist/{BlockNoteEditor-7HAAXN3H.mjs.map → BlockNoteEditor-6CBDTVKV.mjs.map} +0 -0
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import { F as FeatureInterface } from './feature.interface-BO25VLlx.js';
|
|
2
|
-
import { R as RoleInterface } from './notification.interface-DcSuc9CL.js';
|
|
3
|
-
import { P as PermissionMappingInterface, M as ModulePathsInterface, A as ActionType, a as PermissionValue, b as PermissionsMap } from './ModulePathsInterface-DJKs7s_s.js';
|
|
4
|
-
|
|
5
|
-
type PageInfo = {
|
|
6
|
-
startItem: number;
|
|
7
|
-
endItem: number;
|
|
8
|
-
pageSize: number;
|
|
9
|
-
};
|
|
10
|
-
type DataListRetriever<T> = {
|
|
11
|
-
ready?: boolean;
|
|
12
|
-
setReady: (state: boolean) => void;
|
|
13
|
-
isLoaded: boolean;
|
|
14
|
-
data: T[] | undefined;
|
|
15
|
-
total?: number;
|
|
16
|
-
next?: (onlyNewRecords?: boolean) => Promise<void>;
|
|
17
|
-
previous?: (onlyNewRecords?: boolean) => Promise<void>;
|
|
18
|
-
search: (search: string) => Promise<void>;
|
|
19
|
-
refresh: () => Promise<void>;
|
|
20
|
-
addAdditionalParameter: (key: string, value: any | null) => void;
|
|
21
|
-
removeAdditionalParameter: (key: string) => void;
|
|
22
|
-
setRefreshedElement: (element: T) => void;
|
|
23
|
-
removeElement: (element: T) => void;
|
|
24
|
-
isSearch: boolean;
|
|
25
|
-
pageInfo?: PageInfo;
|
|
26
|
-
};
|
|
27
|
-
declare function useDataListRetriever<T>(params: {
|
|
28
|
-
ready?: boolean;
|
|
29
|
-
retriever: (params: any) => Promise<T[]>;
|
|
30
|
-
retrieverParams?: any;
|
|
31
|
-
search?: string;
|
|
32
|
-
addAdditionalParameter?: (key: string, value: any | null) => void;
|
|
33
|
-
requiresSearch?: boolean;
|
|
34
|
-
module: any;
|
|
35
|
-
}): DataListRetriever<T>;
|
|
36
|
-
|
|
37
|
-
interface OriginalData {
|
|
38
|
-
features: FeatureInterface[];
|
|
39
|
-
roles: RoleInterface[];
|
|
40
|
-
permissionMappings: PermissionMappingInterface[];
|
|
41
|
-
moduleRelationshipPaths: Map<string, string[]>;
|
|
42
|
-
}
|
|
43
|
-
declare function useRbacState(): {
|
|
44
|
-
original: OriginalData | null;
|
|
45
|
-
isDirty: boolean;
|
|
46
|
-
init: (features: FeatureInterface[], roles: RoleInterface[], permissionMappings: PermissionMappingInterface[], modulePaths: ModulePathsInterface[]) => void;
|
|
47
|
-
setFeatureIsCore: (featureId: string, isCore: boolean) => void;
|
|
48
|
-
setModuleDefaultPermission: (moduleId: string, actionType: ActionType, value: PermissionValue) => void;
|
|
49
|
-
setRolePermission: (roleId: string, moduleId: string, actionType: ActionType, value: PermissionValue) => void;
|
|
50
|
-
clearRolePermission: (roleId: string, moduleId: string, actionType: ActionType) => void;
|
|
51
|
-
clearAllRolePermissions: (roleId: string, moduleId: string) => void;
|
|
52
|
-
resetModulePermissions: (moduleId: string, roles: {
|
|
53
|
-
id: string;
|
|
54
|
-
}[]) => void;
|
|
55
|
-
reset: () => void;
|
|
56
|
-
getFeatureIsCore: (featureId: string) => boolean;
|
|
57
|
-
getModuleDefaultPermission: (moduleId: string, actionType: ActionType) => PermissionValue | undefined;
|
|
58
|
-
getRolePermission: (roleId: string, moduleId: string, actionType: ActionType) => PermissionValue | undefined | null;
|
|
59
|
-
getEffectiveConfiguration: () => {
|
|
60
|
-
features: {
|
|
61
|
-
id: string;
|
|
62
|
-
name: string;
|
|
63
|
-
isCore: boolean;
|
|
64
|
-
modules: {
|
|
65
|
-
id: string;
|
|
66
|
-
name: string;
|
|
67
|
-
permissions: PermissionsMap;
|
|
68
|
-
}[];
|
|
69
|
-
}[];
|
|
70
|
-
roles: RoleInterface[];
|
|
71
|
-
rolePermissionsMap: Map<string, PermissionsMap>;
|
|
72
|
-
} | null;
|
|
73
|
-
getModuleRelationshipPaths: (moduleId: string) => string[];
|
|
74
|
-
};
|
|
75
|
-
type RbacStateApi = ReturnType<typeof useRbacState>;
|
|
76
|
-
|
|
77
|
-
export { type DataListRetriever as D, type RbacStateApi as R, useDataListRetriever as a, useRbacState as u };
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import { F as FeatureInterface } from './feature.interface-CXb1-vNq.mjs';
|
|
2
|
-
import { R as RoleInterface } from './notification.interface-DG6obXUH.mjs';
|
|
3
|
-
import { P as PermissionMappingInterface, M as ModulePathsInterface, A as ActionType, a as PermissionValue, b as PermissionsMap } from './ModulePathsInterface-BrdqgteS.mjs';
|
|
4
|
-
|
|
5
|
-
type PageInfo = {
|
|
6
|
-
startItem: number;
|
|
7
|
-
endItem: number;
|
|
8
|
-
pageSize: number;
|
|
9
|
-
};
|
|
10
|
-
type DataListRetriever<T> = {
|
|
11
|
-
ready?: boolean;
|
|
12
|
-
setReady: (state: boolean) => void;
|
|
13
|
-
isLoaded: boolean;
|
|
14
|
-
data: T[] | undefined;
|
|
15
|
-
total?: number;
|
|
16
|
-
next?: (onlyNewRecords?: boolean) => Promise<void>;
|
|
17
|
-
previous?: (onlyNewRecords?: boolean) => Promise<void>;
|
|
18
|
-
search: (search: string) => Promise<void>;
|
|
19
|
-
refresh: () => Promise<void>;
|
|
20
|
-
addAdditionalParameter: (key: string, value: any | null) => void;
|
|
21
|
-
removeAdditionalParameter: (key: string) => void;
|
|
22
|
-
setRefreshedElement: (element: T) => void;
|
|
23
|
-
removeElement: (element: T) => void;
|
|
24
|
-
isSearch: boolean;
|
|
25
|
-
pageInfo?: PageInfo;
|
|
26
|
-
};
|
|
27
|
-
declare function useDataListRetriever<T>(params: {
|
|
28
|
-
ready?: boolean;
|
|
29
|
-
retriever: (params: any) => Promise<T[]>;
|
|
30
|
-
retrieverParams?: any;
|
|
31
|
-
search?: string;
|
|
32
|
-
addAdditionalParameter?: (key: string, value: any | null) => void;
|
|
33
|
-
requiresSearch?: boolean;
|
|
34
|
-
module: any;
|
|
35
|
-
}): DataListRetriever<T>;
|
|
36
|
-
|
|
37
|
-
interface OriginalData {
|
|
38
|
-
features: FeatureInterface[];
|
|
39
|
-
roles: RoleInterface[];
|
|
40
|
-
permissionMappings: PermissionMappingInterface[];
|
|
41
|
-
moduleRelationshipPaths: Map<string, string[]>;
|
|
42
|
-
}
|
|
43
|
-
declare function useRbacState(): {
|
|
44
|
-
original: OriginalData | null;
|
|
45
|
-
isDirty: boolean;
|
|
46
|
-
init: (features: FeatureInterface[], roles: RoleInterface[], permissionMappings: PermissionMappingInterface[], modulePaths: ModulePathsInterface[]) => void;
|
|
47
|
-
setFeatureIsCore: (featureId: string, isCore: boolean) => void;
|
|
48
|
-
setModuleDefaultPermission: (moduleId: string, actionType: ActionType, value: PermissionValue) => void;
|
|
49
|
-
setRolePermission: (roleId: string, moduleId: string, actionType: ActionType, value: PermissionValue) => void;
|
|
50
|
-
clearRolePermission: (roleId: string, moduleId: string, actionType: ActionType) => void;
|
|
51
|
-
clearAllRolePermissions: (roleId: string, moduleId: string) => void;
|
|
52
|
-
resetModulePermissions: (moduleId: string, roles: {
|
|
53
|
-
id: string;
|
|
54
|
-
}[]) => void;
|
|
55
|
-
reset: () => void;
|
|
56
|
-
getFeatureIsCore: (featureId: string) => boolean;
|
|
57
|
-
getModuleDefaultPermission: (moduleId: string, actionType: ActionType) => PermissionValue | undefined;
|
|
58
|
-
getRolePermission: (roleId: string, moduleId: string, actionType: ActionType) => PermissionValue | undefined | null;
|
|
59
|
-
getEffectiveConfiguration: () => {
|
|
60
|
-
features: {
|
|
61
|
-
id: string;
|
|
62
|
-
name: string;
|
|
63
|
-
isCore: boolean;
|
|
64
|
-
modules: {
|
|
65
|
-
id: string;
|
|
66
|
-
name: string;
|
|
67
|
-
permissions: PermissionsMap;
|
|
68
|
-
}[];
|
|
69
|
-
}[];
|
|
70
|
-
roles: RoleInterface[];
|
|
71
|
-
rolePermissionsMap: Map<string, PermissionsMap>;
|
|
72
|
-
} | null;
|
|
73
|
-
getModuleRelationshipPaths: (moduleId: string) => string[];
|
|
74
|
-
};
|
|
75
|
-
type RbacStateApi = ReturnType<typeof useRbacState>;
|
|
76
|
-
|
|
77
|
-
export { type DataListRetriever as D, type RbacStateApi as R, useDataListRetriever as a, useRbacState as u };
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { cn } from "../../../lib/utils";
|
|
4
|
-
import { ChevronDownIcon } from "lucide-react";
|
|
5
|
-
import { useTranslations } from "next-intl";
|
|
6
|
-
import { useState } from "react";
|
|
7
|
-
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "../../../shadcnui";
|
|
8
|
-
import { Switch } from "../../../shadcnui";
|
|
9
|
-
import { FeatureInterface } from "../../feature";
|
|
10
|
-
import { RoleInterface } from "../../role";
|
|
11
|
-
import { RbacStateApi } from "../hooks/useRbacState";
|
|
12
|
-
import RbacModuleTable from "./RbacModuleTable";
|
|
13
|
-
|
|
14
|
-
interface RbacFeatureSectionProps {
|
|
15
|
-
feature: FeatureInterface;
|
|
16
|
-
roles: RoleInterface[];
|
|
17
|
-
stateApi: RbacStateApi;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export default function RbacFeatureSection({ feature, roles, stateApi }: RbacFeatureSectionProps) {
|
|
21
|
-
const t = useTranslations();
|
|
22
|
-
const [isOpen, setIsOpen] = useState(true);
|
|
23
|
-
const featureIsCore = stateApi.getFeatureIsCore(feature.id);
|
|
24
|
-
|
|
25
|
-
return (
|
|
26
|
-
<Collapsible open={isOpen} onOpenChange={setIsOpen}>
|
|
27
|
-
<div className="rounded-lg border bg-card">
|
|
28
|
-
{/* Feature header */}
|
|
29
|
-
<CollapsibleTrigger className="w-full">
|
|
30
|
-
<div className="flex cursor-pointer items-center justify-between px-4 py-3 hover:bg-muted/50 transition-colors">
|
|
31
|
-
<div className="flex items-center gap-3">
|
|
32
|
-
<ChevronDownIcon
|
|
33
|
-
className={cn("h-4 w-4 text-muted-foreground transition-transform", !isOpen && "-rotate-90")}
|
|
34
|
-
/>
|
|
35
|
-
<h3 className="text-base font-semibold">{feature.name}</h3>
|
|
36
|
-
<span className="text-xs text-muted-foreground">
|
|
37
|
-
{t("rbac.module_count", { count: feature.modules.length })}
|
|
38
|
-
</span>
|
|
39
|
-
</div>
|
|
40
|
-
<div className="flex items-center gap-2" onClick={(e) => e.stopPropagation()}>
|
|
41
|
-
<span className="text-xs text-muted-foreground">{t("rbac.core")}</span>
|
|
42
|
-
<Switch
|
|
43
|
-
checked={featureIsCore}
|
|
44
|
-
onCheckedChange={(checked) => stateApi.setFeatureIsCore(feature.id, checked)}
|
|
45
|
-
className="data-checked:bg-accent data-unchecked:bg-gray-300"
|
|
46
|
-
/>
|
|
47
|
-
</div>
|
|
48
|
-
</div>
|
|
49
|
-
</CollapsibleTrigger>
|
|
50
|
-
|
|
51
|
-
<CollapsibleContent>
|
|
52
|
-
<div className="space-y-3 p-4 pt-0">
|
|
53
|
-
{feature.modules.map((mod) => (
|
|
54
|
-
<RbacModuleTable key={mod.id} module={mod} roles={roles} stateApi={stateApi} />
|
|
55
|
-
))}
|
|
56
|
-
{feature.modules.length === 0 && (
|
|
57
|
-
<p className="text-sm text-muted-foreground italic py-4 text-center">{t("rbac.no_modules")}</p>
|
|
58
|
-
)}
|
|
59
|
-
</div>
|
|
60
|
-
</CollapsibleContent>
|
|
61
|
-
</div>
|
|
62
|
-
</Collapsible>
|
|
63
|
-
);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export { RbacFeatureSection };
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { RotateCcwIcon } from "lucide-react";
|
|
4
|
-
import { useTranslations } from "next-intl";
|
|
5
|
-
import { Button } from "../../../shadcnui";
|
|
6
|
-
import { ModuleInterface } from "../../module";
|
|
7
|
-
import { RoleInterface } from "../../role";
|
|
8
|
-
import { ACTION_TYPES, ActionType, COMPANY_ADMINISTRATOR_ROLE_ID } from "../data/RbacTypes";
|
|
9
|
-
import { RbacStateApi } from "../hooks/useRbacState";
|
|
10
|
-
import RbacPermissionPicker from "./RbacPermissionPicker";
|
|
11
|
-
|
|
12
|
-
interface RbacModuleTableProps {
|
|
13
|
-
module: ModuleInterface;
|
|
14
|
-
roles: RoleInterface[];
|
|
15
|
-
stateApi: RbacStateApi;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const ACTION_LABELS: Record<ActionType, string> = {
|
|
19
|
-
read: "Read",
|
|
20
|
-
create: "Create",
|
|
21
|
-
update: "Update",
|
|
22
|
-
delete: "Delete",
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
export default function RbacModuleTable({ module, roles, stateApi }: RbacModuleTableProps) {
|
|
26
|
-
const t = useTranslations();
|
|
27
|
-
const handleReset = () => {
|
|
28
|
-
stateApi.resetModulePermissions(module.id, roles);
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
return (
|
|
32
|
-
<div className="rounded-lg border border-accent bg-card">
|
|
33
|
-
{/* Module header */}
|
|
34
|
-
<div className="flex items-center justify-between border-b px-4 py-2">
|
|
35
|
-
<h4 className="text-sm font-medium">{module.name}</h4>
|
|
36
|
-
<Button
|
|
37
|
-
variant="ghost"
|
|
38
|
-
size="sm"
|
|
39
|
-
onClick={handleReset}
|
|
40
|
-
className="h-7 px-2 text-muted-foreground hover:text-foreground"
|
|
41
|
-
>
|
|
42
|
-
<RotateCcwIcon className="h-3.5 w-3.5" />
|
|
43
|
-
</Button>
|
|
44
|
-
</div>
|
|
45
|
-
|
|
46
|
-
{/* Permission grid - Rows = Default + Roles, Columns = Actions */}
|
|
47
|
-
<div className="overflow-x-auto">
|
|
48
|
-
<table className="w-full text-sm">
|
|
49
|
-
<thead>
|
|
50
|
-
<tr className="border-b bg-muted/50">
|
|
51
|
-
<th className="px-4 py-2 text-left text-xs font-medium text-muted-foreground w-40">{t("rbac.role")}</th>
|
|
52
|
-
{ACTION_TYPES.map((actionType) => (
|
|
53
|
-
<th
|
|
54
|
-
key={actionType}
|
|
55
|
-
className="px-2 py-2 text-center text-xs font-medium text-muted-foreground min-w-28"
|
|
56
|
-
>
|
|
57
|
-
{ACTION_LABELS[actionType]}
|
|
58
|
-
</th>
|
|
59
|
-
))}
|
|
60
|
-
</tr>
|
|
61
|
-
</thead>
|
|
62
|
-
<tbody>
|
|
63
|
-
{/* Default permissions row */}
|
|
64
|
-
<tr className="border-b">
|
|
65
|
-
<td className="px-4 py-1 text-xs font-medium text-muted-foreground">{t("rbac.defaults")}</td>
|
|
66
|
-
{ACTION_TYPES.map((actionType) => {
|
|
67
|
-
const defaultValue = stateApi.getModuleDefaultPermission(module.id, actionType) ?? false;
|
|
68
|
-
const originalDefaultValue = module.permissions[actionType] ?? false;
|
|
69
|
-
|
|
70
|
-
return (
|
|
71
|
-
<td key={actionType} className="px-2 py-1">
|
|
72
|
-
<RbacPermissionPicker
|
|
73
|
-
value={defaultValue}
|
|
74
|
-
originalValue={originalDefaultValue}
|
|
75
|
-
isRoleColumn={false}
|
|
76
|
-
knownSegments={stateApi.getModuleRelationshipPaths(module.id)}
|
|
77
|
-
onSetValue={(value) => stateApi.setModuleDefaultPermission(module.id, actionType, value)}
|
|
78
|
-
/>
|
|
79
|
-
</td>
|
|
80
|
-
);
|
|
81
|
-
})}
|
|
82
|
-
</tr>
|
|
83
|
-
|
|
84
|
-
{/* Role rows (CompanyAdministrator hidden — always all-true in migration) */}
|
|
85
|
-
{roles
|
|
86
|
-
.filter((role) => role.id !== COMPANY_ADMINISTRATOR_ROLE_ID)
|
|
87
|
-
.map((role) => (
|
|
88
|
-
<tr key={role.id} className="border-b last:border-b-0">
|
|
89
|
-
<td className="px-4 py-1 text-xs font-medium text-muted-foreground">{role.name}</td>
|
|
90
|
-
{ACTION_TYPES.map((actionType) => {
|
|
91
|
-
const roleValue = stateApi.getRolePermission(role.id, module.id, actionType);
|
|
92
|
-
const originalMapping = stateApi.original?.permissionMappings.find(
|
|
93
|
-
(pm) => pm.roleId === role.id && pm.moduleId === module.id,
|
|
94
|
-
);
|
|
95
|
-
const originalRoleValue = originalMapping
|
|
96
|
-
? (originalMapping.permissions[actionType] ?? null)
|
|
97
|
-
: undefined;
|
|
98
|
-
|
|
99
|
-
return (
|
|
100
|
-
<td key={actionType} className="px-2 py-1">
|
|
101
|
-
<RbacPermissionPicker
|
|
102
|
-
value={roleValue}
|
|
103
|
-
originalValue={originalRoleValue}
|
|
104
|
-
isRoleColumn={true}
|
|
105
|
-
knownSegments={stateApi.getModuleRelationshipPaths(module.id)}
|
|
106
|
-
onSetValue={(value) => stateApi.setRolePermission(role.id, module.id, actionType, value)}
|
|
107
|
-
onClear={() => stateApi.clearRolePermission(role.id, module.id, actionType)}
|
|
108
|
-
/>
|
|
109
|
-
</td>
|
|
110
|
-
);
|
|
111
|
-
})}
|
|
112
|
-
</tr>
|
|
113
|
-
))}
|
|
114
|
-
</tbody>
|
|
115
|
-
</table>
|
|
116
|
-
</div>
|
|
117
|
-
</div>
|
|
118
|
-
);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
export { RbacModuleTable };
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { DownloadIcon, RotateCcwIcon } from "lucide-react";
|
|
4
|
-
import { useTranslations } from "next-intl";
|
|
5
|
-
import { Badge, Button } from "../../../shadcnui";
|
|
6
|
-
|
|
7
|
-
interface RbacToolbarProps {
|
|
8
|
-
isDirty: boolean;
|
|
9
|
-
onGenerate: () => void;
|
|
10
|
-
onReset: () => void;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export default function RbacToolbar({ isDirty, onGenerate, onReset }: RbacToolbarProps) {
|
|
14
|
-
const t = useTranslations();
|
|
15
|
-
|
|
16
|
-
return (
|
|
17
|
-
<div className="flex items-center justify-between rounded-lg border bg-card px-4 py-3">
|
|
18
|
-
<div className="flex items-center gap-3">
|
|
19
|
-
<h2 className="text-lg font-semibold">{t("rbac.title")}</h2>
|
|
20
|
-
{isDirty && (
|
|
21
|
-
<Badge variant="outline" className="border-amber-400 text-amber-600">
|
|
22
|
-
{t("rbac.unsaved_changes")}
|
|
23
|
-
</Badge>
|
|
24
|
-
)}
|
|
25
|
-
</div>
|
|
26
|
-
<div className="flex items-center gap-2">
|
|
27
|
-
<Button variant="outline" size="sm" onClick={onReset} disabled={!isDirty} className="gap-1">
|
|
28
|
-
<RotateCcwIcon className="h-3.5 w-3.5" />
|
|
29
|
-
{t("rbac.reset")}
|
|
30
|
-
</Button>
|
|
31
|
-
<Button size="sm" onClick={onGenerate} disabled={!isDirty} className="gap-1">
|
|
32
|
-
<DownloadIcon className="h-3.5 w-3.5" />
|
|
33
|
-
{t("rbac.generate_migration")}
|
|
34
|
-
</Button>
|
|
35
|
-
</div>
|
|
36
|
-
</div>
|
|
37
|
-
);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export { RbacToolbar };
|
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { renderHook, act } from "@testing-library/react";
|
|
3
|
-
import { useRbacState } from "./useRbacState";
|
|
4
|
-
import { FeatureInterface } from "../../feature";
|
|
5
|
-
import { RoleInterface } from "../../role";
|
|
6
|
-
import { PermissionMappingInterface } from "../data/PermissionMappingInterface";
|
|
7
|
-
import { ModulePathsInterface } from "../data/ModulePathsInterface";
|
|
8
|
-
import { ModuleInterface } from "../../module";
|
|
9
|
-
|
|
10
|
-
const mockModule: ModuleInterface = {
|
|
11
|
-
id: "mod-1",
|
|
12
|
-
type: "modules",
|
|
13
|
-
included: [],
|
|
14
|
-
createdAt: new Date(),
|
|
15
|
-
updatedAt: new Date(),
|
|
16
|
-
identifier: "pipelines",
|
|
17
|
-
name: "pipelines",
|
|
18
|
-
permissions: { create: true, read: true, update: true, delete: false },
|
|
19
|
-
} as ModuleInterface;
|
|
20
|
-
|
|
21
|
-
const mockFeatures: FeatureInterface[] = [
|
|
22
|
-
{
|
|
23
|
-
id: "feat-1",
|
|
24
|
-
type: "features",
|
|
25
|
-
included: [],
|
|
26
|
-
createdAt: new Date(),
|
|
27
|
-
updatedAt: new Date(),
|
|
28
|
-
identifier: "CRM",
|
|
29
|
-
name: "CRM",
|
|
30
|
-
isCore: false,
|
|
31
|
-
modules: [mockModule],
|
|
32
|
-
} as FeatureInterface,
|
|
33
|
-
];
|
|
34
|
-
|
|
35
|
-
const mockRoles: RoleInterface[] = [
|
|
36
|
-
{
|
|
37
|
-
id: "role-1",
|
|
38
|
-
type: "roles",
|
|
39
|
-
included: [],
|
|
40
|
-
createdAt: new Date(),
|
|
41
|
-
updatedAt: new Date(),
|
|
42
|
-
name: "Manager",
|
|
43
|
-
description: "",
|
|
44
|
-
isSelectable: true,
|
|
45
|
-
requiredFeature: undefined,
|
|
46
|
-
} as unknown as RoleInterface,
|
|
47
|
-
{
|
|
48
|
-
id: "role-2",
|
|
49
|
-
type: "roles",
|
|
50
|
-
included: [],
|
|
51
|
-
createdAt: new Date(),
|
|
52
|
-
updatedAt: new Date(),
|
|
53
|
-
name: "Viewer",
|
|
54
|
-
description: "",
|
|
55
|
-
isSelectable: true,
|
|
56
|
-
requiredFeature: undefined,
|
|
57
|
-
} as unknown as RoleInterface,
|
|
58
|
-
];
|
|
59
|
-
|
|
60
|
-
const mockPermissionMappings: PermissionMappingInterface[] = [];
|
|
61
|
-
|
|
62
|
-
const mockModulePaths: ModulePathsInterface[] = [
|
|
63
|
-
{
|
|
64
|
-
id: "mod-1",
|
|
65
|
-
type: "module-paths",
|
|
66
|
-
included: [],
|
|
67
|
-
createdAt: new Date(),
|
|
68
|
-
updatedAt: new Date(),
|
|
69
|
-
moduleId: "mod-1",
|
|
70
|
-
paths: ["owner", "company.user"],
|
|
71
|
-
} as unknown as ModulePathsInterface,
|
|
72
|
-
];
|
|
73
|
-
|
|
74
|
-
function initHook() {
|
|
75
|
-
const { result } = renderHook(() => useRbacState());
|
|
76
|
-
act(() => {
|
|
77
|
-
result.current.init(mockFeatures, mockRoles, mockPermissionMappings, mockModulePaths);
|
|
78
|
-
});
|
|
79
|
-
return result;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
describe("useRbacState", () => {
|
|
83
|
-
describe("Scenario: Initialize state from fetched data", () => {
|
|
84
|
-
it("should initialize with features and roles", () => {
|
|
85
|
-
const result = initHook();
|
|
86
|
-
|
|
87
|
-
expect(result.current.original).not.toBeNull();
|
|
88
|
-
expect(result.current.original!.features).toHaveLength(1);
|
|
89
|
-
expect(result.current.original!.features[0].name).toBe("CRM");
|
|
90
|
-
expect(result.current.isDirty).toBe(false);
|
|
91
|
-
});
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
describe("Scenario: Set module default permission", () => {
|
|
95
|
-
it("should update default permission and mark as dirty", () => {
|
|
96
|
-
const result = initHook();
|
|
97
|
-
|
|
98
|
-
act(() => {
|
|
99
|
-
result.current.setModuleDefaultPermission("mod-1", "delete", true);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
expect(result.current.getModuleDefaultPermission("mod-1", "delete")).toBe(true);
|
|
103
|
-
expect(result.current.isDirty).toBe(true);
|
|
104
|
-
});
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
describe("Scenario: Set role-specific permission override", () => {
|
|
108
|
-
it("should set role permission for a specific module and action", () => {
|
|
109
|
-
const result = initHook();
|
|
110
|
-
|
|
111
|
-
act(() => {
|
|
112
|
-
result.current.setRolePermission("role-1", "mod-1", "create", false);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
expect(result.current.getRolePermission("role-1", "mod-1", "create")).toBe(false);
|
|
116
|
-
expect(result.current.isDirty).toBe(true);
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
it("should support string path values for role permissions", () => {
|
|
120
|
-
const result = initHook();
|
|
121
|
-
|
|
122
|
-
act(() => {
|
|
123
|
-
result.current.setRolePermission("role-2", "mod-1", "update", "owner");
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
expect(result.current.getRolePermission("role-2", "mod-1", "update")).toBe("owner");
|
|
127
|
-
});
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
describe("Scenario: Clear role permission (inherit from default)", () => {
|
|
131
|
-
it("should clear a role permission so it inherits from default", () => {
|
|
132
|
-
const result = initHook();
|
|
133
|
-
|
|
134
|
-
act(() => {
|
|
135
|
-
result.current.setRolePermission("role-1", "mod-1", "create", false);
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
act(() => {
|
|
139
|
-
result.current.clearRolePermission("role-1", "mod-1", "create");
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
expect(result.current.getRolePermission("role-1", "mod-1", "create")).toBeNull();
|
|
143
|
-
});
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
describe("Scenario: Reset to initial state", () => {
|
|
147
|
-
it("should reset all changes and clear dirty flag", () => {
|
|
148
|
-
const result = initHook();
|
|
149
|
-
|
|
150
|
-
act(() => {
|
|
151
|
-
result.current.setModuleDefaultPermission("mod-1", "delete", true);
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
expect(result.current.isDirty).toBe(true);
|
|
155
|
-
|
|
156
|
-
act(() => {
|
|
157
|
-
result.current.reset();
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
expect(result.current.isDirty).toBe(false);
|
|
161
|
-
expect(result.current.getModuleDefaultPermission("mod-1", "delete")).toBe(false);
|
|
162
|
-
});
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
describe("Scenario: Get effective configuration for migration", () => {
|
|
166
|
-
it("should return complete configuration with all changes applied", () => {
|
|
167
|
-
const result = initHook();
|
|
168
|
-
|
|
169
|
-
act(() => {
|
|
170
|
-
result.current.setRolePermission("role-1", "mod-1", "delete", true);
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
const config = result.current.getEffectiveConfiguration();
|
|
174
|
-
expect(config).not.toBeNull();
|
|
175
|
-
expect(config!.features).toHaveLength(1);
|
|
176
|
-
expect(config!.roles).toHaveLength(2);
|
|
177
|
-
expect(config!.rolePermissionsMap).toBeInstanceOf(Map);
|
|
178
|
-
});
|
|
179
|
-
});
|
|
180
|
-
});
|