feat: enforce user roles
enforces user admin role requirement for - creating / deleting / setting role for organization users - creating / deleting / setting role for project users - updating project name - deleting project hides action elements based on role for - admin console - team settings if team is only visible through project membership - add project tile if not team admin - project name text editor if not team / project admin - add redirect from team page if settings only visible through project membership - add redirect from admin console if not org admin role enforcement is handled on the api side through a custom GraphQL directive `hasRole`. on the client side, role information is fetched in the TopNavbar's `me` query and stored in the `UserContext`. there is a custom hook, `useCurrentUser`, that provides a user object with two functions, `isVisibile` & `isAdmin` which is used to check roles in order to render/hide relevant UI elements.
This commit is contained in:
committed by
Jordan Knott
parent
5dbdc20b36
commit
e64f6f8569
@ -25,6 +25,7 @@ export const Default = () => {
|
||||
<ThemeProvider theme={theme}>
|
||||
<Admin
|
||||
onInviteUser={action('invite user')}
|
||||
canInviteUser
|
||||
initialTab={1}
|
||||
onUpdateUserPassword={action('update user password')}
|
||||
onDeleteUser={action('delete user')}
|
||||
|
@ -55,7 +55,7 @@ export const MiniProfileActionItem = styled.span<{ disabled?: boolean }>`
|
||||
position: relative;
|
||||
text-decoration: none;
|
||||
|
||||
${(props) =>
|
||||
${props =>
|
||||
props.disabled
|
||||
? css`
|
||||
user-select: none;
|
||||
@ -75,7 +75,7 @@ export const Content = styled.div`
|
||||
|
||||
export const CurrentPermission = styled.span`
|
||||
margin-left: 4px;
|
||||
color: rgba(${(props) => props.theme.colors.text.secondary}, 0.4);
|
||||
color: rgba(${props => props.theme.colors.text.secondary}, 0.4);
|
||||
`;
|
||||
|
||||
export const Separator = styled.div`
|
||||
@ -86,13 +86,13 @@ export const Separator = styled.div`
|
||||
|
||||
export const WarningText = styled.span`
|
||||
display: flex;
|
||||
color: rgba(${(props) => props.theme.colors.text.primary}, 0.4);
|
||||
color: rgba(${props => props.theme.colors.text.primary}, 0.4);
|
||||
padding: 6px;
|
||||
`;
|
||||
|
||||
export const DeleteDescription = styled.div`
|
||||
font-size: 14px;
|
||||
color: rgba(${(props) => props.theme.colors.text.primary});
|
||||
color: rgba(${props => props.theme.colors.text.primary});
|
||||
`;
|
||||
|
||||
export const RemoveMemberButton = styled(Button)`
|
||||
@ -159,8 +159,8 @@ const TeamRoleManagerPopup: React.FC<TeamRoleManagerPopupProps> = ({
|
||||
<MiniProfileActions>
|
||||
<MiniProfileActionWrapper>
|
||||
{permissions
|
||||
.filter((p) => (user.role && user.role.code === 'owner') || p.code !== 'owner')
|
||||
.map((perm) => (
|
||||
.filter(p => (user.role && user.role.code === 'owner') || p.code !== 'owner')
|
||||
.map(perm => (
|
||||
<MiniProfileActionItem
|
||||
disabled={user.role && perm.code !== user.role.code && !canChangeRole}
|
||||
key={perm.code}
|
||||
@ -211,9 +211,9 @@ const TeamRoleManagerPopup: React.FC<TeamRoleManagerPopupProps> = ({
|
||||
Choose a new user to take over ownership of this user's teams & projects.
|
||||
</DeleteDescription>
|
||||
<UserSelect
|
||||
onChange={(v) => setDeleteUser(v)}
|
||||
onChange={v => setDeleteUser(v)}
|
||||
value={deleteUser}
|
||||
options={users.map((u) => ({ label: u.fullName, value: u.id }))}
|
||||
options={users.map(u => ({ label: u.fullName, value: u.id }))}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
@ -239,11 +239,7 @@ const TeamRoleManagerPopup: React.FC<TeamRoleManagerPopupProps> = ({
|
||||
Removing this user from the organzation will remove them from assigned tasks, projects, and teams.
|
||||
</DeleteDescription>
|
||||
<DeleteDescription>{`The user is the owner of ${user.owned.projects.length} projects & ${user.owned.teams.length} teams.`}</DeleteDescription>
|
||||
<UserSelect
|
||||
onChange={() => {}}
|
||||
value={null}
|
||||
options={users.map((u) => ({ label: u.fullName, value: u.id }))}
|
||||
/>
|
||||
<UserSelect onChange={() => {}} value={null} options={users.map(u => ({ label: u.fullName, value: u.id }))} />
|
||||
<UserPassConfirmButton
|
||||
onClick={() => {
|
||||
// onDeleteUser();
|
||||
@ -334,14 +330,14 @@ const MemberItemOption = styled(Button)`
|
||||
`;
|
||||
|
||||
const MemberList = styled.div`
|
||||
border-top: 1px solid rgba(${(props) => props.theme.colors.border});
|
||||
border-top: 1px solid rgba(${props => props.theme.colors.border});
|
||||
`;
|
||||
|
||||
const MemberListItem = styled.div`
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid rgba(${(props) => props.theme.colors.border});
|
||||
border-bottom: 1px solid rgba(${props => props.theme.colors.border});
|
||||
min-height: 40px;
|
||||
padding: 12px 0 12px 40px;
|
||||
position: relative;
|
||||
@ -365,11 +361,11 @@ const MemberProfile = styled(TaskAssignee)`
|
||||
`;
|
||||
|
||||
const MemberItemName = styled.p`
|
||||
color: rgba(${(props) => props.theme.colors.text.secondary});
|
||||
color: rgba(${props => props.theme.colors.text.secondary});
|
||||
`;
|
||||
|
||||
const MemberItemUsername = styled.p`
|
||||
color: rgba(${(props) => props.theme.colors.text.primary});
|
||||
color: rgba(${props => props.theme.colors.text.primary});
|
||||
`;
|
||||
|
||||
const MemberListHeader = styled.div`
|
||||
@ -378,12 +374,12 @@ const MemberListHeader = styled.div`
|
||||
`;
|
||||
const ListTitle = styled.h3`
|
||||
font-size: 18px;
|
||||
color: rgba(${(props) => props.theme.colors.text.secondary});
|
||||
color: rgba(${props => props.theme.colors.text.secondary});
|
||||
margin-bottom: 12px;
|
||||
`;
|
||||
const ListDesc = styled.span`
|
||||
font-size: 16px;
|
||||
color: rgba(${(props) => props.theme.colors.text.primary});
|
||||
color: rgba(${props => props.theme.colors.text.primary});
|
||||
`;
|
||||
const FilterSearch = styled(Input)`
|
||||
margin: 0;
|
||||
@ -484,7 +480,7 @@ const ActionButtons = (params: any) => {
|
||||
<ActionButton onClick={() => {}}>
|
||||
<EditUserIcon width={16} height={16} />
|
||||
</ActionButton>
|
||||
<ActionButton onClick={($target) => params.onDeleteUser($target, params.value)}>
|
||||
<ActionButton onClick={$target => params.onDeleteUser($target, params.value)}>
|
||||
<DeleteUserIcon width={16} height={16} />
|
||||
</ActionButton>
|
||||
</>
|
||||
@ -541,7 +537,7 @@ const TabNavItemButton = styled.button<{ active: boolean }>`
|
||||
width: 100%;
|
||||
position: relative;
|
||||
|
||||
color: ${(props) => (props.active ? 'rgba(115, 103, 240)' : '#c2c6dc')};
|
||||
color: ${props => (props.active ? 'rgba(115, 103, 240)' : '#c2c6dc')};
|
||||
&:hover {
|
||||
color: rgba(115, 103, 240);
|
||||
}
|
||||
@ -562,7 +558,7 @@ const TabNavLine = styled.span<{ top: number }>`
|
||||
width: 2px;
|
||||
height: 48px;
|
||||
transform: scaleX(1);
|
||||
top: ${(props) => props.top}px;
|
||||
top: ${props => props.top}px;
|
||||
|
||||
background: linear-gradient(30deg, rgba(115, 103, 240), rgba(115, 103, 240));
|
||||
box-shadow: 0 0 8px 0 rgba(115, 103, 240);
|
||||
@ -624,6 +620,7 @@ type AdminProps = {
|
||||
onDeleteUser: (userID: string, newOwnerID: string | null) => void;
|
||||
onInviteUser: ($target: React.RefObject<HTMLElement>) => void;
|
||||
users: Array<User>;
|
||||
canInviteUser: boolean;
|
||||
onUpdateUserPassword: (user: TaskUser, password: string) => void;
|
||||
};
|
||||
|
||||
@ -631,6 +628,7 @@ const Admin: React.FC<AdminProps> = ({
|
||||
initialTab,
|
||||
onAddUser,
|
||||
onUpdateUserPassword,
|
||||
canInviteUser,
|
||||
onDeleteUser,
|
||||
onInviteUser,
|
||||
users,
|
||||
@ -675,18 +673,20 @@ const Admin: React.FC<AdminProps> = ({
|
||||
</ListDesc>
|
||||
<ListActions>
|
||||
<FilterSearch width="250px" variant="alternate" placeholder="Filter by name" />
|
||||
<InviteMemberButton
|
||||
onClick={($target) => {
|
||||
onAddUser($target);
|
||||
}}
|
||||
>
|
||||
<InviteIcon width={16} height={16} />
|
||||
New Member
|
||||
</InviteMemberButton>
|
||||
{canInviteUser && (
|
||||
<InviteMemberButton
|
||||
onClick={$target => {
|
||||
onAddUser($target);
|
||||
}}
|
||||
>
|
||||
<InviteIcon width={16} height={16} />
|
||||
New Member
|
||||
</InviteMemberButton>
|
||||
)}
|
||||
</ListActions>
|
||||
</MemberListHeader>
|
||||
<MemberList>
|
||||
{users.map((member) => {
|
||||
{users.map(member => {
|
||||
const projectTotal = member.owned.projects.length + member.member.projects.length;
|
||||
return (
|
||||
<MemberListItem>
|
||||
@ -699,7 +699,7 @@ const Admin: React.FC<AdminProps> = ({
|
||||
<MemberItemOption variant="flat">{`On ${projectTotal} projects`}</MemberItemOption>
|
||||
<MemberItemOption
|
||||
variant="outline"
|
||||
onClick={($target) => {
|
||||
onClick={$target => {
|
||||
showPopup(
|
||||
$target,
|
||||
<TeamRoleManagerPopup
|
||||
@ -710,7 +710,7 @@ const Admin: React.FC<AdminProps> = ({
|
||||
onUpdateUserPassword(user, password);
|
||||
}}
|
||||
canChangeRole={(member.role && member.role.code !== 'owner') ?? false}
|
||||
onChangeRole={(roleCode) => {
|
||||
onChangeRole={roleCode => {
|
||||
updateUserRole({ variables: { userID: member.id, roleCode } });
|
||||
}}
|
||||
onDeleteUser={onDeleteUser}
|
||||
|
@ -37,17 +37,22 @@ const DropdownMenu: React.FC<DropdownMenuProps> = ({ left, top, onLogout, onClos
|
||||
type ProfileMenuProps = {
|
||||
onProfile: () => void;
|
||||
onLogout: () => void;
|
||||
showAdminConsole: boolean;
|
||||
onAdminConsole: () => void;
|
||||
};
|
||||
|
||||
const ProfileMenu: React.FC<ProfileMenuProps> = ({ onAdminConsole, onProfile, onLogout }) => {
|
||||
const ProfileMenu: React.FC<ProfileMenuProps> = ({ showAdminConsole, onAdminConsole, onProfile, onLogout }) => {
|
||||
return (
|
||||
<>
|
||||
<ActionItem onClick={onAdminConsole}>
|
||||
<Cog size={16} color="#c2c6dc" />
|
||||
<ActionTitle>Admin Console</ActionTitle>
|
||||
</ActionItem>
|
||||
<Separator />
|
||||
{showAdminConsole && (
|
||||
<>
|
||||
<ActionItem onClick={onAdminConsole}>
|
||||
<Cog size={16} color="#c2c6dc" />
|
||||
<ActionTitle>Admin Console</ActionTitle>
|
||||
</ActionItem>
|
||||
<Separator />
|
||||
</>
|
||||
)}
|
||||
<ActionItem onClick={onProfile}>
|
||||
<User size={16} color="#c2c6dc" />
|
||||
<ActionTitle>Profile</ActionTitle>
|
||||
|
@ -50,7 +50,6 @@ type MiniProfileProps = {
|
||||
onRemoveFromTask?: () => void;
|
||||
onChangeRole?: (roleCode: RoleCode) => void;
|
||||
onRemoveFromBoard?: () => void;
|
||||
onChangeProjectOwner?: (userID: string) => void;
|
||||
warning?: string | null;
|
||||
canChangeRole?: boolean;
|
||||
};
|
||||
@ -58,7 +57,6 @@ const MiniProfile: React.FC<MiniProfileProps> = ({
|
||||
user,
|
||||
bio,
|
||||
canChangeRole,
|
||||
onChangeProjectOwner,
|
||||
onRemoveFromTask,
|
||||
onChangeRole,
|
||||
onRemoveFromBoard,
|
||||
@ -91,15 +89,6 @@ const MiniProfile: React.FC<MiniProfileProps> = ({
|
||||
Remove from card
|
||||
</MiniProfileActionItem>
|
||||
)}
|
||||
{onChangeProjectOwner && (
|
||||
<MiniProfileActionItem
|
||||
onClick={() => {
|
||||
setTab(3);
|
||||
}}
|
||||
>
|
||||
Set as project owner
|
||||
</MiniProfileActionItem>
|
||||
)}
|
||||
{onChangeRole && user.role && (
|
||||
<MiniProfileActionItem
|
||||
onClick={() => {
|
||||
@ -193,24 +182,6 @@ const MiniProfile: React.FC<MiniProfileProps> = ({
|
||||
</RemoveMemberButton>
|
||||
</Content>
|
||||
</Popup>
|
||||
<Popup title="Set as Project Owner?" onClose={() => hidePopup()} tab={3}>
|
||||
<Content>
|
||||
<DeleteDescription>
|
||||
This will change the project owner from you to this user. They will be able to view and edit cards, remove
|
||||
members, and change all settings for the project. They will also be able to delete the project.
|
||||
</DeleteDescription>
|
||||
<RemoveMemberButton
|
||||
color="warning"
|
||||
onClick={() => {
|
||||
if (onChangeProjectOwner) {
|
||||
onChangeProjectOwner(user.id);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Set as Project Owner
|
||||
</RemoveMemberButton>
|
||||
</Content>
|
||||
</Popup>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -38,6 +38,7 @@ const HomeDashboard = styled(Home)``;
|
||||
type ProjectHeadingProps = {
|
||||
onFavorite?: () => void;
|
||||
name: string;
|
||||
canEditProjectName: boolean;
|
||||
onSaveProjectName?: (projectName: string) => void;
|
||||
onOpenSettings: ($target: React.RefObject<HTMLElement>) => void;
|
||||
};
|
||||
@ -46,6 +47,7 @@ const ProjectHeading: React.FC<ProjectHeadingProps> = ({
|
||||
onFavorite,
|
||||
name: initialProjectName,
|
||||
onSaveProjectName,
|
||||
canEditProjectName,
|
||||
onOpenSettings,
|
||||
}) => {
|
||||
const [isEditProjectName, setEditProjectName] = useState(false);
|
||||
@ -94,7 +96,9 @@ const ProjectHeading: React.FC<ProjectHeadingProps> = ({
|
||||
) : (
|
||||
<ProjectName
|
||||
onClick={() => {
|
||||
setEditProjectName(true);
|
||||
if (canEditProjectName) {
|
||||
setEditProjectName(true);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{projectName}
|
||||
@ -142,19 +146,25 @@ type NavBarProps = {
|
||||
onProfileClick: ($target: React.RefObject<HTMLElement>) => void;
|
||||
onSaveName?: (name: string) => void;
|
||||
onNotificationClick: () => void;
|
||||
canEditProjectName?: boolean;
|
||||
canInviteUser?: boolean;
|
||||
onInviteUser?: ($target: React.RefObject<HTMLElement>) => void;
|
||||
onDashboardClick: () => void;
|
||||
user: TaskUser | null;
|
||||
onOpenSettings: ($target: React.RefObject<HTMLElement>) => void;
|
||||
projectMembers?: Array<TaskUser> | null;
|
||||
onRemoveFromBoard?: (userID: string) => void;
|
||||
onMemberProfile?: ($targetRef: React.RefObject<HTMLElement>, memberID: string) => void;
|
||||
};
|
||||
|
||||
const NavBar: React.FC<NavBarProps> = ({
|
||||
menuType,
|
||||
canInviteUser = false,
|
||||
onInviteUser,
|
||||
onChangeProjectOwner,
|
||||
currentTab,
|
||||
onMemberProfile,
|
||||
canEditProjectName = false,
|
||||
onOpenProjectFinder,
|
||||
onFavorite,
|
||||
onSetTab,
|
||||
@ -175,47 +185,6 @@ const NavBar: React.FC<NavBarProps> = ({
|
||||
}
|
||||
};
|
||||
const { showPopup } = usePopup();
|
||||
const onMemberProfile = ($targetRef: React.RefObject<HTMLElement>, memberID: string) => {
|
||||
const member = projectMembers ? projectMembers.find(u => u.id === memberID) : null;
|
||||
const warning =
|
||||
'You can’t leave because you are the only admin. To make another user an admin, click their avatar, select “Change permissions…”, and select “Admin”.';
|
||||
if (member) {
|
||||
console.log(member);
|
||||
showPopup(
|
||||
$targetRef,
|
||||
<MiniProfile
|
||||
warning={member.role && member.role.code === 'owner' ? warning : null}
|
||||
onChangeProjectOwner={
|
||||
member.role && member.role.code !== 'owner'
|
||||
? (userID: string) => {
|
||||
if (user && onChangeProjectOwner) {
|
||||
onChangeProjectOwner(userID);
|
||||
}
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
canChangeRole={member.role && member.role.code !== 'owner'}
|
||||
onChangeRole={roleCode => {
|
||||
if (onChangeRole) {
|
||||
onChangeRole(member.id, roleCode);
|
||||
}
|
||||
}}
|
||||
onRemoveFromBoard={
|
||||
member.role && member.role.code === 'owner'
|
||||
? undefined
|
||||
: () => {
|
||||
if (onRemoveFromBoard) {
|
||||
onRemoveFromBoard(member.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
user={member}
|
||||
bio=""
|
||||
/>,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<NavbarWrapper>
|
||||
<NavbarHeader>
|
||||
@ -226,6 +195,7 @@ const NavBar: React.FC<NavBarProps> = ({
|
||||
onFavorite={onFavorite}
|
||||
onOpenSettings={onOpenSettings}
|
||||
name={name}
|
||||
canEditProjectName={canEditProjectName}
|
||||
onSaveProjectName={onSaveName}
|
||||
/>
|
||||
)}
|
||||
@ -255,7 +225,7 @@ const NavBar: React.FC<NavBarProps> = ({
|
||||
<TaskcafeTitle>Taskcafé</TaskcafeTitle>
|
||||
</LogoContainer>
|
||||
<GlobalActions>
|
||||
{projectMembers && (
|
||||
{projectMembers && onMemberProfile && (
|
||||
<>
|
||||
<ProjectMembers>
|
||||
{projectMembers.map((member, idx) => (
|
||||
@ -268,16 +238,18 @@ const NavBar: React.FC<NavBarProps> = ({
|
||||
onMemberProfile={onMemberProfile}
|
||||
/>
|
||||
))}
|
||||
<InviteButton
|
||||
onClick={$target => {
|
||||
if (onInviteUser) {
|
||||
onInviteUser($target);
|
||||
}
|
||||
}}
|
||||
variant="outline"
|
||||
>
|
||||
Invite
|
||||
</InviteButton>
|
||||
{canInviteUser && (
|
||||
<InviteButton
|
||||
onClick={$target => {
|
||||
if (onInviteUser) {
|
||||
onInviteUser($target);
|
||||
}
|
||||
}}
|
||||
variant="outline"
|
||||
>
|
||||
Invite
|
||||
</InviteButton>
|
||||
)}
|
||||
</ProjectMembers>
|
||||
<NavSeparator />
|
||||
</>
|
||||
|
@ -17,6 +17,7 @@ export type Scalars = {
|
||||
|
||||
|
||||
|
||||
|
||||
export enum RoleCode {
|
||||
Owner = 'owner',
|
||||
Admin = 'admin',
|
||||
@ -125,7 +126,6 @@ export type Project = {
|
||||
createdAt: Scalars['Time'];
|
||||
name: Scalars['String'];
|
||||
team: Team;
|
||||
owner: Member;
|
||||
taskGroups: Array<TaskGroup>;
|
||||
members: Array<Member>;
|
||||
labels: Array<ProjectLabel>;
|
||||
@ -192,6 +192,24 @@ export type TaskChecklist = {
|
||||
items: Array<TaskChecklistItem>;
|
||||
};
|
||||
|
||||
export enum RoleLevel {
|
||||
Admin = 'ADMIN',
|
||||
Member = 'MEMBER'
|
||||
}
|
||||
|
||||
export enum ActionLevel {
|
||||
Org = 'ORG',
|
||||
Team = 'TEAM',
|
||||
Project = 'PROJECT'
|
||||
}
|
||||
|
||||
export enum ObjectType {
|
||||
Org = 'ORG',
|
||||
Team = 'TEAM',
|
||||
Project = 'PROJECT',
|
||||
Task = 'TASK'
|
||||
}
|
||||
|
||||
export type Query = {
|
||||
__typename?: 'Query';
|
||||
organizations: Array<Organization>;
|
||||
@ -204,7 +222,7 @@ export type Query = {
|
||||
teams: Array<Team>;
|
||||
labelColors: Array<LabelColor>;
|
||||
taskGroups: Array<TaskGroup>;
|
||||
me: UserAccount;
|
||||
me: MePayload;
|
||||
};
|
||||
|
||||
|
||||
@ -260,10 +278,8 @@ export type Mutation = {
|
||||
deleteUserAccount: DeleteUserAccountPayload;
|
||||
logoutUser: Scalars['Boolean'];
|
||||
removeTaskLabel: Task;
|
||||
setProjectOwner: SetProjectOwnerPayload;
|
||||
setTaskChecklistItemComplete: TaskChecklistItem;
|
||||
setTaskComplete: Task;
|
||||
setTeamOwner: SetTeamOwnerPayload;
|
||||
toggleTaskLabel: ToggleTaskLabelPayload;
|
||||
unassignTask: Task;
|
||||
updateProjectLabel: ProjectLabel;
|
||||
@ -412,11 +428,6 @@ export type MutationRemoveTaskLabelArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationSetProjectOwnerArgs = {
|
||||
input: SetProjectOwner;
|
||||
};
|
||||
|
||||
|
||||
export type MutationSetTaskChecklistItemCompleteArgs = {
|
||||
input: SetTaskChecklistItemComplete;
|
||||
};
|
||||
@ -427,11 +438,6 @@ export type MutationSetTaskCompleteArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationSetTeamOwnerArgs = {
|
||||
input: SetTeamOwner;
|
||||
};
|
||||
|
||||
|
||||
export type MutationToggleTaskLabelArgs = {
|
||||
input: ToggleTaskLabelInput;
|
||||
};
|
||||
@ -531,6 +537,25 @@ export type MutationUpdateUserRoleArgs = {
|
||||
input: UpdateUserRole;
|
||||
};
|
||||
|
||||
export type TeamRole = {
|
||||
__typename?: 'TeamRole';
|
||||
teamID: Scalars['UUID'];
|
||||
roleCode: RoleCode;
|
||||
};
|
||||
|
||||
export type ProjectRole = {
|
||||
__typename?: 'ProjectRole';
|
||||
projectID: Scalars['UUID'];
|
||||
roleCode: RoleCode;
|
||||
};
|
||||
|
||||
export type MePayload = {
|
||||
__typename?: 'MePayload';
|
||||
user: UserAccount;
|
||||
teamRoles: Array<TeamRole>;
|
||||
projectRoles: Array<ProjectRole>;
|
||||
};
|
||||
|
||||
export type ProjectsFilter = {
|
||||
teamID?: Maybe<Scalars['UUID']>;
|
||||
};
|
||||
@ -540,7 +565,7 @@ export type FindUser = {
|
||||
};
|
||||
|
||||
export type FindProject = {
|
||||
projectId: Scalars['String'];
|
||||
projectID: Scalars['UUID'];
|
||||
};
|
||||
|
||||
export type FindTask = {
|
||||
@ -633,18 +658,6 @@ export type UpdateProjectMemberRolePayload = {
|
||||
member: Member;
|
||||
};
|
||||
|
||||
export type SetProjectOwner = {
|
||||
projectID: Scalars['UUID'];
|
||||
ownerID: Scalars['UUID'];
|
||||
};
|
||||
|
||||
export type SetProjectOwnerPayload = {
|
||||
__typename?: 'SetProjectOwnerPayload';
|
||||
ok: Scalars['Boolean'];
|
||||
prevOwner: Member;
|
||||
newOwner: Member;
|
||||
};
|
||||
|
||||
export type NewTask = {
|
||||
taskGroupID: Scalars['String'];
|
||||
name: Scalars['String'];
|
||||
@ -868,19 +881,8 @@ export type UpdateTeamMemberRole = {
|
||||
export type UpdateTeamMemberRolePayload = {
|
||||
__typename?: 'UpdateTeamMemberRolePayload';
|
||||
ok: Scalars['Boolean'];
|
||||
member: Member;
|
||||
};
|
||||
|
||||
export type SetTeamOwner = {
|
||||
teamID: Scalars['UUID'];
|
||||
userID: Scalars['UUID'];
|
||||
};
|
||||
|
||||
export type SetTeamOwnerPayload = {
|
||||
__typename?: 'SetTeamOwnerPayload';
|
||||
ok: Scalars['Boolean'];
|
||||
prevOwner: Member;
|
||||
newOwner: Member;
|
||||
member: Member;
|
||||
};
|
||||
|
||||
export type UpdateUserPassword = {
|
||||
@ -1066,7 +1068,7 @@ export type DeleteTaskGroupMutation = (
|
||||
);
|
||||
|
||||
export type FindProjectQueryVariables = {
|
||||
projectId: Scalars['String'];
|
||||
projectID: Scalars['UUID'];
|
||||
};
|
||||
|
||||
|
||||
@ -1075,7 +1077,10 @@ export type FindProjectQuery = (
|
||||
& { findProject: (
|
||||
{ __typename?: 'Project' }
|
||||
& Pick<Project, 'name'>
|
||||
& { members: Array<(
|
||||
& { team: (
|
||||
{ __typename?: 'Team' }
|
||||
& Pick<Team, 'id'>
|
||||
), members: Array<(
|
||||
{ __typename?: 'Member' }
|
||||
& Pick<Member, 'id' | 'fullName' | 'username'>
|
||||
& { role: (
|
||||
@ -1242,12 +1247,21 @@ export type MeQueryVariables = {};
|
||||
export type MeQuery = (
|
||||
{ __typename?: 'Query' }
|
||||
& { me: (
|
||||
{ __typename?: 'UserAccount' }
|
||||
& Pick<UserAccount, 'id' | 'fullName'>
|
||||
& { profileIcon: (
|
||||
{ __typename?: 'ProfileIcon' }
|
||||
& Pick<ProfileIcon, 'initials' | 'bgColor' | 'url'>
|
||||
) }
|
||||
{ __typename?: 'MePayload' }
|
||||
& { user: (
|
||||
{ __typename?: 'UserAccount' }
|
||||
& Pick<UserAccount, 'id' | 'fullName'>
|
||||
& { profileIcon: (
|
||||
{ __typename?: 'ProfileIcon' }
|
||||
& Pick<ProfileIcon, 'initials' | 'bgColor' | 'url'>
|
||||
) }
|
||||
), teamRoles: Array<(
|
||||
{ __typename?: 'TeamRole' }
|
||||
& Pick<TeamRole, 'teamID' | 'roleCode'>
|
||||
)>, projectRoles: Array<(
|
||||
{ __typename?: 'ProjectRole' }
|
||||
& Pick<ProjectRole, 'projectID' | 'roleCode'>
|
||||
)> }
|
||||
) }
|
||||
);
|
||||
|
||||
@ -1311,35 +1325,6 @@ export type DeleteProjectMemberMutation = (
|
||||
) }
|
||||
);
|
||||
|
||||
export type SetProjectOwnerMutationVariables = {
|
||||
projectID: Scalars['UUID'];
|
||||
ownerID: Scalars['UUID'];
|
||||
};
|
||||
|
||||
|
||||
export type SetProjectOwnerMutation = (
|
||||
{ __typename?: 'Mutation' }
|
||||
& { setProjectOwner: (
|
||||
{ __typename?: 'SetProjectOwnerPayload' }
|
||||
& Pick<SetProjectOwnerPayload, 'ok'>
|
||||
& { newOwner: (
|
||||
{ __typename?: 'Member' }
|
||||
& Pick<Member, 'id'>
|
||||
& { role: (
|
||||
{ __typename?: 'Role' }
|
||||
& Pick<Role, 'code' | 'name'>
|
||||
) }
|
||||
), prevOwner: (
|
||||
{ __typename?: 'Member' }
|
||||
& Pick<Member, 'id'>
|
||||
& { role: (
|
||||
{ __typename?: 'Role' }
|
||||
& Pick<Role, 'code' | 'name'>
|
||||
) }
|
||||
) }
|
||||
) }
|
||||
);
|
||||
|
||||
export type UpdateProjectMemberRoleMutationVariables = {
|
||||
projectID: Scalars['UUID'];
|
||||
userID: Scalars['UUID'];
|
||||
@ -1706,6 +1691,29 @@ export type GetTeamQuery = (
|
||||
)> }
|
||||
);
|
||||
|
||||
export type UpdateTeamMemberRoleMutationVariables = {
|
||||
teamID: Scalars['UUID'];
|
||||
userID: Scalars['UUID'];
|
||||
roleCode: RoleCode;
|
||||
};
|
||||
|
||||
|
||||
export type UpdateTeamMemberRoleMutation = (
|
||||
{ __typename?: 'Mutation' }
|
||||
& { updateTeamMemberRole: (
|
||||
{ __typename?: 'UpdateTeamMemberRolePayload' }
|
||||
& Pick<UpdateTeamMemberRolePayload, 'teamID'>
|
||||
& { member: (
|
||||
{ __typename?: 'Member' }
|
||||
& Pick<Member, 'id'>
|
||||
& { role: (
|
||||
{ __typename?: 'Role' }
|
||||
& Pick<Role, 'code' | 'name'>
|
||||
) }
|
||||
) }
|
||||
) }
|
||||
);
|
||||
|
||||
export type ToggleTaskLabelMutationVariables = {
|
||||
taskID: Scalars['UUID'];
|
||||
projectLabelID: Scalars['UUID'];
|
||||
@ -2325,9 +2333,12 @@ export type DeleteTaskGroupMutationHookResult = ReturnType<typeof useDeleteTaskG
|
||||
export type DeleteTaskGroupMutationResult = ApolloReactCommon.MutationResult<DeleteTaskGroupMutation>;
|
||||
export type DeleteTaskGroupMutationOptions = ApolloReactCommon.BaseMutationOptions<DeleteTaskGroupMutation, DeleteTaskGroupMutationVariables>;
|
||||
export const FindProjectDocument = gql`
|
||||
query findProject($projectId: String!) {
|
||||
findProject(input: {projectId: $projectId}) {
|
||||
query findProject($projectID: UUID!) {
|
||||
findProject(input: {projectID: $projectID}) {
|
||||
name
|
||||
team {
|
||||
id
|
||||
}
|
||||
members {
|
||||
id
|
||||
fullName
|
||||
@ -2418,7 +2429,7 @@ export const FindProjectDocument = gql`
|
||||
* @example
|
||||
* const { data, loading, error } = useFindProjectQuery({
|
||||
* variables: {
|
||||
* projectId: // value for 'projectId'
|
||||
* projectID: // value for 'projectID'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
@ -2563,12 +2574,22 @@ export type GetProjectsQueryResult = ApolloReactCommon.QueryResult<GetProjectsQu
|
||||
export const MeDocument = gql`
|
||||
query me {
|
||||
me {
|
||||
id
|
||||
fullName
|
||||
profileIcon {
|
||||
initials
|
||||
bgColor
|
||||
url
|
||||
user {
|
||||
id
|
||||
fullName
|
||||
profileIcon {
|
||||
initials
|
||||
bgColor
|
||||
url
|
||||
}
|
||||
}
|
||||
teamRoles {
|
||||
teamID
|
||||
roleCode
|
||||
}
|
||||
projectRoles {
|
||||
projectID
|
||||
roleCode
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2717,53 +2738,6 @@ export function useDeleteProjectMemberMutation(baseOptions?: ApolloReactHooks.Mu
|
||||
export type DeleteProjectMemberMutationHookResult = ReturnType<typeof useDeleteProjectMemberMutation>;
|
||||
export type DeleteProjectMemberMutationResult = ApolloReactCommon.MutationResult<DeleteProjectMemberMutation>;
|
||||
export type DeleteProjectMemberMutationOptions = ApolloReactCommon.BaseMutationOptions<DeleteProjectMemberMutation, DeleteProjectMemberMutationVariables>;
|
||||
export const SetProjectOwnerDocument = gql`
|
||||
mutation setProjectOwner($projectID: UUID!, $ownerID: UUID!) {
|
||||
setProjectOwner(input: {projectID: $projectID, ownerID: $ownerID}) {
|
||||
ok
|
||||
newOwner {
|
||||
id
|
||||
role {
|
||||
code
|
||||
name
|
||||
}
|
||||
}
|
||||
prevOwner {
|
||||
id
|
||||
role {
|
||||
code
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
export type SetProjectOwnerMutationFn = ApolloReactCommon.MutationFunction<SetProjectOwnerMutation, SetProjectOwnerMutationVariables>;
|
||||
|
||||
/**
|
||||
* __useSetProjectOwnerMutation__
|
||||
*
|
||||
* To run a mutation, you first call `useSetProjectOwnerMutation` within a React component and pass it any options that fit your needs.
|
||||
* When your component renders, `useSetProjectOwnerMutation` returns a tuple that includes:
|
||||
* - A mutate function that you can call at any time to execute the mutation
|
||||
* - An object with fields that represent the current status of the mutation's execution
|
||||
*
|
||||
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||
*
|
||||
* @example
|
||||
* const [setProjectOwnerMutation, { data, loading, error }] = useSetProjectOwnerMutation({
|
||||
* variables: {
|
||||
* projectID: // value for 'projectID'
|
||||
* ownerID: // value for 'ownerID'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useSetProjectOwnerMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<SetProjectOwnerMutation, SetProjectOwnerMutationVariables>) {
|
||||
return ApolloReactHooks.useMutation<SetProjectOwnerMutation, SetProjectOwnerMutationVariables>(SetProjectOwnerDocument, baseOptions);
|
||||
}
|
||||
export type SetProjectOwnerMutationHookResult = ReturnType<typeof useSetProjectOwnerMutation>;
|
||||
export type SetProjectOwnerMutationResult = ApolloReactCommon.MutationResult<SetProjectOwnerMutation>;
|
||||
export type SetProjectOwnerMutationOptions = ApolloReactCommon.BaseMutationOptions<SetProjectOwnerMutation, SetProjectOwnerMutationVariables>;
|
||||
export const UpdateProjectMemberRoleDocument = gql`
|
||||
mutation updateProjectMemberRole($projectID: UUID!, $userID: UUID!, $roleCode: RoleCode!) {
|
||||
updateProjectMemberRole(input: {projectID: $projectID, userID: $userID, roleCode: $roleCode}) {
|
||||
@ -3510,6 +3484,47 @@ export function useGetTeamLazyQuery(baseOptions?: ApolloReactHooks.LazyQueryHook
|
||||
export type GetTeamQueryHookResult = ReturnType<typeof useGetTeamQuery>;
|
||||
export type GetTeamLazyQueryHookResult = ReturnType<typeof useGetTeamLazyQuery>;
|
||||
export type GetTeamQueryResult = ApolloReactCommon.QueryResult<GetTeamQuery, GetTeamQueryVariables>;
|
||||
export const UpdateTeamMemberRoleDocument = gql`
|
||||
mutation updateTeamMemberRole($teamID: UUID!, $userID: UUID!, $roleCode: RoleCode!) {
|
||||
updateTeamMemberRole(input: {teamID: $teamID, userID: $userID, roleCode: $roleCode}) {
|
||||
member {
|
||||
id
|
||||
role {
|
||||
code
|
||||
name
|
||||
}
|
||||
}
|
||||
teamID
|
||||
}
|
||||
}
|
||||
`;
|
||||
export type UpdateTeamMemberRoleMutationFn = ApolloReactCommon.MutationFunction<UpdateTeamMemberRoleMutation, UpdateTeamMemberRoleMutationVariables>;
|
||||
|
||||
/**
|
||||
* __useUpdateTeamMemberRoleMutation__
|
||||
*
|
||||
* To run a mutation, you first call `useUpdateTeamMemberRoleMutation` within a React component and pass it any options that fit your needs.
|
||||
* When your component renders, `useUpdateTeamMemberRoleMutation` returns a tuple that includes:
|
||||
* - A mutate function that you can call at any time to execute the mutation
|
||||
* - An object with fields that represent the current status of the mutation's execution
|
||||
*
|
||||
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||
*
|
||||
* @example
|
||||
* const [updateTeamMemberRoleMutation, { data, loading, error }] = useUpdateTeamMemberRoleMutation({
|
||||
* variables: {
|
||||
* teamID: // value for 'teamID'
|
||||
* userID: // value for 'userID'
|
||||
* roleCode: // value for 'roleCode'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useUpdateTeamMemberRoleMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<UpdateTeamMemberRoleMutation, UpdateTeamMemberRoleMutationVariables>) {
|
||||
return ApolloReactHooks.useMutation<UpdateTeamMemberRoleMutation, UpdateTeamMemberRoleMutationVariables>(UpdateTeamMemberRoleDocument, baseOptions);
|
||||
}
|
||||
export type UpdateTeamMemberRoleMutationHookResult = ReturnType<typeof useUpdateTeamMemberRoleMutation>;
|
||||
export type UpdateTeamMemberRoleMutationResult = ApolloReactCommon.MutationResult<UpdateTeamMemberRoleMutation>;
|
||||
export type UpdateTeamMemberRoleMutationOptions = ApolloReactCommon.BaseMutationOptions<UpdateTeamMemberRoleMutation, UpdateTeamMemberRoleMutationVariables>;
|
||||
export const ToggleTaskLabelDocument = gql`
|
||||
mutation toggleTaskLabel($taskID: UUID!, $projectLabelID: UUID!) {
|
||||
toggleTaskLabel(input: {taskID: $taskID, projectLabelID: $projectLabelID}) {
|
||||
|
@ -2,9 +2,12 @@ import gql from 'graphql-tag';
|
||||
import TASK_FRAGMENT from './fragments/task';
|
||||
|
||||
const FIND_PROJECT_QUERY = gql`
|
||||
query findProject($projectId: String!) {
|
||||
findProject(input: { projectId: $projectId }) {
|
||||
query findProject($projectID: UUID!) {
|
||||
findProject(input: { projectID: $projectID }) {
|
||||
name
|
||||
team {
|
||||
id
|
||||
}
|
||||
members {
|
||||
id
|
||||
fullName
|
||||
|
@ -1,11 +1,21 @@
|
||||
query me {
|
||||
me {
|
||||
id
|
||||
fullName
|
||||
profileIcon {
|
||||
initials
|
||||
bgColor
|
||||
url
|
||||
user {
|
||||
id
|
||||
fullName
|
||||
profileIcon {
|
||||
initials
|
||||
bgColor
|
||||
url
|
||||
}
|
||||
}
|
||||
teamRoles {
|
||||
teamID
|
||||
roleCode
|
||||
}
|
||||
projectRoles {
|
||||
projectID
|
||||
roleCode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,25 +0,0 @@
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
export const SET_PROJECT_OWNER_MUTATION = gql`
|
||||
mutation setProjectOwner($projectID: UUID!, $ownerID: UUID!) {
|
||||
setProjectOwner(input: { projectID: $projectID, ownerID: $ownerID }) {
|
||||
ok
|
||||
newOwner {
|
||||
id
|
||||
role {
|
||||
code
|
||||
name
|
||||
}
|
||||
}
|
||||
prevOwner {
|
||||
id
|
||||
role {
|
||||
code
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default SET_PROJECT_OWNER_MUTATION;
|
18
frontend/src/shared/graphql/team/updateTeamMemberRole.ts
Normal file
18
frontend/src/shared/graphql/team/updateTeamMemberRole.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
export const UPDATE_TEAM_MEMBER_ROLE_MUTATION = gql`
|
||||
mutation updateTeamMemberRole($teamID: UUID!, $userID: UUID!, $roleCode: RoleCode!) {
|
||||
updateTeamMemberRole(input: { teamID: $teamID, userID: $userID, roleCode: $roleCode }) {
|
||||
member {
|
||||
id
|
||||
role {
|
||||
code
|
||||
name
|
||||
}
|
||||
}
|
||||
teamID
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default UPDATE_TEAM_MEMBER_ROLE_MUTATION;
|
5
frontend/src/shared/utils/user.ts
Normal file
5
frontend/src/shared/utils/user.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { PermissionObjectType, PermissionLevel } from 'App/context';
|
||||
|
||||
export default function userCan(level: PermissionLevel, objectType: PermissionObjectType) {
|
||||
return false;
|
||||
}
|
Reference in New Issue
Block a user