mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 22:27:22 +00:00
feat: role list section completed with delete
This commit is contained in:
@@ -475,6 +475,7 @@ export const PROJECT_USERS = {
|
|||||||
},
|
},
|
||||||
GET_USER_MEMBERSHIP: {
|
GET_USER_MEMBERSHIP: {
|
||||||
workspaceId: "The ID of the project to get memberships from.",
|
workspaceId: "The ID of the project to get memberships from.",
|
||||||
|
membershipId: "The ID of the user's project membership.",
|
||||||
username: "The username to get project membership of. Email is the default username."
|
username: "The username to get project membership of. Email is the default username."
|
||||||
},
|
},
|
||||||
UPDATE_USER_MEMBERSHIP: {
|
UPDATE_USER_MEMBERSHIP: {
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.omit({ createdAt: true, updatedAt: true })
|
.omit({ updatedAt: true })
|
||||||
.array()
|
.array()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -74,6 +74,64 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/:workspaceId/memberships/:membershipId",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
description: "Return project user membership",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
params: z.object({
|
||||||
|
workspaceId: z.string().min(1).trim().describe(PROJECT_USERS.GET_USER_MEMBERSHIP.workspaceId),
|
||||||
|
membershipId: z.string().min(1).trim().describe(PROJECT_USERS.GET_USER_MEMBERSHIP.membershipId)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
membership: ProjectMembershipsSchema.extend({
|
||||||
|
user: UsersSchema.pick({
|
||||||
|
email: true,
|
||||||
|
firstName: true,
|
||||||
|
lastName: true,
|
||||||
|
id: true
|
||||||
|
}).merge(UserEncryptionKeysSchema.pick({ publicKey: true })),
|
||||||
|
roles: z.array(
|
||||||
|
z.object({
|
||||||
|
id: z.string(),
|
||||||
|
role: z.string(),
|
||||||
|
customRoleId: z.string().optional().nullable(),
|
||||||
|
customRoleName: z.string().optional().nullable(),
|
||||||
|
customRoleSlug: z.string().optional().nullable(),
|
||||||
|
isTemporary: z.boolean(),
|
||||||
|
temporaryMode: z.string().optional().nullable(),
|
||||||
|
temporaryRange: z.string().nullable().optional(),
|
||||||
|
temporaryAccessStartTime: z.date().nullable().optional(),
|
||||||
|
temporaryAccessEndTime: z.date().nullable().optional()
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}).omit({ updatedAt: true })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const membership = await server.services.projectMembership.getProjectMembershipById({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
projectId: req.params.workspaceId,
|
||||||
|
id: req.params.membershipId
|
||||||
|
});
|
||||||
|
return { membership };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "POST",
|
method: "POST",
|
||||||
url: "/:workspaceId/memberships/details",
|
url: "/:workspaceId/memberships/details",
|
||||||
|
|||||||
@@ -158,6 +158,7 @@ export const groupProjectDALFactory = (db: TDbClient) => {
|
|||||||
)
|
)
|
||||||
.select(
|
.select(
|
||||||
db.ref("id").withSchema(TableName.UserGroupMembership),
|
db.ref("id").withSchema(TableName.UserGroupMembership),
|
||||||
|
db.ref("createdAt").withSchema(TableName.UserGroupMembership),
|
||||||
db.ref("isGhost").withSchema(TableName.Users),
|
db.ref("isGhost").withSchema(TableName.Users),
|
||||||
db.ref("username").withSchema(TableName.Users),
|
db.ref("username").withSchema(TableName.Users),
|
||||||
db.ref("email").withSchema(TableName.Users),
|
db.ref("email").withSchema(TableName.Users),
|
||||||
@@ -181,7 +182,18 @@ export const groupProjectDALFactory = (db: TDbClient) => {
|
|||||||
|
|
||||||
const members = sqlNestRelationships({
|
const members = sqlNestRelationships({
|
||||||
data: docs,
|
data: docs,
|
||||||
parentMapper: ({ email, firstName, username, lastName, publicKey, isGhost, id, userId, projectName }) => ({
|
parentMapper: ({
|
||||||
|
email,
|
||||||
|
firstName,
|
||||||
|
username,
|
||||||
|
lastName,
|
||||||
|
publicKey,
|
||||||
|
isGhost,
|
||||||
|
id,
|
||||||
|
userId,
|
||||||
|
projectName,
|
||||||
|
createdAt
|
||||||
|
}) => ({
|
||||||
isGroupMember: true,
|
isGroupMember: true,
|
||||||
id,
|
id,
|
||||||
userId,
|
userId,
|
||||||
@@ -190,7 +202,8 @@ export const groupProjectDALFactory = (db: TDbClient) => {
|
|||||||
id: projectId,
|
id: projectId,
|
||||||
name: projectName
|
name: projectName
|
||||||
},
|
},
|
||||||
user: { email, username, firstName, lastName, id: userId, publicKey, isGhost }
|
user: { email, username, firstName, lastName, id: userId, publicKey, isGhost },
|
||||||
|
createdAt
|
||||||
}),
|
}),
|
||||||
key: "id",
|
key: "id",
|
||||||
childrenMapper: [
|
childrenMapper: [
|
||||||
|
|||||||
@@ -11,7 +11,10 @@ export const projectMembershipDALFactory = (db: TDbClient) => {
|
|||||||
const projectMemberOrm = ormify(db, TableName.ProjectMembership);
|
const projectMemberOrm = ormify(db, TableName.ProjectMembership);
|
||||||
|
|
||||||
// special query
|
// special query
|
||||||
const findAllProjectMembers = async (projectId: string, filter: { usernames?: string[]; username?: string } = {}) => {
|
const findAllProjectMembers = async (
|
||||||
|
projectId: string,
|
||||||
|
filter: { usernames?: string[]; username?: string; id?: string } = {}
|
||||||
|
) => {
|
||||||
try {
|
try {
|
||||||
const docs = await db
|
const docs = await db
|
||||||
.replicaNode()(TableName.ProjectMembership)
|
.replicaNode()(TableName.ProjectMembership)
|
||||||
@@ -25,6 +28,9 @@ export const projectMembershipDALFactory = (db: TDbClient) => {
|
|||||||
if (filter.username) {
|
if (filter.username) {
|
||||||
void qb.where("username", filter.username);
|
void qb.where("username", filter.username);
|
||||||
}
|
}
|
||||||
|
if (filter.id) {
|
||||||
|
void qb.where(`${TableName.ProjectMembership}.id`, filter.id);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.join<TUserEncryptionKeys>(
|
.join<TUserEncryptionKeys>(
|
||||||
TableName.UserEncryptionKey,
|
TableName.UserEncryptionKey,
|
||||||
@@ -43,6 +49,7 @@ export const projectMembershipDALFactory = (db: TDbClient) => {
|
|||||||
)
|
)
|
||||||
.select(
|
.select(
|
||||||
db.ref("id").withSchema(TableName.ProjectMembership),
|
db.ref("id").withSchema(TableName.ProjectMembership),
|
||||||
|
db.ref("createdAt").withSchema(TableName.ProjectMembership),
|
||||||
db.ref("isGhost").withSchema(TableName.Users),
|
db.ref("isGhost").withSchema(TableName.Users),
|
||||||
db.ref("username").withSchema(TableName.Users),
|
db.ref("username").withSchema(TableName.Users),
|
||||||
db.ref("email").withSchema(TableName.Users),
|
db.ref("email").withSchema(TableName.Users),
|
||||||
@@ -66,7 +73,18 @@ export const projectMembershipDALFactory = (db: TDbClient) => {
|
|||||||
|
|
||||||
const members = sqlNestRelationships({
|
const members = sqlNestRelationships({
|
||||||
data: docs,
|
data: docs,
|
||||||
parentMapper: ({ email, firstName, username, lastName, publicKey, isGhost, id, userId, projectName }) => ({
|
parentMapper: ({
|
||||||
|
email,
|
||||||
|
firstName,
|
||||||
|
username,
|
||||||
|
lastName,
|
||||||
|
publicKey,
|
||||||
|
isGhost,
|
||||||
|
id,
|
||||||
|
userId,
|
||||||
|
projectName,
|
||||||
|
createdAt
|
||||||
|
}) => ({
|
||||||
id,
|
id,
|
||||||
userId,
|
userId,
|
||||||
projectId,
|
projectId,
|
||||||
@@ -74,7 +92,8 @@ export const projectMembershipDALFactory = (db: TDbClient) => {
|
|||||||
project: {
|
project: {
|
||||||
id: projectId,
|
id: projectId,
|
||||||
name: projectName
|
name: projectName
|
||||||
}
|
},
|
||||||
|
createdAt
|
||||||
}),
|
}),
|
||||||
key: "id",
|
key: "id",
|
||||||
childrenMapper: [
|
childrenMapper: [
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import {
|
|||||||
TAddUsersToWorkspaceDTO,
|
TAddUsersToWorkspaceDTO,
|
||||||
TDeleteProjectMembershipOldDTO,
|
TDeleteProjectMembershipOldDTO,
|
||||||
TDeleteProjectMembershipsDTO,
|
TDeleteProjectMembershipsDTO,
|
||||||
|
TGetProjectMembershipByIdDTO,
|
||||||
TGetProjectMembershipByUsernameDTO,
|
TGetProjectMembershipByUsernameDTO,
|
||||||
TGetProjectMembershipDTO,
|
TGetProjectMembershipDTO,
|
||||||
TLeaveProjectDTO,
|
TLeaveProjectDTO,
|
||||||
@@ -133,6 +134,28 @@ export const projectMembershipServiceFactory = ({
|
|||||||
return membership;
|
return membership;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getProjectMembershipById = async ({
|
||||||
|
actorId,
|
||||||
|
actor,
|
||||||
|
actorOrgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
projectId,
|
||||||
|
id
|
||||||
|
}: TGetProjectMembershipByIdDTO) => {
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
|
||||||
|
|
||||||
|
const [membership] = await projectMembershipDAL.findAllProjectMembers(projectId, { id });
|
||||||
|
if (!membership) throw new NotFoundError({ message: `Project membership not found for user ${id}` });
|
||||||
|
return membership;
|
||||||
|
};
|
||||||
|
|
||||||
const addUsersToProject = async ({
|
const addUsersToProject = async ({
|
||||||
projectId,
|
projectId,
|
||||||
actorId,
|
actorId,
|
||||||
@@ -487,6 +510,7 @@ export const projectMembershipServiceFactory = ({
|
|||||||
deleteProjectMemberships,
|
deleteProjectMemberships,
|
||||||
deleteProjectMembership, // TODO: Remove this
|
deleteProjectMembership, // TODO: Remove this
|
||||||
addUsersToProject,
|
addUsersToProject,
|
||||||
leaveProject
|
leaveProject,
|
||||||
|
getProjectMembershipById
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ export type TGetProjectMembershipByUsernameDTO = {
|
|||||||
username: string;
|
username: string;
|
||||||
} & TProjectPermission;
|
} & TProjectPermission;
|
||||||
|
|
||||||
|
export type TGetProjectMembershipByIdDTO = {
|
||||||
|
id: string;
|
||||||
|
} & TProjectPermission;
|
||||||
|
|
||||||
export type TUpdateProjectMembershipDTO = {
|
export type TUpdateProjectMembershipDTO = {
|
||||||
membershipId: string;
|
membershipId: string;
|
||||||
roles: (
|
roles: (
|
||||||
|
|||||||
@@ -19,14 +19,14 @@ export const useCreateProjectRole = () => {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation<TProjectRole, {}, TCreateProjectRoleDTO>({
|
return useMutation<TProjectRole, {}, TCreateProjectRoleDTO>({
|
||||||
mutationFn: async ({ projectSlug, ...dto }: TCreateProjectRoleDTO) => {
|
mutationFn: async ({ projectId, ...dto }: TCreateProjectRoleDTO) => {
|
||||||
const {
|
const {
|
||||||
data: { role }
|
data: { role }
|
||||||
} = await apiRequest.post(`/api/v2/workspace/${projectSlug}/roles`, dto);
|
} = await apiRequest.post(`/api/v2/workspace/${projectId}/roles`, dto);
|
||||||
return role;
|
return role;
|
||||||
},
|
},
|
||||||
onSuccess: (_, { projectSlug }) => {
|
onSuccess: (_, { projectId }) => {
|
||||||
queryClient.invalidateQueries(roleQueryKeys.getProjectRoles(projectSlug));
|
queryClient.invalidateQueries(roleQueryKeys.getProjectRoles(projectId));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -35,14 +35,14 @@ export const useUpdateProjectRole = () => {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation<TProjectRole, {}, TUpdateProjectRoleDTO>({
|
return useMutation<TProjectRole, {}, TUpdateProjectRoleDTO>({
|
||||||
mutationFn: async ({ id, projectSlug, ...dto }: TUpdateProjectRoleDTO) => {
|
mutationFn: async ({ id, projectId, ...dto }: TUpdateProjectRoleDTO) => {
|
||||||
const {
|
const {
|
||||||
data: { role }
|
data: { role }
|
||||||
} = await apiRequest.patch(`/api/v2/workspace/${projectSlug}/roles/${id}`, dto);
|
} = await apiRequest.patch(`/api/v2/workspace/${projectId}/roles/${id}`, dto);
|
||||||
return role;
|
return role;
|
||||||
},
|
},
|
||||||
onSuccess: (_, { projectSlug }) => {
|
onSuccess: (_, { projectId }) => {
|
||||||
queryClient.invalidateQueries(roleQueryKeys.getProjectRoles(projectSlug));
|
queryClient.invalidateQueries(roleQueryKeys.getProjectRoles(projectId));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -50,14 +50,14 @@ export const useUpdateProjectRole = () => {
|
|||||||
export const useDeleteProjectRole = () => {
|
export const useDeleteProjectRole = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
return useMutation<TProjectRole, {}, TDeleteProjectRoleDTO>({
|
return useMutation<TProjectRole, {}, TDeleteProjectRoleDTO>({
|
||||||
mutationFn: async ({ projectSlug, id }: TDeleteProjectRoleDTO) => {
|
mutationFn: async ({ projectId, id }: TDeleteProjectRoleDTO) => {
|
||||||
const {
|
const {
|
||||||
data: { role }
|
data: { role }
|
||||||
} = await apiRequest.delete(`/api/v2/workspace/${projectSlug}/roles/${id}`);
|
} = await apiRequest.delete(`/api/v2/workspace/${projectId}/roles/${id}`);
|
||||||
return role;
|
return role;
|
||||||
},
|
},
|
||||||
onSuccess: (_, { projectSlug }) => {
|
onSuccess: (_, { projectId }) => {
|
||||||
queryClient.invalidateQueries(roleQueryKeys.getProjectRoles(projectSlug));
|
queryClient.invalidateQueries(roleQueryKeys.getProjectRoles(projectId));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ const glob: JsInterpreter<FieldCondition<string>> = (node, object, context) => {
|
|||||||
const conditionsMatcher = buildMongoQueryMatcher({ $glob }, { glob });
|
const conditionsMatcher = buildMongoQueryMatcher({ $glob }, { glob });
|
||||||
|
|
||||||
export const roleQueryKeys = {
|
export const roleQueryKeys = {
|
||||||
getProjectRoles: (projectSlug: string) => ["roles", { projectSlug }] as const,
|
getProjectRoles: (projectId: string) => ["roles", { projectSlug: projectId }] as const,
|
||||||
getProjectRoleBySlug: (projectSlug: string, roleSlug: string) =>
|
getProjectRoleBySlug: (projectSlug: string, roleSlug: string) =>
|
||||||
["roles", { projectSlug, roleSlug }] as const,
|
["roles", { projectSlug, roleSlug }] as const,
|
||||||
getOrgRoles: (orgId: string) => ["org-roles", { orgId }] as const,
|
getOrgRoles: (orgId: string) => ["org-roles", { orgId }] as const,
|
||||||
@@ -56,11 +56,11 @@ export const getProjectRoles = async (projectId: string) => {
|
|||||||
return data.roles;
|
return data.roles;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useGetProjectRoles = (projectSlug: string) =>
|
export const useGetProjectRoles = (projectId: string) =>
|
||||||
useQuery({
|
useQuery({
|
||||||
queryKey: roleQueryKeys.getProjectRoles(projectSlug),
|
queryKey: roleQueryKeys.getProjectRoles(projectId),
|
||||||
queryFn: () => getProjectRoles(projectSlug),
|
queryFn: () => getProjectRoles(projectId),
|
||||||
enabled: Boolean(projectSlug)
|
enabled: Boolean(projectId)
|
||||||
});
|
});
|
||||||
|
|
||||||
export const useGetProjectRoleBySlug = (projectSlug: string, roleSlug: string) =>
|
export const useGetProjectRoleBySlug = (projectSlug: string, roleSlug: string) =>
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ export type TDeleteOrgRoleDTO = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type TCreateProjectRoleDTO = {
|
export type TCreateProjectRoleDTO = {
|
||||||
projectSlug: string;
|
projectId: string;
|
||||||
name: string;
|
name: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
@@ -80,11 +80,11 @@ export type TCreateProjectRoleDTO = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type TUpdateProjectRoleDTO = {
|
export type TUpdateProjectRoleDTO = {
|
||||||
projectSlug: string;
|
projectId: string;
|
||||||
id: string;
|
id: string;
|
||||||
} & Partial<Omit<TCreateProjectRoleDTO, "orgId">>;
|
} & Partial<Omit<TCreateProjectRoleDTO, "orgId">>;
|
||||||
|
|
||||||
export type TDeleteProjectRoleDTO = {
|
export type TDeleteProjectRoleDTO = {
|
||||||
projectSlug: string;
|
projectId: string;
|
||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { UserWsKeyPair } from "../keys/types";
|
import { UserWsKeyPair } from "../keys/types";
|
||||||
|
import { ProjectUserMembershipTemporaryMode } from "../workspace/types";
|
||||||
|
|
||||||
export enum AuthMethod {
|
export enum AuthMethod {
|
||||||
EMAIL = "email",
|
EMAIL = "email",
|
||||||
@@ -85,6 +86,7 @@ export type TWorkspaceUser = {
|
|||||||
id: string;
|
id: string;
|
||||||
publicKey: string;
|
publicKey: string;
|
||||||
};
|
};
|
||||||
|
createdAt: string;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
isGroupMember: boolean;
|
isGroupMember: boolean;
|
||||||
project: {
|
project: {
|
||||||
@@ -114,7 +116,7 @@ export type TWorkspaceUser = {
|
|||||||
customRoleSlug: string;
|
customRoleSlug: string;
|
||||||
isTemporary: true;
|
isTemporary: true;
|
||||||
temporaryRange: string;
|
temporaryRange: string;
|
||||||
temporaryMode: string;
|
temporaryMode: ProjectUserMembershipTemporaryMode;
|
||||||
temporaryAccessEndTime: string;
|
temporaryAccessEndTime: string;
|
||||||
temporaryAccessStartTime: string;
|
temporaryAccessStartTime: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ export {
|
|||||||
useUpdateIdentityWorkspaceRole,
|
useUpdateIdentityWorkspaceRole,
|
||||||
useUpdateUserWorkspaceRole,
|
useUpdateUserWorkspaceRole,
|
||||||
useUpdateWsEnvironment,
|
useUpdateWsEnvironment,
|
||||||
useUpgradeProject
|
useUpgradeProject,
|
||||||
|
useGetWorkspaceUserDetails
|
||||||
} from "./queries";
|
} from "./queries";
|
||||||
export { workspaceKeys } from "./query-keys";
|
export { workspaceKeys } from "./query-keys";
|
||||||
|
|||||||
@@ -365,6 +365,21 @@ export const useGetWorkspaceUsers = (workspaceId: string, includeGroupMembers?:
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useGetWorkspaceUserDetails = (workspaceId: string, membershipId: string) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: workspaceKeys.getWorkspaceUserDetails(workspaceId, membershipId),
|
||||||
|
queryFn: async () => {
|
||||||
|
const {
|
||||||
|
data: { membership }
|
||||||
|
} = await apiRequest.get<{ membership: TWorkspaceUser }>(
|
||||||
|
`/api/v1/workspace/${workspaceId}/memberships/${membershipId}`
|
||||||
|
);
|
||||||
|
return membership;
|
||||||
|
},
|
||||||
|
enabled: Boolean(workspaceId) && Boolean(membershipId)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const useDeleteUserFromWorkspace = () => {
|
export const useDeleteUserFromWorkspace = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ export const workspaceKeys = {
|
|||||||
getWorkspaceAuditLogs: (workspaceId: string) =>
|
getWorkspaceAuditLogs: (workspaceId: string) =>
|
||||||
[{ workspaceId }, "workspace-audit-logs"] as const,
|
[{ workspaceId }, "workspace-audit-logs"] as const,
|
||||||
getWorkspaceUsers: (workspaceId: string) => [{ workspaceId }, "workspace-users"] as const,
|
getWorkspaceUsers: (workspaceId: string) => [{ workspaceId }, "workspace-users"] as const,
|
||||||
|
getWorkspaceUserDetails: (workspaceId: string, membershipId: string) =>
|
||||||
|
[{ workspaceId, membershipId }, "workspace-user-details"] as const,
|
||||||
getWorkspaceIdentityMemberships: (workspaceId: string) =>
|
getWorkspaceIdentityMemberships: (workspaceId: string) =>
|
||||||
[{ workspaceId }, "workspace-identity-memberships"] as const,
|
[{ workspaceId }, "workspace-identity-memberships"] as const,
|
||||||
// allows invalidation using above key without knowing params
|
// allows invalidation using above key without knowing params
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import Head from "next/head";
|
||||||
|
import { MemberDetailsPage } from "@app/views/Project/MemberDetailsPage";
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Head>
|
||||||
|
<title>{t("common.head-title", { title: t("settings.members.title") })}</title>
|
||||||
|
<link rel="icon" href="/infisical.ico" />
|
||||||
|
</Head>
|
||||||
|
<MemberDetailsPage />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Page.requireAuth = true;
|
||||||
+2
-2
@@ -5,7 +5,7 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
import { Button, FormControl, Modal, ModalContent, Select, SelectItem } from "@app/components/v2";
|
import { Button, FormControl, Modal, ModalContent, Select, SelectItem } from "@app/components/v2";
|
||||||
import { useOrganization,useWorkspace } from "@app/context";
|
import { useOrganization, useWorkspace } from "@app/context";
|
||||||
import {
|
import {
|
||||||
useAddIdentityToWorkspace,
|
useAddIdentityToWorkspace,
|
||||||
useGetIdentityProjectMemberships,
|
useGetIdentityProjectMemberships,
|
||||||
@@ -50,7 +50,7 @@ export const IdentityAddToProjectModal = ({ identityId, popUp, handlePopUpToggle
|
|||||||
const projectId = watch("projectId");
|
const projectId = watch("projectId");
|
||||||
const { data: projectMemberships } = useGetIdentityProjectMemberships(identityId);
|
const { data: projectMemberships } = useGetIdentityProjectMemberships(identityId);
|
||||||
const { data: project } = useGetWorkspaceById(projectId);
|
const { data: project } = useGetWorkspaceById(projectId);
|
||||||
const { data: roles } = useGetProjectRoles(project?.slug ?? "");
|
const { data: roles } = useGetProjectRoles(project?.id ?? "");
|
||||||
|
|
||||||
const filteredWorkspaces = useMemo(() => {
|
const filteredWorkspaces = useMemo(() => {
|
||||||
const wsWorkspaceIds = new Map();
|
const wsWorkspaceIds = new Map();
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import { Button } from "@app/components/v2";
|
||||||
|
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
|
||||||
|
import { withProjectPermission } from "@app/hoc";
|
||||||
|
import { useGetWorkspaceUserDetails } from "@app/hooks/api";
|
||||||
|
import { faChevronLeft, faUser } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { format } from "date-fns";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
import { MemberRoleDetailsSection } from "./components/MemberRoleDetailsSection";
|
||||||
|
|
||||||
|
export const MemberDetailsPage = withProjectPermission(
|
||||||
|
() => {
|
||||||
|
const router = useRouter();
|
||||||
|
const { currentWorkspace } = useWorkspace();
|
||||||
|
|
||||||
|
const workspaceId = currentWorkspace?.id || "";
|
||||||
|
const membershipId = router.query.membershipId as string;
|
||||||
|
|
||||||
|
const { data: membershipDetails, isLoading: isMembershipDetailsLoading } =
|
||||||
|
useGetWorkspaceUserDetails(workspaceId, membershipId);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto flex max-w-7xl flex-col justify-between bg-bunker-800 p-6 text-white">
|
||||||
|
<div className="mb-4">
|
||||||
|
<Button
|
||||||
|
variant="link"
|
||||||
|
type="submit"
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
|
||||||
|
onClick={() => {
|
||||||
|
router.push(`/project/${workspaceId}/members`);
|
||||||
|
}}
|
||||||
|
className="mb-4"
|
||||||
|
>
|
||||||
|
Project Access Control
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className="mb-4">
|
||||||
|
<div className="w-full rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
|
<div className="mb-4 flex items-center justify-between ">
|
||||||
|
<h3 className="text-lg font-semibold text-mineshaft-100">Project User Management</h3>
|
||||||
|
<div>
|
||||||
|
<Button colorSchema="danger" variant="outline_bg" size="xs">
|
||||||
|
Remove
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="">
|
||||||
|
{membershipDetails && (
|
||||||
|
<p className="capitalize">
|
||||||
|
{membershipDetails.user.firstName || membershipDetails.user.lastName
|
||||||
|
? `${membershipDetails.user.firstName} ${membershipDetails.user.lastName}`
|
||||||
|
: "-"}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
<div className="mt-2 text-sm text-gray-400">
|
||||||
|
Joined on{" "}
|
||||||
|
{membershipDetails?.createdAt &&
|
||||||
|
format(new Date(membershipDetails?.createdAt || ""), "yyyy-MM-dd")}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<MemberRoleDetailsSection
|
||||||
|
membershipDetails={membershipDetails}
|
||||||
|
isMembershipDetailsLoading={isMembershipDetailsLoading}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: ProjectPermissionActions.Read,
|
||||||
|
subject: ProjectPermissionSub.Member
|
||||||
|
}
|
||||||
|
);
|
||||||
+144
@@ -0,0 +1,144 @@
|
|||||||
|
import { createNotification } from "@app/components/notifications";
|
||||||
|
import {
|
||||||
|
DeleteActionModal,
|
||||||
|
IconButton,
|
||||||
|
TableContainer,
|
||||||
|
Td,
|
||||||
|
Table,
|
||||||
|
Tr,
|
||||||
|
Th,
|
||||||
|
THead,
|
||||||
|
TableSkeleton,
|
||||||
|
EmptyState,
|
||||||
|
TBody,
|
||||||
|
Tooltip
|
||||||
|
} from "@app/components/v2";
|
||||||
|
import { useUser, useWorkspace } from "@app/context";
|
||||||
|
import { usePopUp } from "@app/hooks";
|
||||||
|
import {
|
||||||
|
useDeleteProjectRole,
|
||||||
|
useGetWorkspaceUserDetails,
|
||||||
|
useUpdateUserWorkspaceRole
|
||||||
|
} from "@app/hooks/api";
|
||||||
|
import { TProjectRole } from "@app/hooks/api/roles/types";
|
||||||
|
import { TWorkspaceUser } from "@app/hooks/api/types";
|
||||||
|
import { faFolder, faPlus, faTrash } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
membershipDetails: TWorkspaceUser;
|
||||||
|
isMembershipDetailsLoading?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MemberRoleDetailsSection = ({
|
||||||
|
membershipDetails,
|
||||||
|
isMembershipDetailsLoading
|
||||||
|
}: Props) => {
|
||||||
|
const router = useRouter();
|
||||||
|
const { user } = useUser();
|
||||||
|
const { currentWorkspace } = useWorkspace();
|
||||||
|
const { popUp, handlePopUpOpen, handlePopUpToggle, handlePopUpClose } = usePopUp([
|
||||||
|
"deleteRole"
|
||||||
|
] as const);
|
||||||
|
const { mutateAsync: updateUserWorkspaceRole } = useUpdateUserWorkspaceRole();
|
||||||
|
|
||||||
|
const userId = user?.id;
|
||||||
|
|
||||||
|
const handleRoleDelete = async () => {
|
||||||
|
const { id } = popUp?.deleteRole?.data as TProjectRole;
|
||||||
|
try {
|
||||||
|
const updatedRole = membershipDetails?.roles?.filter((el) => el.id !== id);
|
||||||
|
await updateUserWorkspaceRole({
|
||||||
|
workspaceId: currentWorkspace?.id || "",
|
||||||
|
roles: updatedRole,
|
||||||
|
membershipId: membershipDetails.id
|
||||||
|
});
|
||||||
|
createNotification({ type: "success", text: "Successfully removed the role" });
|
||||||
|
handlePopUpClose("deleteRole");
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
createNotification({ type: "error", text: "Failed to delete role" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-full rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
|
<div className="flex items-center justify-between border-b border-mineshaft-400 pb-4">
|
||||||
|
<h3 className="text-lg font-semibold text-mineshaft-100">Project Roles</h3>
|
||||||
|
{userId !== membershipDetails?.user?.id && membershipDetails?.status !== "invited" && (
|
||||||
|
<IconButton
|
||||||
|
ariaLabel="copy icon"
|
||||||
|
variant="plain"
|
||||||
|
className="group relative"
|
||||||
|
onClick={() => {}}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faPlus} />
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="py-4">
|
||||||
|
<TableContainer>
|
||||||
|
<Table>
|
||||||
|
<THead>
|
||||||
|
<Tr>
|
||||||
|
<Th>Role</Th>
|
||||||
|
<Th>Type</Th>
|
||||||
|
<Th className="w-5" />
|
||||||
|
</Tr>
|
||||||
|
</THead>
|
||||||
|
<TBody>
|
||||||
|
{isMembershipDetailsLoading && (
|
||||||
|
<TableSkeleton columns={3} innerKey="user-project-memberships" />
|
||||||
|
)}
|
||||||
|
{!isMembershipDetailsLoading &&
|
||||||
|
membershipDetails?.roles?.map((roleDetails) => {
|
||||||
|
return (
|
||||||
|
<Tr className="group h-10" key={`user-project-membership-${roleDetails?.id}`}>
|
||||||
|
<Td>
|
||||||
|
{roleDetails.role === "custom"
|
||||||
|
? roleDetails.customRoleName
|
||||||
|
: roleDetails.role}
|
||||||
|
</Td>
|
||||||
|
<Td>{!roleDetails.isTemporary ? "permanent" : "temporary"}</Td>
|
||||||
|
<Td>
|
||||||
|
<div className="opacity-0 transition-opacity duration-300 group-hover:opacity-100">
|
||||||
|
<Tooltip content="Remove">
|
||||||
|
<IconButton
|
||||||
|
colorSchema="danger"
|
||||||
|
ariaLabel="copy icon"
|
||||||
|
variant="plain"
|
||||||
|
className="group relative"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handlePopUpOpen("deleteRole", {
|
||||||
|
id: roleDetails?.id,
|
||||||
|
slug: roleDetails?.customRoleSlug || roleDetails?.role
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faTrash} />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</TBody>
|
||||||
|
</Table>
|
||||||
|
{!isMembershipDetailsLoading && !membershipDetails?.roles?.length && (
|
||||||
|
<EmptyState title="This user has not been assigned to any projects" icon={faFolder} />
|
||||||
|
)}
|
||||||
|
</TableContainer>
|
||||||
|
</div>
|
||||||
|
<DeleteActionModal
|
||||||
|
isOpen={popUp.deleteRole.isOpen}
|
||||||
|
deleteKey="remove"
|
||||||
|
title={`Do you want to remove role ${(popUp?.deleteRole?.data as TProjectRole)?.slug}?`}
|
||||||
|
onChange={(isOpen) => handlePopUpToggle("deleteRole", isOpen)}
|
||||||
|
onDeleteApproved={() => handleRoleDelete()}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export { MemberRoleDetailsSection } from "./MemberRoleDetailsSection";
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { MemberDetailsPage } from "./MemberDetailPage";
|
||||||
+1
-2
@@ -32,12 +32,11 @@ export const GroupModal = ({ popUp, handlePopUpToggle }: Props) => {
|
|||||||
const { currentWorkspace } = useWorkspace();
|
const { currentWorkspace } = useWorkspace();
|
||||||
|
|
||||||
const orgId = currentOrg?.id || "";
|
const orgId = currentOrg?.id || "";
|
||||||
const projectSlug = currentWorkspace?.slug || "";
|
|
||||||
|
|
||||||
const { data: groups } = useGetOrganizationGroups(orgId);
|
const { data: groups } = useGetOrganizationGroups(orgId);
|
||||||
const { data: groupMemberships } = useListWorkspaceGroups(currentWorkspace?.id || "");
|
const { data: groupMemberships } = useListWorkspaceGroups(currentWorkspace?.id || "");
|
||||||
|
|
||||||
const { data: roles } = useGetProjectRoles(projectSlug);
|
const { data: roles } = useGetProjectRoles(currentWorkspace?.id || "");
|
||||||
|
|
||||||
const { mutateAsync: addGroupToWorkspaceMutateAsync } = useAddGroupToWorkspace();
|
const { mutateAsync: addGroupToWorkspaceMutateAsync } = useAddGroupToWorkspace();
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -216,9 +216,9 @@ export const GroupRoles = ({ roles = [], disableEdit = false, groupId }: TMember
|
|||||||
resolver: zodResolver(formSchema)
|
resolver: zodResolver(formSchema)
|
||||||
});
|
});
|
||||||
|
|
||||||
const projectSlug = currentWorkspace?.slug || "";
|
const { data: projectRoles, isLoading: isRolesLoading } = useGetProjectRoles(
|
||||||
|
currentWorkspace?.id ?? ""
|
||||||
const { data: projectRoles, isLoading: isRolesLoading } = useGetProjectRoles(projectSlug);
|
);
|
||||||
const userRolesGroupBySlug = groupBy(roles, ({ customRoleSlug, role }) => customRoleSlug || role);
|
const userRolesGroupBySlug = groupBy(roles, ({ customRoleSlug, role }) => customRoleSlug || role);
|
||||||
|
|
||||||
const updateGroupWorkspaceRole = useUpdateGroupWorkspaceRole();
|
const updateGroupWorkspaceRole = useUpdateGroupWorkspaceRole();
|
||||||
|
|||||||
+1
-2
@@ -42,7 +42,6 @@ export const IdentityModal = ({ popUp, handlePopUpToggle }: Props) => {
|
|||||||
|
|
||||||
const organizationId = currentOrg?.id || "";
|
const organizationId = currentOrg?.id || "";
|
||||||
const workspaceId = currentWorkspace?.id || "";
|
const workspaceId = currentWorkspace?.id || "";
|
||||||
const projectSlug = currentWorkspace?.slug || "";
|
|
||||||
|
|
||||||
const { data: identityMembershipOrgsData } = useGetIdentityMembershipOrgs({
|
const { data: identityMembershipOrgsData } = useGetIdentityMembershipOrgs({
|
||||||
organizationId,
|
organizationId,
|
||||||
@@ -59,7 +58,7 @@ export const IdentityModal = ({ popUp, handlePopUpToggle }: Props) => {
|
|||||||
data: roles,
|
data: roles,
|
||||||
isLoading: isRolesLoading,
|
isLoading: isRolesLoading,
|
||||||
isFetched: isRolesFetched
|
isFetched: isRolesFetched
|
||||||
} = useGetProjectRoles(projectSlug);
|
} = useGetProjectRoles(workspaceId);
|
||||||
|
|
||||||
const { mutateAsync: addIdentityToWorkspaceMutateAsync } = useAddIdentityToWorkspace();
|
const { mutateAsync: addIdentityToWorkspaceMutateAsync } = useAddIdentityToWorkspace();
|
||||||
|
|
||||||
|
|||||||
+1
-2
@@ -65,8 +65,7 @@ export const IdentityRbacSection = ({ identityProjectMember, onOpenUpgradeModal
|
|||||||
const { subscription } = useSubscription();
|
const { subscription } = useSubscription();
|
||||||
const { currentWorkspace } = useWorkspace();
|
const { currentWorkspace } = useWorkspace();
|
||||||
const workspaceId = currentWorkspace?.id || "";
|
const workspaceId = currentWorkspace?.id || "";
|
||||||
const projectSlug = currentWorkspace?.slug || "";
|
const { data: projectRoles, isLoading: isRolesLoading } = useGetProjectRoles(workspaceId);
|
||||||
const { data: projectRoles, isLoading: isRolesLoading } = useGetProjectRoles(projectSlug);
|
|
||||||
const { permission } = useProjectPermission();
|
const { permission } = useProjectPermission();
|
||||||
const isMemberEditDisabled = permission.cannot(
|
const isMemberEditDisabled = permission.cannot(
|
||||||
ProjectPermissionActions.Edit,
|
ProjectPermissionActions.Edit,
|
||||||
|
|||||||
+1
-1
@@ -54,7 +54,7 @@ export const AddMemberModal = ({ popUp, handlePopUpToggle }: Props) => {
|
|||||||
const { data: members } = useGetWorkspaceUsers(workspaceId);
|
const { data: members } = useGetWorkspaceUsers(workspaceId);
|
||||||
const { data: orgUsers } = useGetOrgUsers(orgId);
|
const { data: orgUsers } = useGetOrgUsers(orgId);
|
||||||
|
|
||||||
const { data: roles } = useGetProjectRoles(currentWorkspace?.slug || "");
|
const { data: roles } = useGetProjectRoles(currentWorkspace?.id || "");
|
||||||
|
|
||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
|
|||||||
+1
-2
@@ -65,8 +65,7 @@ export const MemberRbacSection = ({ projectMember, onOpenUpgradeModal }: Props)
|
|||||||
const { subscription } = useSubscription();
|
const { subscription } = useSubscription();
|
||||||
const { currentWorkspace } = useWorkspace();
|
const { currentWorkspace } = useWorkspace();
|
||||||
const workspaceId = currentWorkspace?.id || "";
|
const workspaceId = currentWorkspace?.id || "";
|
||||||
const projectSlug = currentWorkspace?.slug || "";
|
const { data: projectRoles, isLoading: isRolesLoading } = useGetProjectRoles(workspaceId);
|
||||||
const { data: projectRoles, isLoading: isRolesLoading } = useGetProjectRoles(projectSlug);
|
|
||||||
const { permission } = useProjectPermission();
|
const { permission } = useProjectPermission();
|
||||||
const isMemberEditDisabled = permission.cannot(
|
const isMemberEditDisabled = permission.cannot(
|
||||||
ProjectPermissionActions.Edit,
|
ProjectPermissionActions.Edit,
|
||||||
|
|||||||
+24
-7
@@ -7,6 +7,7 @@ import {
|
|||||||
faUsers
|
faUsers
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||||
@@ -65,6 +66,7 @@ export const MembersTable = ({ popUp, handlePopUpOpen, handlePopUpToggle }: Prop
|
|||||||
|
|
||||||
const { currentWorkspace } = useWorkspace();
|
const { currentWorkspace } = useWorkspace();
|
||||||
const { user } = useUser();
|
const { user } = useUser();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
const userId = user?.id || "";
|
const userId = user?.id || "";
|
||||||
const workspaceId = currentWorkspace?.id || "";
|
const workspaceId = currentWorkspace?.id || "";
|
||||||
@@ -111,7 +113,18 @@ export const MembersTable = ({ popUp, handlePopUpOpen, handlePopUpToggle }: Prop
|
|||||||
const email = u?.email || inviteEmail;
|
const email = u?.email || inviteEmail;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tr key={`membership-${membershipId}`} className="group w-full">
|
<Tr
|
||||||
|
key={`membership-${membershipId}`}
|
||||||
|
className="group w-full cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700"
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
onKeyDown={(evt) => {
|
||||||
|
if (evt.key === "Enter") {
|
||||||
|
router.push(`/project/${workspaceId}/members/${membershipId}`);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onClick={() => router.push(`/project/${workspaceId}/members/${membershipId}`)}
|
||||||
|
>
|
||||||
<Td>{name}</Td>
|
<Td>{name}</Td>
|
||||||
<Td>{email}</Td>
|
<Td>{email}</Td>
|
||||||
<Td>
|
<Td>
|
||||||
@@ -202,9 +215,11 @@ export const MembersTable = ({ popUp, handlePopUpOpen, handlePopUpToggle }: Prop
|
|||||||
size="sm"
|
size="sm"
|
||||||
variant="plain"
|
variant="plain"
|
||||||
ariaLabel="update-role"
|
ariaLabel="update-role"
|
||||||
onClick={() =>
|
onClick={(evt) => {
|
||||||
handlePopUpOpen("updateRole", { ...projectMember, index })
|
evt.preventDefault();
|
||||||
}
|
evt.stopPropagation();
|
||||||
|
handlePopUpOpen("updateRole", { ...projectMember, index });
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={faEdit} />
|
<FontAwesomeIcon icon={faEdit} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
@@ -226,9 +241,11 @@ export const MembersTable = ({ popUp, handlePopUpOpen, handlePopUpToggle }: Prop
|
|||||||
ariaLabel="update"
|
ariaLabel="update"
|
||||||
className="ml-4"
|
className="ml-4"
|
||||||
isDisabled={userId === u?.id || !isAllowed}
|
isDisabled={userId === u?.id || !isAllowed}
|
||||||
onClick={() =>
|
onClick={(evt) => {
|
||||||
handlePopUpOpen("removeMember", { username: u.username })
|
evt.preventDefault();
|
||||||
}
|
evt.stopPropagation();
|
||||||
|
handlePopUpOpen("removeMember", { username: u.username });
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={faTrash} />
|
<FontAwesomeIcon icon={faTrash} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|||||||
+2
-3
@@ -34,10 +34,9 @@ export const ProjectRoleList = () => {
|
|||||||
"deleteRole"
|
"deleteRole"
|
||||||
] as const);
|
] as const);
|
||||||
const { currentWorkspace } = useWorkspace();
|
const { currentWorkspace } = useWorkspace();
|
||||||
const projectSlug = currentWorkspace?.slug || "";
|
|
||||||
const projectId = currentWorkspace?.id || "";
|
const projectId = currentWorkspace?.id || "";
|
||||||
|
|
||||||
const { data: roles, isLoading: isRolesLoading } = useGetProjectRoles(projectSlug);
|
const { data: roles, isLoading: isRolesLoading } = useGetProjectRoles(projectId);
|
||||||
|
|
||||||
const { mutateAsync: deleteRole } = useDeleteProjectRole();
|
const { mutateAsync: deleteRole } = useDeleteProjectRole();
|
||||||
|
|
||||||
@@ -45,7 +44,7 @@ export const ProjectRoleList = () => {
|
|||||||
const { id } = popUp?.deleteRole?.data as TProjectRole;
|
const { id } = popUp?.deleteRole?.data as TProjectRole;
|
||||||
try {
|
try {
|
||||||
await deleteRole({
|
await deleteRole({
|
||||||
projectSlug,
|
projectId,
|
||||||
id
|
id
|
||||||
});
|
});
|
||||||
createNotification({ type: "success", text: "Successfully removed the role" });
|
createNotification({ type: "success", text: "Successfully removed the role" });
|
||||||
|
|||||||
Reference in New Issue
Block a user