feature: fix user admin related bugs
This commit is contained in:
parent
68fa7aef94
commit
e5bfe9b9ab
@ -214,18 +214,9 @@ const AdminRoute = () => {
|
||||
console.log(password);
|
||||
hidePopup();
|
||||
}}
|
||||
onDeleteUser={($target, userID) => {
|
||||
showPopup(
|
||||
$target,
|
||||
<Popup tab={0} title="Delete user?" onClose={() => hidePopup()}>
|
||||
<DeleteUserPopup
|
||||
onDeleteUser={() => {
|
||||
deleteUser({ variables: { userID } });
|
||||
hidePopup();
|
||||
}}
|
||||
/>
|
||||
</Popup>,
|
||||
);
|
||||
onDeleteUser={(userID, newOwnerID) => {
|
||||
deleteUser({ variables: { userID, newOwnerID } });
|
||||
hidePopup();
|
||||
}}
|
||||
onAddUser={$target => {
|
||||
showPopup(
|
||||
|
@ -481,9 +481,9 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({
|
||||
activeMembers={task.assigned ?? []}
|
||||
onMemberChange={(member, isActive) => {
|
||||
if (isActive) {
|
||||
assignTask({ variables: { taskID: task.id, userID: userID ?? '' } });
|
||||
assignTask({ variables: { taskID: task.id, userID: member.id } });
|
||||
} else {
|
||||
unassignTask({ variables: { taskID: task.id, userID: userID ?? '' } });
|
||||
unassignTask({ variables: { taskID: task.id, userID: member.id } });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
@ -335,7 +335,6 @@ const Details: React.FC<DetailsProps> = ({
|
||||
onChecklistItemDrop={(prevChecklistID, checklistID, checklistItem) => {
|
||||
updateTaskChecklistItemLocation({
|
||||
variables: { checklistID, checklistItemID: checklistItem.id, position: checklistItem.position },
|
||||
|
||||
optimisticResponse: {
|
||||
__typename: 'Mutation',
|
||||
updateTaskChecklistItemLocation: {
|
||||
|
@ -104,24 +104,28 @@ export const RemoveMemberButton = styled(Button)`
|
||||
width: 100%;
|
||||
`;
|
||||
type TeamRoleManagerPopupProps = {
|
||||
user: TaskUser;
|
||||
user: User;
|
||||
users: Array<User>;
|
||||
warning?: string | null;
|
||||
canChangeRole: boolean;
|
||||
onChangeRole: (roleCode: RoleCode) => void;
|
||||
updateUserPassword?: (user: TaskUser, password: string) => void;
|
||||
onRemoveFromTeam?: () => void;
|
||||
onDeleteUser?: (userID: string, newOwnerID: string | null) => void;
|
||||
};
|
||||
|
||||
const TeamRoleManagerPopup: React.FC<TeamRoleManagerPopupProps> = ({
|
||||
warning,
|
||||
user,
|
||||
users,
|
||||
canChangeRole,
|
||||
onRemoveFromTeam,
|
||||
onDeleteUser,
|
||||
updateUserPassword,
|
||||
onChangeRole,
|
||||
}) => {
|
||||
const { hidePopup, setTab } = usePopup();
|
||||
const [userPass, setUserPass] = useState({ pass: '', passConfirm: '' });
|
||||
const [deleteUser, setDeleteUser] = useState<{ label: string; value: string } | null>(null);
|
||||
const hasOwned = user.owned.projects.length !== 0 || user.owned.teams.length !== 0;
|
||||
return (
|
||||
<>
|
||||
<Popup title={null} tab={0}>
|
||||
@ -144,7 +148,7 @@ const TeamRoleManagerPopup: React.FC<TeamRoleManagerPopupProps> = ({
|
||||
>
|
||||
Reset password...
|
||||
</MiniProfileActionItem>
|
||||
<MiniProfileActionItem onClick={() => setTab(5)}>Remove from organzation...</MiniProfileActionItem>
|
||||
<MiniProfileActionItem onClick={() => setTab(2)}>Remove from organzation...</MiniProfileActionItem>
|
||||
</MiniProfileActionWrapper>
|
||||
</MiniProfileActions>
|
||||
{warning && (
|
||||
@ -198,21 +202,55 @@ const TeamRoleManagerPopup: React.FC<TeamRoleManagerPopupProps> = ({
|
||||
)}
|
||||
</MiniProfileActions>
|
||||
</Popup>
|
||||
<Popup title="Remove from Team?" onClose={() => hidePopup()} tab={2}>
|
||||
<Popup title="Remove from Organization?" onClose={() => hidePopup()} tab={2}>
|
||||
<Content>
|
||||
<DeleteDescription>
|
||||
The member will be removed from all cards on this project. They will receive a notification.
|
||||
Removing this user from the organzation will remove them from assigned tasks, projects, and teams.
|
||||
</DeleteDescription>
|
||||
<RemoveMemberButton
|
||||
color="danger"
|
||||
{hasOwned && (
|
||||
<>
|
||||
<DeleteDescription>{`The user is the owner of ${user.owned.projects.length} projects & ${user.owned.teams.length} teams.`}</DeleteDescription>
|
||||
<DeleteDescription>
|
||||
Choose a new user to take over ownership of this user's teams & projects.
|
||||
</DeleteDescription>
|
||||
<UserSelect
|
||||
onChange={v => setDeleteUser(v)}
|
||||
value={deleteUser}
|
||||
options={users.map(u => ({ label: u.fullName, value: u.id }))}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<UserPassConfirmButton
|
||||
disabled={!(!hasOwned || (hasOwned && deleteUser))}
|
||||
onClick={() => {
|
||||
if (onRemoveFromTeam) {
|
||||
onRemoveFromTeam();
|
||||
if (onDeleteUser) {
|
||||
console.log(`${!hasOwned} || (${hasOwned} && ${deleteUser})`);
|
||||
if (!hasOwned || (hasOwned && deleteUser)) {
|
||||
onDeleteUser(user.id, deleteUser ? deleteUser.value : null);
|
||||
}
|
||||
}
|
||||
}}
|
||||
color="danger"
|
||||
>
|
||||
Remove Member
|
||||
</RemoveMemberButton>
|
||||
Delete user
|
||||
</UserPassConfirmButton>
|
||||
</Content>
|
||||
</Popup>
|
||||
<Popup title="Really remove from Team?" onClose={() => hidePopup()} tab={4}>
|
||||
<Content>
|
||||
<DeleteDescription>
|
||||
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 }))} />
|
||||
<UserPassConfirmButton
|
||||
onClick={() => {
|
||||
// onDeleteUser();
|
||||
}}
|
||||
color="danger"
|
||||
>
|
||||
Delete user
|
||||
</UserPassConfirmButton>
|
||||
</Content>
|
||||
</Popup>
|
||||
<Popup title="Reset password?" onClose={() => hidePopup()} tab={3}>
|
||||
@ -253,18 +291,6 @@ const TeamRoleManagerPopup: React.FC<TeamRoleManagerPopupProps> = ({
|
||||
</UserPassConfirmButton>
|
||||
</Content>
|
||||
</Popup>
|
||||
<Popup title="Remove user" onClose={() => hidePopup()} tab={5}>
|
||||
<Content>
|
||||
<DeleteDescription>
|
||||
Removing this user from the organzation will remove them from assigned tasks, projects, and teams.
|
||||
</DeleteDescription>
|
||||
<DeleteDescription>The user is the owner of 3 projects & 2 teams.</DeleteDescription>
|
||||
<UserSelect onChange={() => {}} value={null} options={[{ label: 'Jordan Knott', value: 'jordanknott' }]} />
|
||||
<UserPassConfirmButton onClick={() => {}} color="danger">
|
||||
Set password
|
||||
</UserPassConfirmButton>
|
||||
</Content>
|
||||
</Popup>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -653,7 +679,7 @@ const NavItem: React.FC<NavItemProps> = ({ active, name, tab, onClick }) => {
|
||||
type AdminProps = {
|
||||
initialTab: number;
|
||||
onAddUser: ($target: React.RefObject<HTMLElement>) => void;
|
||||
onDeleteUser: ($target: React.RefObject<HTMLElement>, userID: string) => void;
|
||||
onDeleteUser: (userID: string, newOwnerID: string | null) => void;
|
||||
onInviteUser: ($target: React.RefObject<HTMLElement>) => void;
|
||||
users: Array<User>;
|
||||
onUpdateUserPassword: (user: TaskUser, password: string) => void;
|
||||
@ -700,9 +726,10 @@ const Admin: React.FC<AdminProps> = ({
|
||||
<TabContent>
|
||||
<MemberListWrapper>
|
||||
<MemberListHeader>
|
||||
<ListTitle>{`Users (${users.length})`}</ListTitle>
|
||||
<ListTitle>{`Members (${users.length})`}</ListTitle>
|
||||
<ListDesc>
|
||||
Team members can view and join all Team Visible boards and create new boards in the team.
|
||||
Organization admins can create / manage / delete all projects & teams. Members only have access to teams
|
||||
or projects they have been added to.
|
||||
</ListDesc>
|
||||
<ListActions>
|
||||
<FilterSearch width="250px" variant="alternate" placeholder="Filter by name" />
|
||||
@ -735,6 +762,7 @@ const Admin: React.FC<AdminProps> = ({
|
||||
$target,
|
||||
<TeamRoleManagerPopup
|
||||
user={member}
|
||||
users={users}
|
||||
warning={member.role && member.role.code === 'owner' ? warning : null}
|
||||
updateUserPassword={(user, password) => {
|
||||
onUpdateUserPassword(user, password);
|
||||
@ -743,13 +771,7 @@ const Admin: React.FC<AdminProps> = ({
|
||||
onChangeRole={roleCode => {
|
||||
updateUserRole({ variables: { userID: member.id, roleCode } });
|
||||
}}
|
||||
onRemoveFromTeam={
|
||||
member.role && member.role.code === 'owner'
|
||||
? undefined
|
||||
: () => {
|
||||
hidePopup();
|
||||
}
|
||||
}
|
||||
onDeleteUser={onDeleteUser}
|
||||
/>,
|
||||
);
|
||||
}}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, {useState, useRef, useEffect} from 'react';
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import {CheckSquare, Trash, Square, CheckSquareOutline, Clock, Cross, AccountPlus} from 'shared/icons';
|
||||
import {DragDropContext, Droppable, Draggable, DropResult} from 'react-beautiful-dnd';
|
||||
import { CheckSquare, Trash, Square, CheckSquareOutline, Clock, Cross, AccountPlus } from 'shared/icons';
|
||||
import { DragDropContext, Droppable, Draggable, DropResult } from 'react-beautiful-dnd';
|
||||
import {
|
||||
isPositionChanged,
|
||||
getSortedDraggables,
|
||||
@ -81,7 +81,7 @@ const ChecklistProgressBar = styled.div`
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
`;
|
||||
const ChecklistProgressBarCurrent = styled.div<{width: number}>`
|
||||
const ChecklistProgressBarCurrent = styled.div<{ width: number }>`
|
||||
width: ${props => props.width}%;
|
||||
background: rgba(${props => (props.width === 100 ? props.theme.colors.success : props.theme.colors.primary)});
|
||||
bottom: 0;
|
||||
@ -132,7 +132,7 @@ const ChecklistItemTextControls = styled.div`
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const ChecklistItemText = styled.span<{complete: boolean}>`
|
||||
const ChecklistItemText = styled.span<{ complete: boolean }>`
|
||||
color: ${props => (props.complete ? '#5e6c84' : `rgba(${props.theme.colors.text.primary})`)};
|
||||
${props => props.complete && 'text-decoration: line-through;'}
|
||||
line-height: 20px;
|
||||
@ -156,11 +156,11 @@ const ControlButton = styled.div`
|
||||
padding: 4px 6px;
|
||||
border-radius: 6px;
|
||||
background-color: rgba(${props => props.theme.colors.bg.primary}, 0.8);
|
||||
display: flex;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
&:hover {
|
||||
background-color: rgba(${props => props.theme.colors.primary}, 1);
|
||||
}
|
||||
@ -212,16 +212,13 @@ const TrashButton = styled(Trash)`
|
||||
fill: rgba(${props => props.theme.colors.text.primary});
|
||||
`;
|
||||
|
||||
const ChecklistItemWrapper = styled.div<{ref: any}>`
|
||||
const ChecklistItemWrapper = styled.div<{ ref: any }>`
|
||||
user-select: none;
|
||||
clear: both;
|
||||
padding-left: 40px;
|
||||
position: relative;
|
||||
border-radius: 6px;
|
||||
transform-origin: left bottom;
|
||||
transition-property: transform, opacity, height, padding, margin;
|
||||
transition-duration: 0.14s;
|
||||
transition-timing-function: ease-in;
|
||||
|
||||
& ${ControlButton}:last-child {
|
||||
margin-right: 4px;
|
||||
}
|
||||
@ -295,7 +292,17 @@ type ChecklistItemProps = {
|
||||
|
||||
export const ChecklistItem = React.forwardRef(
|
||||
(
|
||||
{itemID, checklistID, complete, name, wrapperProps, handleProps, onChangeName, onToggleItem, onDeleteItem}: ChecklistItemProps,
|
||||
{
|
||||
itemID,
|
||||
checklistID,
|
||||
complete,
|
||||
name,
|
||||
wrapperProps,
|
||||
handleProps,
|
||||
onChangeName,
|
||||
onToggleItem,
|
||||
onDeleteItem,
|
||||
}: ChecklistItemProps,
|
||||
$item,
|
||||
) => {
|
||||
const $editor = useRef<HTMLTextAreaElement>(null);
|
||||
@ -319,8 +326,8 @@ export const ChecklistItem = React.forwardRef(
|
||||
{complete ? (
|
||||
<ChecklistItemCheckedIcon width={20} height={20} />
|
||||
) : (
|
||||
<ChecklistItemUncheckedIcon width={20} height={20} />
|
||||
)}
|
||||
<ChecklistItemUncheckedIcon width={20} height={20} />
|
||||
)}
|
||||
</ChecklistIcon>
|
||||
{editting ? (
|
||||
<>
|
||||
@ -370,34 +377,34 @@ export const ChecklistItem = React.forwardRef(
|
||||
</EditControls>
|
||||
</>
|
||||
) : (
|
||||
<ChecklistItemDetails
|
||||
onClick={() => {
|
||||
setEditting(true);
|
||||
}}
|
||||
>
|
||||
<ChecklistItemRow>
|
||||
<ChecklistItemTextControls>
|
||||
<ChecklistItemText complete={complete}>{name}</ChecklistItemText>
|
||||
<ChecklistControls>
|
||||
<ControlButton>
|
||||
<AssignUserButton width={14} height={14} />
|
||||
</ControlButton>
|
||||
<ControlButton>
|
||||
<ClockButton width={14} height={14} />
|
||||
</ControlButton>
|
||||
<ControlButton
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
onDeleteItem(checklistID, itemID);
|
||||
}}
|
||||
>
|
||||
<TrashButton width={14} height={14} />
|
||||
</ControlButton>
|
||||
</ChecklistControls>
|
||||
</ChecklistItemTextControls>
|
||||
</ChecklistItemRow>
|
||||
</ChecklistItemDetails>
|
||||
)}
|
||||
<ChecklistItemDetails
|
||||
onClick={() => {
|
||||
setEditting(true);
|
||||
}}
|
||||
>
|
||||
<ChecklistItemRow>
|
||||
<ChecklistItemTextControls>
|
||||
<ChecklistItemText complete={complete}>{name}</ChecklistItemText>
|
||||
<ChecklistControls>
|
||||
<ControlButton>
|
||||
<AssignUserButton width={14} height={14} />
|
||||
</ControlButton>
|
||||
<ControlButton>
|
||||
<ClockButton width={14} height={14} />
|
||||
</ControlButton>
|
||||
<ControlButton
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
onDeleteItem(checklistID, itemID);
|
||||
}}
|
||||
>
|
||||
<TrashButton width={14} height={14} />
|
||||
</ControlButton>
|
||||
</ChecklistControls>
|
||||
</ChecklistItemTextControls>
|
||||
</ChecklistItemRow>
|
||||
</ChecklistItemDetails>
|
||||
)}
|
||||
</ChecklistItemWrapper>
|
||||
);
|
||||
},
|
||||
@ -407,7 +414,7 @@ type AddNewItemProps = {
|
||||
onAddItem: (name: string) => void;
|
||||
};
|
||||
|
||||
const AddNewItem: React.FC<AddNewItemProps> = ({onAddItem}) => {
|
||||
const AddNewItem: React.FC<AddNewItemProps> = ({ onAddItem }) => {
|
||||
const $editor = useRef<HTMLTextAreaElement>(null);
|
||||
const $wrapper = useRef<HTMLDivElement>(null);
|
||||
const [currentName, setCurrentName] = useState('');
|
||||
@ -464,8 +471,8 @@ const AddNewItem: React.FC<AddNewItemProps> = ({onAddItem}) => {
|
||||
</EditControls>
|
||||
</>
|
||||
) : (
|
||||
<NewItemButton onClick={() => setEditting(true)}>Add an item</NewItemButton>
|
||||
)}
|
||||
<NewItemButton onClick={() => setEditting(true)}>Add an item</NewItemButton>
|
||||
)}
|
||||
</ChecklistNewItem>
|
||||
);
|
||||
};
|
||||
@ -477,7 +484,7 @@ type ChecklistTitleEditorProps = {
|
||||
};
|
||||
|
||||
const ChecklistTitleEditor = React.forwardRef(
|
||||
({name, onChangeName, onCancel}: ChecklistTitleEditorProps, $name: any) => {
|
||||
({ name, onChangeName, onCancel }: ChecklistTitleEditorProps, $name: any) => {
|
||||
const [currentName, setCurrentName] = useState(name);
|
||||
return (
|
||||
<>
|
||||
@ -579,21 +586,21 @@ const Checklist = React.forwardRef(
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<WindowChecklistTitle {...handleProps}>
|
||||
<WindowTitleText onClick={() => setEditting(true)}>{name}</WindowTitleText>
|
||||
<WindowOptions>
|
||||
<DeleteButton
|
||||
onClick={$target => {
|
||||
onDeleteChecklist($target, checklistID);
|
||||
}}
|
||||
color="danger"
|
||||
variant="outline"
|
||||
>
|
||||
Delete
|
||||
<WindowChecklistTitle {...handleProps}>
|
||||
<WindowTitleText onClick={() => setEditting(true)}>{name}</WindowTitleText>
|
||||
<WindowOptions>
|
||||
<DeleteButton
|
||||
onClick={$target => {
|
||||
onDeleteChecklist($target, checklistID);
|
||||
}}
|
||||
color="danger"
|
||||
variant="outline"
|
||||
>
|
||||
Delete
|
||||
</DeleteButton>
|
||||
</WindowOptions>
|
||||
</WindowChecklistTitle>
|
||||
)}
|
||||
</WindowOptions>
|
||||
</WindowChecklistTitle>
|
||||
)}
|
||||
</WindowTitle>
|
||||
<ChecklistProgress>
|
||||
<ChecklistProgressPercent>{`${percent}%`}</ChecklistProgressPercent>
|
||||
|
@ -1,6 +1,14 @@
|
||||
import React, { useRef } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import TaskAssignee from 'shared/components/TaskAssignee';
|
||||
import { Checkmark } from 'shared/icons';
|
||||
|
||||
const CardCheckmark = styled(Checkmark)`
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
margin: 11px;
|
||||
`;
|
||||
const CardMember = styled.div<{ bgColor: string }>`
|
||||
height: 28px;
|
||||
width: 28px;
|
||||
@ -34,6 +42,7 @@ type MemberProps = {
|
||||
member: TaskUser;
|
||||
showName?: boolean;
|
||||
className?: string;
|
||||
showCheckmark?: boolean;
|
||||
};
|
||||
|
||||
const CardMemberWrapper = styled.div<{ ref: any }>`
|
||||
@ -46,7 +55,14 @@ const CardMemberName = styled.span`
|
||||
padding-left: 8px;
|
||||
`;
|
||||
|
||||
const Member: React.FC<MemberProps> = ({ onCardMemberClick, taskID, member, showName, className }) => {
|
||||
const Member: React.FC<MemberProps> = ({
|
||||
onCardMemberClick,
|
||||
taskID,
|
||||
member,
|
||||
showName,
|
||||
showCheckmark = false,
|
||||
className,
|
||||
}) => {
|
||||
const $targetRef = useRef<HTMLDivElement>();
|
||||
return (
|
||||
<CardMemberWrapper
|
||||
@ -60,10 +76,9 @@ const Member: React.FC<MemberProps> = ({ onCardMemberClick, taskID, member, show
|
||||
}
|
||||
}}
|
||||
>
|
||||
<CardMember bgColor={member.profileIcon.bgColor ?? '#7367F0'}>
|
||||
<CardMemberInitials>{member.profileIcon.initials}</CardMemberInitials>
|
||||
</CardMember>
|
||||
<TaskAssignee onMemberProfile={() => {}} size={28} member={member} />
|
||||
{showName && <CardMemberName>{member.fullName}</CardMemberName>}
|
||||
{showCheckmark && <CardCheckmark width={12} height={12} />}
|
||||
</CardMemberWrapper>
|
||||
);
|
||||
};
|
||||
|
@ -1,6 +1,7 @@
|
||||
import styled from 'styled-components';
|
||||
import TextareaAutosize from 'react-autosize-textarea/lib';
|
||||
import { mixin } from 'shared/utils/styles';
|
||||
import Member from '../Member';
|
||||
|
||||
export const MemberManagerWrapper = styled.div``;
|
||||
|
||||
@ -48,7 +49,7 @@ export const BoardMembersList = styled.ul`
|
||||
|
||||
export const BoardMembersListItem = styled.li``;
|
||||
|
||||
export const BoardMemberListItemContent = styled.div`
|
||||
export const BoardMemberListItemContent = styled(Member)`
|
||||
background-color: rgba(9, 30, 66, 0.04);
|
||||
padding-right: 28px;
|
||||
border-radius: 3px;
|
||||
@ -64,6 +65,11 @@ export const BoardMemberListItemContent = styled.div`
|
||||
padding: 4px;
|
||||
margin-bottom: 2px;
|
||||
color: #c2c6dc;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(${props => props.theme.colors.primary});
|
||||
color: rgba(${props => props.theme.colors.text.secondary});
|
||||
}
|
||||
`;
|
||||
|
||||
export const ProfileIcon = styled.div`
|
||||
|
@ -13,6 +13,7 @@ import {
|
||||
ActiveIconWrapper,
|
||||
} from './Styles';
|
||||
import { Checkmark } from 'shared/icons';
|
||||
import Member from 'shared/components/Member';
|
||||
|
||||
type MemberManagerProps = {
|
||||
availableMembers: Array<TaskUser>;
|
||||
@ -45,7 +46,10 @@ const MemberManager: React.FC<MemberManagerProps> = ({
|
||||
return (
|
||||
<BoardMembersListItem key={member.id}>
|
||||
<BoardMemberListItemContent
|
||||
onClick={() => {
|
||||
member={member}
|
||||
showName
|
||||
showCheckmark={activeMembers.findIndex(m => m.id === member.id) !== -1}
|
||||
onCardMemberClick={() => {
|
||||
const isActive = activeMembers.findIndex(m => m.id === member.id) !== -1;
|
||||
if (isActive) {
|
||||
setActiveMembers(activeMembers.filter(m => m.id !== member.id));
|
||||
@ -54,15 +58,7 @@ const MemberManager: React.FC<MemberManagerProps> = ({
|
||||
}
|
||||
onMemberChange(member, !isActive);
|
||||
}}
|
||||
>
|
||||
<ProfileIcon>JK</ProfileIcon>
|
||||
<MemberName>{member.fullName}</MemberName>
|
||||
{activeMembers.findIndex(m => m.id === member.id) !== -1 && (
|
||||
<ActiveIconWrapper>
|
||||
<Checkmark width={16} height={16} />
|
||||
</ActiveIconWrapper>
|
||||
)}
|
||||
</BoardMemberListItemContent>
|
||||
/>
|
||||
</BoardMembersListItem>
|
||||
);
|
||||
})}
|
||||
|
@ -74,7 +74,13 @@ export const EditorButton = styled.div`
|
||||
margin: 0 0 4px 8px;
|
||||
padding: 6px 12px 6px 8px;
|
||||
text-decoration: none;
|
||||
transition: transform 85ms ease-in;
|
||||
transition: all 85ms ease-in;
|
||||
|
||||
&:hover {
|
||||
transform: translateX(5px);
|
||||
background: rgba(0, 0, 0, 1);
|
||||
color: #fff;
|
||||
}
|
||||
`;
|
||||
|
||||
export const CloseButton = styled.div`
|
||||
|
@ -420,33 +420,35 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
|
||||
>
|
||||
<Droppable direction="vertical" type="checklistItem" droppableId={checklist.id}>
|
||||
{checklistDrop => (
|
||||
<ChecklistItems ref={checklistDrop.innerRef} {...checklistDrop.droppableProps}>
|
||||
{checklist.items
|
||||
.slice()
|
||||
.sort((a, b) => a.position - b.position)
|
||||
.map((item, itemIdx) => (
|
||||
<Draggable key={item.id} draggableId={item.id} index={itemIdx}>
|
||||
{itemDrop => (
|
||||
<ChecklistItem
|
||||
key={item.id}
|
||||
itemID={item.id}
|
||||
checklistID={item.taskChecklistID}
|
||||
ref={itemDrop.innerRef}
|
||||
wrapperProps={itemDrop.draggableProps}
|
||||
handleProps={itemDrop.dragHandleProps}
|
||||
name={item.name}
|
||||
complete={item.complete}
|
||||
onDeleteItem={onDeleteItem}
|
||||
onChangeName={onChangeItemName}
|
||||
onToggleItem={(itemID, complete) =>
|
||||
onToggleChecklistItem(item.id, complete)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</Draggable>
|
||||
))}
|
||||
<>
|
||||
<ChecklistItems ref={checklistDrop.innerRef} {...checklistDrop.droppableProps}>
|
||||
{checklist.items
|
||||
.slice()
|
||||
.sort((a, b) => a.position - b.position)
|
||||
.map((item, itemIdx) => (
|
||||
<Draggable key={item.id} draggableId={item.id} index={itemIdx}>
|
||||
{itemDrop => (
|
||||
<ChecklistItem
|
||||
key={item.id}
|
||||
itemID={item.id}
|
||||
checklistID={item.taskChecklistID}
|
||||
ref={itemDrop.innerRef}
|
||||
wrapperProps={itemDrop.draggableProps}
|
||||
handleProps={itemDrop.dragHandleProps}
|
||||
name={item.name}
|
||||
complete={item.complete}
|
||||
onDeleteItem={onDeleteItem}
|
||||
onChangeName={onChangeItemName}
|
||||
onToggleItem={(itemID, complete) =>
|
||||
onToggleChecklistItem(item.id, complete)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</Draggable>
|
||||
))}
|
||||
</ChecklistItems>
|
||||
{checklistDrop.placeholder}
|
||||
</ChecklistItems>
|
||||
</>
|
||||
)}
|
||||
</Droppable>
|
||||
</Checklist>
|
||||
|
@ -923,6 +923,7 @@ export type LogoutUser = {
|
||||
|
||||
export type DeleteUserAccount = {
|
||||
userID: Scalars['UUID'];
|
||||
newOwnerID?: Maybe<Scalars['UUID']>;
|
||||
};
|
||||
|
||||
export type DeleteUserAccountPayload = {
|
||||
@ -1886,12 +1887,31 @@ export type CreateUserAccountMutation = (
|
||||
), role: (
|
||||
{ __typename?: 'Role' }
|
||||
& Pick<Role, 'code' | 'name'>
|
||||
), owned: (
|
||||
{ __typename?: 'OwnedList' }
|
||||
& { teams: Array<(
|
||||
{ __typename?: 'Team' }
|
||||
& Pick<Team, 'id' | 'name'>
|
||||
)>, projects: Array<(
|
||||
{ __typename?: 'Project' }
|
||||
& Pick<Project, 'id' | 'name'>
|
||||
)> }
|
||||
), member: (
|
||||
{ __typename?: 'MemberList' }
|
||||
& { teams: Array<(
|
||||
{ __typename?: 'Team' }
|
||||
& Pick<Team, 'id' | 'name'>
|
||||
)>, projects: Array<(
|
||||
{ __typename?: 'Project' }
|
||||
& Pick<Project, 'id' | 'name'>
|
||||
)> }
|
||||
) }
|
||||
) }
|
||||
);
|
||||
|
||||
export type DeleteUserAccountMutationVariables = {
|
||||
userID: Scalars['UUID'];
|
||||
newOwnerID?: Maybe<Scalars['UUID']>;
|
||||
};
|
||||
|
||||
|
||||
@ -3851,6 +3871,26 @@ export const CreateUserAccountDocument = gql`
|
||||
code
|
||||
name
|
||||
}
|
||||
owned {
|
||||
teams {
|
||||
id
|
||||
name
|
||||
}
|
||||
projects {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
member {
|
||||
teams {
|
||||
id
|
||||
name
|
||||
}
|
||||
projects {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
@ -3885,8 +3925,8 @@ export type CreateUserAccountMutationHookResult = ReturnType<typeof useCreateUse
|
||||
export type CreateUserAccountMutationResult = ApolloReactCommon.MutationResult<CreateUserAccountMutation>;
|
||||
export type CreateUserAccountMutationOptions = ApolloReactCommon.BaseMutationOptions<CreateUserAccountMutation, CreateUserAccountMutationVariables>;
|
||||
export const DeleteUserAccountDocument = gql`
|
||||
mutation deleteUserAccount($userID: UUID!) {
|
||||
deleteUserAccount(input: {userID: $userID}) {
|
||||
mutation deleteUserAccount($userID: UUID!, $newOwnerID: UUID) {
|
||||
deleteUserAccount(input: {userID: $userID, newOwnerID: $newOwnerID}) {
|
||||
ok
|
||||
userAccount {
|
||||
id
|
||||
@ -3910,6 +3950,7 @@ export type DeleteUserAccountMutationFn = ApolloReactCommon.MutationFunction<Del
|
||||
* const [deleteUserAccountMutation, { data, loading, error }] = useDeleteUserAccountMutation({
|
||||
* variables: {
|
||||
* userID: // value for 'userID'
|
||||
* newOwnerID: // value for 'newOwnerID'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
|
@ -33,6 +33,26 @@ export const CREATE_USER_MUTATION = gql`
|
||||
code
|
||||
name
|
||||
}
|
||||
owned {
|
||||
teams {
|
||||
id
|
||||
name
|
||||
}
|
||||
projects {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
member {
|
||||
teams {
|
||||
id
|
||||
name
|
||||
}
|
||||
projects {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
@ -1,8 +1,8 @@
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
export const DELETE_USER_MUTATION = gql`
|
||||
mutation deleteUserAccount($userID: UUID!) {
|
||||
deleteUserAccount(input: { userID: $userID }) {
|
||||
mutation deleteUserAccount($userID: UUID!, $newOwnerID: UUID) {
|
||||
deleteUserAccount(input: { userID: $userID, newOwnerID: $newOwnerID }) {
|
||||
ok
|
||||
userAccount {
|
||||
id
|
||||
|
@ -3,8 +3,8 @@ import Icon, { IconProps } from './Icon';
|
||||
|
||||
const Checkmark: React.FC<IconProps> = ({ width = '16px', height = '16px', className }) => {
|
||||
return (
|
||||
<Icon width={width} height={height} className={className} viewBox="0 0 16 16">
|
||||
<path d="M13.5 2l-7.5 7.5-3.5-3.5-2.5 2.5 6 6 10-10z" />
|
||||
<Icon width={width} height={height} className={className} viewBox="0 0 512 512">
|
||||
<path d="M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z" />
|
||||
</Icon>
|
||||
);
|
||||
};
|
||||
|
@ -384,3 +384,35 @@ func (q *Queries) UpdateProjectNameByID(ctx context.Context, arg UpdateProjectNa
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const updateProjectOwnerByOwnerID = `-- name: UpdateProjectOwnerByOwnerID :many
|
||||
UPDATE project SET owner = $2 WHERE owner = $1 RETURNING project_id
|
||||
`
|
||||
|
||||
type UpdateProjectOwnerByOwnerIDParams struct {
|
||||
Owner uuid.UUID `json:"owner"`
|
||||
Owner_2 uuid.UUID `json:"owner_2"`
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateProjectOwnerByOwnerID(ctx context.Context, arg UpdateProjectOwnerByOwnerIDParams) ([]uuid.UUID, error) {
|
||||
rows, err := q.db.QueryContext(ctx, updateProjectOwnerByOwnerID, arg.Owner, arg.Owner_2)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []uuid.UUID
|
||||
for rows.Next() {
|
||||
var project_id uuid.UUID
|
||||
if err := rows.Scan(&project_id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, project_id)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
@ -95,6 +95,7 @@ type Querier interface {
|
||||
UpdateProjectLabelName(ctx context.Context, arg UpdateProjectLabelNameParams) (ProjectLabel, error)
|
||||
UpdateProjectMemberRole(ctx context.Context, arg UpdateProjectMemberRoleParams) (ProjectMember, error)
|
||||
UpdateProjectNameByID(ctx context.Context, arg UpdateProjectNameByIDParams) (Project, error)
|
||||
UpdateProjectOwnerByOwnerID(ctx context.Context, arg UpdateProjectOwnerByOwnerIDParams) ([]uuid.UUID, error)
|
||||
UpdateTaskChecklistItemLocation(ctx context.Context, arg UpdateTaskChecklistItemLocationParams) (TaskChecklistItem, error)
|
||||
UpdateTaskChecklistItemName(ctx context.Context, arg UpdateTaskChecklistItemNameParams) (TaskChecklistItem, error)
|
||||
UpdateTaskChecklistName(ctx context.Context, arg UpdateTaskChecklistNameParams) (TaskChecklist, error)
|
||||
@ -105,6 +106,7 @@ type Querier interface {
|
||||
UpdateTaskLocation(ctx context.Context, arg UpdateTaskLocationParams) (Task, error)
|
||||
UpdateTaskName(ctx context.Context, arg UpdateTaskNameParams) (Task, error)
|
||||
UpdateTeamMemberRole(ctx context.Context, arg UpdateTeamMemberRoleParams) (TeamMember, error)
|
||||
UpdateTeamOwnerByOwnerID(ctx context.Context, arg UpdateTeamOwnerByOwnerIDParams) ([]uuid.UUID, error)
|
||||
UpdateUserAccountProfileAvatarURL(ctx context.Context, arg UpdateUserAccountProfileAvatarURLParams) (UserAccount, error)
|
||||
UpdateUserRole(ctx context.Context, arg UpdateUserRoleParams) (UserAccount, error)
|
||||
}
|
||||
|
@ -45,3 +45,6 @@ SELECT * FROM project WHERE owner = $1;
|
||||
|
||||
-- name: GetMemberProjectIDsForUserID :many
|
||||
SELECT project_id FROM project_member WHERE user_id = $1;
|
||||
|
||||
-- name: UpdateProjectOwnerByOwnerID :many
|
||||
UPDATE project SET owner = $2 WHERE owner = $1 RETURNING project_id;
|
||||
|
@ -21,3 +21,6 @@ SELECT * FROM team WHERE owner = $1;
|
||||
|
||||
-- name: GetMemberTeamIDsForUserID :many
|
||||
SELECT team_id FROM team_member WHERE user_id = $1;
|
||||
|
||||
-- name: UpdateTeamOwnerByOwnerID :many
|
||||
UPDATE team SET owner = $2 WHERE owner = $1 RETURNING team_id;
|
||||
|
@ -212,3 +212,35 @@ func (q *Queries) SetTeamOwner(ctx context.Context, arg SetTeamOwnerParams) (Tea
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const updateTeamOwnerByOwnerID = `-- name: UpdateTeamOwnerByOwnerID :many
|
||||
UPDATE team SET owner = $2 WHERE owner = $1 RETURNING team_id
|
||||
`
|
||||
|
||||
type UpdateTeamOwnerByOwnerIDParams struct {
|
||||
Owner uuid.UUID `json:"owner"`
|
||||
Owner_2 uuid.UUID `json:"owner_2"`
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateTeamOwnerByOwnerID(ctx context.Context, arg UpdateTeamOwnerByOwnerIDParams) ([]uuid.UUID, error) {
|
||||
rows, err := q.db.QueryContext(ctx, updateTeamOwnerByOwnerID, arg.Owner, arg.Owner_2)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []uuid.UUID
|
||||
for rows.Next() {
|
||||
var team_id uuid.UUID
|
||||
if err := rows.Scan(&team_id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, team_id)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
@ -2859,6 +2859,7 @@ input LogoutUser {
|
||||
|
||||
input DeleteUserAccount {
|
||||
userID: UUID!
|
||||
newOwnerID: UUID
|
||||
}
|
||||
|
||||
type DeleteUserAccountPayload {
|
||||
@ -12139,6 +12140,12 @@ func (ec *executionContext) unmarshalInputDeleteUserAccount(ctx context.Context,
|
||||
if err != nil {
|
||||
return it, err
|
||||
}
|
||||
case "newOwnerID":
|
||||
var err error
|
||||
it.NewOwnerID, err = ec.unmarshalOUUID2ᚖgithubᚗcomᚋgoogleᚋuuidᚐUUID(ctx, v)
|
||||
if err != nil {
|
||||
return it, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -142,7 +142,8 @@ type DeleteTeamPayload struct {
|
||||
}
|
||||
|
||||
type DeleteUserAccount struct {
|
||||
UserID uuid.UUID `json:"userID"`
|
||||
UserID uuid.UUID `json:"userID"`
|
||||
NewOwnerID *uuid.UUID `json:"newOwnerID"`
|
||||
}
|
||||
|
||||
type DeleteUserAccountPayload struct {
|
||||
|
@ -625,6 +625,7 @@ input LogoutUser {
|
||||
|
||||
input DeleteUserAccount {
|
||||
userID: UUID!
|
||||
newOwnerID: UUID
|
||||
}
|
||||
|
||||
type DeleteUserAccountPayload {
|
||||
|
@ -756,6 +756,30 @@ func (r *mutationResolver) DeleteUserAccount(ctx context.Context, input DeleteUs
|
||||
return &DeleteUserAccountPayload{Ok: false}, err
|
||||
}
|
||||
|
||||
var newOwnerID uuid.UUID
|
||||
if input.NewOwnerID == nil {
|
||||
sysUser, err := r.Repository.GetUserAccountByUsername(ctx, "system")
|
||||
if err != nil {
|
||||
return &DeleteUserAccountPayload{Ok: false}, err
|
||||
}
|
||||
newOwnerID = sysUser.UserID
|
||||
} else {
|
||||
newOwnerID = *input.NewOwnerID
|
||||
}
|
||||
projectIDs, err := r.Repository.UpdateProjectOwnerByOwnerID(ctx, db.UpdateProjectOwnerByOwnerIDParams{Owner: user.UserID, Owner_2: newOwnerID})
|
||||
if err != sql.ErrNoRows && err != nil {
|
||||
return &DeleteUserAccountPayload{Ok: false}, err
|
||||
}
|
||||
for _, projectID := range projectIDs {
|
||||
r.Repository.DeleteProjectMember(ctx, db.DeleteProjectMemberParams{UserID: newOwnerID, ProjectID: projectID})
|
||||
}
|
||||
teamIDs, err := r.Repository.UpdateTeamOwnerByOwnerID(ctx, db.UpdateTeamOwnerByOwnerIDParams{Owner: user.UserID, Owner_2: newOwnerID})
|
||||
if err != sql.ErrNoRows && err != nil {
|
||||
return &DeleteUserAccountPayload{Ok: false}, err
|
||||
}
|
||||
for _, teamID := range teamIDs {
|
||||
r.Repository.DeleteTeamMember(ctx, db.DeleteTeamMemberParams{UserID: newOwnerID, TeamID: teamID})
|
||||
}
|
||||
err = r.Repository.DeleteUserAccountByID(ctx, input.UserID)
|
||||
if err != nil {
|
||||
return &DeleteUserAccountPayload{Ok: false}, err
|
||||
|
@ -47,6 +47,7 @@ input LogoutUser {
|
||||
|
||||
input DeleteUserAccount {
|
||||
userID: UUID!
|
||||
newOwnerID: UUID
|
||||
}
|
||||
|
||||
type DeleteUserAccountPayload {
|
||||
|
Loading…
Reference in New Issue
Block a user