feature: fix user admin related bugs
This commit is contained in:
parent
68fa7aef94
commit
e5bfe9b9ab
@ -214,18 +214,9 @@ const AdminRoute = () => {
|
|||||||
console.log(password);
|
console.log(password);
|
||||||
hidePopup();
|
hidePopup();
|
||||||
}}
|
}}
|
||||||
onDeleteUser={($target, userID) => {
|
onDeleteUser={(userID, newOwnerID) => {
|
||||||
showPopup(
|
deleteUser({ variables: { userID, newOwnerID } });
|
||||||
$target,
|
hidePopup();
|
||||||
<Popup tab={0} title="Delete user?" onClose={() => hidePopup()}>
|
|
||||||
<DeleteUserPopup
|
|
||||||
onDeleteUser={() => {
|
|
||||||
deleteUser({ variables: { userID } });
|
|
||||||
hidePopup();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Popup>,
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
onAddUser={$target => {
|
onAddUser={$target => {
|
||||||
showPopup(
|
showPopup(
|
||||||
|
@ -481,9 +481,9 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({
|
|||||||
activeMembers={task.assigned ?? []}
|
activeMembers={task.assigned ?? []}
|
||||||
onMemberChange={(member, isActive) => {
|
onMemberChange={(member, isActive) => {
|
||||||
if (isActive) {
|
if (isActive) {
|
||||||
assignTask({ variables: { taskID: task.id, userID: userID ?? '' } });
|
assignTask({ variables: { taskID: task.id, userID: member.id } });
|
||||||
} else {
|
} 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) => {
|
onChecklistItemDrop={(prevChecklistID, checklistID, checklistItem) => {
|
||||||
updateTaskChecklistItemLocation({
|
updateTaskChecklistItemLocation({
|
||||||
variables: { checklistID, checklistItemID: checklistItem.id, position: checklistItem.position },
|
variables: { checklistID, checklistItemID: checklistItem.id, position: checklistItem.position },
|
||||||
|
|
||||||
optimisticResponse: {
|
optimisticResponse: {
|
||||||
__typename: 'Mutation',
|
__typename: 'Mutation',
|
||||||
updateTaskChecklistItemLocation: {
|
updateTaskChecklistItemLocation: {
|
||||||
|
@ -104,24 +104,28 @@ export const RemoveMemberButton = styled(Button)`
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
`;
|
`;
|
||||||
type TeamRoleManagerPopupProps = {
|
type TeamRoleManagerPopupProps = {
|
||||||
user: TaskUser;
|
user: User;
|
||||||
|
users: Array<User>;
|
||||||
warning?: string | null;
|
warning?: string | null;
|
||||||
canChangeRole: boolean;
|
canChangeRole: boolean;
|
||||||
onChangeRole: (roleCode: RoleCode) => void;
|
onChangeRole: (roleCode: RoleCode) => void;
|
||||||
updateUserPassword?: (user: TaskUser, password: string) => void;
|
updateUserPassword?: (user: TaskUser, password: string) => void;
|
||||||
onRemoveFromTeam?: () => void;
|
onDeleteUser?: (userID: string, newOwnerID: string | null) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const TeamRoleManagerPopup: React.FC<TeamRoleManagerPopupProps> = ({
|
const TeamRoleManagerPopup: React.FC<TeamRoleManagerPopupProps> = ({
|
||||||
warning,
|
warning,
|
||||||
user,
|
user,
|
||||||
|
users,
|
||||||
canChangeRole,
|
canChangeRole,
|
||||||
onRemoveFromTeam,
|
onDeleteUser,
|
||||||
updateUserPassword,
|
updateUserPassword,
|
||||||
onChangeRole,
|
onChangeRole,
|
||||||
}) => {
|
}) => {
|
||||||
const { hidePopup, setTab } = usePopup();
|
const { hidePopup, setTab } = usePopup();
|
||||||
const [userPass, setUserPass] = useState({ pass: '', passConfirm: '' });
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<Popup title={null} tab={0}>
|
<Popup title={null} tab={0}>
|
||||||
@ -144,7 +148,7 @@ const TeamRoleManagerPopup: React.FC<TeamRoleManagerPopupProps> = ({
|
|||||||
>
|
>
|
||||||
Reset password...
|
Reset password...
|
||||||
</MiniProfileActionItem>
|
</MiniProfileActionItem>
|
||||||
<MiniProfileActionItem onClick={() => setTab(5)}>Remove from organzation...</MiniProfileActionItem>
|
<MiniProfileActionItem onClick={() => setTab(2)}>Remove from organzation...</MiniProfileActionItem>
|
||||||
</MiniProfileActionWrapper>
|
</MiniProfileActionWrapper>
|
||||||
</MiniProfileActions>
|
</MiniProfileActions>
|
||||||
{warning && (
|
{warning && (
|
||||||
@ -198,21 +202,55 @@ const TeamRoleManagerPopup: React.FC<TeamRoleManagerPopupProps> = ({
|
|||||||
)}
|
)}
|
||||||
</MiniProfileActions>
|
</MiniProfileActions>
|
||||||
</Popup>
|
</Popup>
|
||||||
<Popup title="Remove from Team?" onClose={() => hidePopup()} tab={2}>
|
<Popup title="Remove from Organization?" onClose={() => hidePopup()} tab={2}>
|
||||||
<Content>
|
<Content>
|
||||||
<DeleteDescription>
|
<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>
|
</DeleteDescription>
|
||||||
<RemoveMemberButton
|
{hasOwned && (
|
||||||
color="danger"
|
<>
|
||||||
|
<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={() => {
|
onClick={() => {
|
||||||
if (onRemoveFromTeam) {
|
if (onDeleteUser) {
|
||||||
onRemoveFromTeam();
|
console.log(`${!hasOwned} || (${hasOwned} && ${deleteUser})`);
|
||||||
|
if (!hasOwned || (hasOwned && deleteUser)) {
|
||||||
|
onDeleteUser(user.id, deleteUser ? deleteUser.value : null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
color="danger"
|
||||||
>
|
>
|
||||||
Remove Member
|
Delete user
|
||||||
</RemoveMemberButton>
|
</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>
|
</Content>
|
||||||
</Popup>
|
</Popup>
|
||||||
<Popup title="Reset password?" onClose={() => hidePopup()} tab={3}>
|
<Popup title="Reset password?" onClose={() => hidePopup()} tab={3}>
|
||||||
@ -253,18 +291,6 @@ const TeamRoleManagerPopup: React.FC<TeamRoleManagerPopupProps> = ({
|
|||||||
</UserPassConfirmButton>
|
</UserPassConfirmButton>
|
||||||
</Content>
|
</Content>
|
||||||
</Popup>
|
</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 = {
|
type AdminProps = {
|
||||||
initialTab: number;
|
initialTab: number;
|
||||||
onAddUser: ($target: React.RefObject<HTMLElement>) => void;
|
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;
|
onInviteUser: ($target: React.RefObject<HTMLElement>) => void;
|
||||||
users: Array<User>;
|
users: Array<User>;
|
||||||
onUpdateUserPassword: (user: TaskUser, password: string) => void;
|
onUpdateUserPassword: (user: TaskUser, password: string) => void;
|
||||||
@ -700,9 +726,10 @@ const Admin: React.FC<AdminProps> = ({
|
|||||||
<TabContent>
|
<TabContent>
|
||||||
<MemberListWrapper>
|
<MemberListWrapper>
|
||||||
<MemberListHeader>
|
<MemberListHeader>
|
||||||
<ListTitle>{`Users (${users.length})`}</ListTitle>
|
<ListTitle>{`Members (${users.length})`}</ListTitle>
|
||||||
<ListDesc>
|
<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>
|
</ListDesc>
|
||||||
<ListActions>
|
<ListActions>
|
||||||
<FilterSearch width="250px" variant="alternate" placeholder="Filter by name" />
|
<FilterSearch width="250px" variant="alternate" placeholder="Filter by name" />
|
||||||
@ -735,6 +762,7 @@ const Admin: React.FC<AdminProps> = ({
|
|||||||
$target,
|
$target,
|
||||||
<TeamRoleManagerPopup
|
<TeamRoleManagerPopup
|
||||||
user={member}
|
user={member}
|
||||||
|
users={users}
|
||||||
warning={member.role && member.role.code === 'owner' ? warning : null}
|
warning={member.role && member.role.code === 'owner' ? warning : null}
|
||||||
updateUserPassword={(user, password) => {
|
updateUserPassword={(user, password) => {
|
||||||
onUpdateUserPassword(user, password);
|
onUpdateUserPassword(user, password);
|
||||||
@ -743,13 +771,7 @@ const Admin: React.FC<AdminProps> = ({
|
|||||||
onChangeRole={roleCode => {
|
onChangeRole={roleCode => {
|
||||||
updateUserRole({ variables: { userID: member.id, roleCode } });
|
updateUserRole({ variables: { userID: member.id, roleCode } });
|
||||||
}}
|
}}
|
||||||
onRemoveFromTeam={
|
onDeleteUser={onDeleteUser}
|
||||||
member.role && member.role.code === 'owner'
|
|
||||||
? undefined
|
|
||||||
: () => {
|
|
||||||
hidePopup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, {useState, useRef, useEffect} from 'react';
|
import React, { useState, useRef, useEffect } from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import {CheckSquare, Trash, Square, CheckSquareOutline, Clock, Cross, AccountPlus} from 'shared/icons';
|
import { CheckSquare, Trash, Square, CheckSquareOutline, Clock, Cross, AccountPlus } from 'shared/icons';
|
||||||
import {DragDropContext, Droppable, Draggable, DropResult} from 'react-beautiful-dnd';
|
import { DragDropContext, Droppable, Draggable, DropResult } from 'react-beautiful-dnd';
|
||||||
import {
|
import {
|
||||||
isPositionChanged,
|
isPositionChanged,
|
||||||
getSortedDraggables,
|
getSortedDraggables,
|
||||||
@ -81,7 +81,7 @@ const ChecklistProgressBar = styled.div`
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
`;
|
`;
|
||||||
const ChecklistProgressBarCurrent = styled.div<{width: number}>`
|
const ChecklistProgressBarCurrent = styled.div<{ width: number }>`
|
||||||
width: ${props => props.width}%;
|
width: ${props => props.width}%;
|
||||||
background: rgba(${props => (props.width === 100 ? props.theme.colors.success : props.theme.colors.primary)});
|
background: rgba(${props => (props.width === 100 ? props.theme.colors.success : props.theme.colors.primary)});
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
@ -132,7 +132,7 @@ const ChecklistItemTextControls = styled.div`
|
|||||||
align-items: center;
|
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})`)};
|
color: ${props => (props.complete ? '#5e6c84' : `rgba(${props.theme.colors.text.primary})`)};
|
||||||
${props => props.complete && 'text-decoration: line-through;'}
|
${props => props.complete && 'text-decoration: line-through;'}
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
@ -156,11 +156,11 @@ const ControlButton = styled.div`
|
|||||||
padding: 4px 6px;
|
padding: 4px 6px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
background-color: rgba(${props => props.theme.colors.bg.primary}, 0.8);
|
background-color: rgba(${props => props.theme.colors.bg.primary}, 0.8);
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 32px;
|
width: 32px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: rgba(${props => props.theme.colors.primary}, 1);
|
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});
|
fill: rgba(${props => props.theme.colors.text.primary});
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const ChecklistItemWrapper = styled.div<{ref: any}>`
|
const ChecklistItemWrapper = styled.div<{ ref: any }>`
|
||||||
user-select: none;
|
user-select: none;
|
||||||
clear: both;
|
clear: both;
|
||||||
padding-left: 40px;
|
padding-left: 40px;
|
||||||
position: relative;
|
position: relative;
|
||||||
border-radius: 6px;
|
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 {
|
& ${ControlButton}:last-child {
|
||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
}
|
}
|
||||||
@ -295,7 +292,17 @@ type ChecklistItemProps = {
|
|||||||
|
|
||||||
export const ChecklistItem = React.forwardRef(
|
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,
|
$item,
|
||||||
) => {
|
) => {
|
||||||
const $editor = useRef<HTMLTextAreaElement>(null);
|
const $editor = useRef<HTMLTextAreaElement>(null);
|
||||||
@ -319,8 +326,8 @@ export const ChecklistItem = React.forwardRef(
|
|||||||
{complete ? (
|
{complete ? (
|
||||||
<ChecklistItemCheckedIcon width={20} height={20} />
|
<ChecklistItemCheckedIcon width={20} height={20} />
|
||||||
) : (
|
) : (
|
||||||
<ChecklistItemUncheckedIcon width={20} height={20} />
|
<ChecklistItemUncheckedIcon width={20} height={20} />
|
||||||
)}
|
)}
|
||||||
</ChecklistIcon>
|
</ChecklistIcon>
|
||||||
{editting ? (
|
{editting ? (
|
||||||
<>
|
<>
|
||||||
@ -370,34 +377,34 @@ export const ChecklistItem = React.forwardRef(
|
|||||||
</EditControls>
|
</EditControls>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<ChecklistItemDetails
|
<ChecklistItemDetails
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setEditting(true);
|
setEditting(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ChecklistItemRow>
|
<ChecklistItemRow>
|
||||||
<ChecklistItemTextControls>
|
<ChecklistItemTextControls>
|
||||||
<ChecklistItemText complete={complete}>{name}</ChecklistItemText>
|
<ChecklistItemText complete={complete}>{name}</ChecklistItemText>
|
||||||
<ChecklistControls>
|
<ChecklistControls>
|
||||||
<ControlButton>
|
<ControlButton>
|
||||||
<AssignUserButton width={14} height={14} />
|
<AssignUserButton width={14} height={14} />
|
||||||
</ControlButton>
|
</ControlButton>
|
||||||
<ControlButton>
|
<ControlButton>
|
||||||
<ClockButton width={14} height={14} />
|
<ClockButton width={14} height={14} />
|
||||||
</ControlButton>
|
</ControlButton>
|
||||||
<ControlButton
|
<ControlButton
|
||||||
onClick={e => {
|
onClick={e => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onDeleteItem(checklistID, itemID);
|
onDeleteItem(checklistID, itemID);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TrashButton width={14} height={14} />
|
<TrashButton width={14} height={14} />
|
||||||
</ControlButton>
|
</ControlButton>
|
||||||
</ChecklistControls>
|
</ChecklistControls>
|
||||||
</ChecklistItemTextControls>
|
</ChecklistItemTextControls>
|
||||||
</ChecklistItemRow>
|
</ChecklistItemRow>
|
||||||
</ChecklistItemDetails>
|
</ChecklistItemDetails>
|
||||||
)}
|
)}
|
||||||
</ChecklistItemWrapper>
|
</ChecklistItemWrapper>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -407,7 +414,7 @@ type AddNewItemProps = {
|
|||||||
onAddItem: (name: string) => void;
|
onAddItem: (name: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const AddNewItem: React.FC<AddNewItemProps> = ({onAddItem}) => {
|
const AddNewItem: React.FC<AddNewItemProps> = ({ onAddItem }) => {
|
||||||
const $editor = useRef<HTMLTextAreaElement>(null);
|
const $editor = useRef<HTMLTextAreaElement>(null);
|
||||||
const $wrapper = useRef<HTMLDivElement>(null);
|
const $wrapper = useRef<HTMLDivElement>(null);
|
||||||
const [currentName, setCurrentName] = useState('');
|
const [currentName, setCurrentName] = useState('');
|
||||||
@ -464,8 +471,8 @@ const AddNewItem: React.FC<AddNewItemProps> = ({onAddItem}) => {
|
|||||||
</EditControls>
|
</EditControls>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<NewItemButton onClick={() => setEditting(true)}>Add an item</NewItemButton>
|
<NewItemButton onClick={() => setEditting(true)}>Add an item</NewItemButton>
|
||||||
)}
|
)}
|
||||||
</ChecklistNewItem>
|
</ChecklistNewItem>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -477,7 +484,7 @@ type ChecklistTitleEditorProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const ChecklistTitleEditor = React.forwardRef(
|
const ChecklistTitleEditor = React.forwardRef(
|
||||||
({name, onChangeName, onCancel}: ChecklistTitleEditorProps, $name: any) => {
|
({ name, onChangeName, onCancel }: ChecklistTitleEditorProps, $name: any) => {
|
||||||
const [currentName, setCurrentName] = useState(name);
|
const [currentName, setCurrentName] = useState(name);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -579,21 +586,21 @@ const Checklist = React.forwardRef(
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<WindowChecklistTitle {...handleProps}>
|
<WindowChecklistTitle {...handleProps}>
|
||||||
<WindowTitleText onClick={() => setEditting(true)}>{name}</WindowTitleText>
|
<WindowTitleText onClick={() => setEditting(true)}>{name}</WindowTitleText>
|
||||||
<WindowOptions>
|
<WindowOptions>
|
||||||
<DeleteButton
|
<DeleteButton
|
||||||
onClick={$target => {
|
onClick={$target => {
|
||||||
onDeleteChecklist($target, checklistID);
|
onDeleteChecklist($target, checklistID);
|
||||||
}}
|
}}
|
||||||
color="danger"
|
color="danger"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
</DeleteButton>
|
</DeleteButton>
|
||||||
</WindowOptions>
|
</WindowOptions>
|
||||||
</WindowChecklistTitle>
|
</WindowChecklistTitle>
|
||||||
)}
|
)}
|
||||||
</WindowTitle>
|
</WindowTitle>
|
||||||
<ChecklistProgress>
|
<ChecklistProgress>
|
||||||
<ChecklistProgressPercent>{`${percent}%`}</ChecklistProgressPercent>
|
<ChecklistProgressPercent>{`${percent}%`}</ChecklistProgressPercent>
|
||||||
|
@ -1,6 +1,14 @@
|
|||||||
import React, { useRef } from 'react';
|
import React, { useRef } from 'react';
|
||||||
import styled from 'styled-components';
|
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 }>`
|
const CardMember = styled.div<{ bgColor: string }>`
|
||||||
height: 28px;
|
height: 28px;
|
||||||
width: 28px;
|
width: 28px;
|
||||||
@ -34,6 +42,7 @@ type MemberProps = {
|
|||||||
member: TaskUser;
|
member: TaskUser;
|
||||||
showName?: boolean;
|
showName?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
showCheckmark?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const CardMemberWrapper = styled.div<{ ref: any }>`
|
const CardMemberWrapper = styled.div<{ ref: any }>`
|
||||||
@ -46,7 +55,14 @@ const CardMemberName = styled.span`
|
|||||||
padding-left: 8px;
|
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>();
|
const $targetRef = useRef<HTMLDivElement>();
|
||||||
return (
|
return (
|
||||||
<CardMemberWrapper
|
<CardMemberWrapper
|
||||||
@ -60,10 +76,9 @@ const Member: React.FC<MemberProps> = ({ onCardMemberClick, taskID, member, show
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CardMember bgColor={member.profileIcon.bgColor ?? '#7367F0'}>
|
<TaskAssignee onMemberProfile={() => {}} size={28} member={member} />
|
||||||
<CardMemberInitials>{member.profileIcon.initials}</CardMemberInitials>
|
|
||||||
</CardMember>
|
|
||||||
{showName && <CardMemberName>{member.fullName}</CardMemberName>}
|
{showName && <CardMemberName>{member.fullName}</CardMemberName>}
|
||||||
|
{showCheckmark && <CardCheckmark width={12} height={12} />}
|
||||||
</CardMemberWrapper>
|
</CardMemberWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import TextareaAutosize from 'react-autosize-textarea/lib';
|
import TextareaAutosize from 'react-autosize-textarea/lib';
|
||||||
import { mixin } from 'shared/utils/styles';
|
import { mixin } from 'shared/utils/styles';
|
||||||
|
import Member from '../Member';
|
||||||
|
|
||||||
export const MemberManagerWrapper = styled.div``;
|
export const MemberManagerWrapper = styled.div``;
|
||||||
|
|
||||||
@ -48,7 +49,7 @@ export const BoardMembersList = styled.ul`
|
|||||||
|
|
||||||
export const BoardMembersListItem = styled.li``;
|
export const BoardMembersListItem = styled.li``;
|
||||||
|
|
||||||
export const BoardMemberListItemContent = styled.div`
|
export const BoardMemberListItemContent = styled(Member)`
|
||||||
background-color: rgba(9, 30, 66, 0.04);
|
background-color: rgba(9, 30, 66, 0.04);
|
||||||
padding-right: 28px;
|
padding-right: 28px;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
@ -64,6 +65,11 @@ export const BoardMemberListItemContent = styled.div`
|
|||||||
padding: 4px;
|
padding: 4px;
|
||||||
margin-bottom: 2px;
|
margin-bottom: 2px;
|
||||||
color: #c2c6dc;
|
color: #c2c6dc;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba(${props => props.theme.colors.primary});
|
||||||
|
color: rgba(${props => props.theme.colors.text.secondary});
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const ProfileIcon = styled.div`
|
export const ProfileIcon = styled.div`
|
||||||
|
@ -13,6 +13,7 @@ import {
|
|||||||
ActiveIconWrapper,
|
ActiveIconWrapper,
|
||||||
} from './Styles';
|
} from './Styles';
|
||||||
import { Checkmark } from 'shared/icons';
|
import { Checkmark } from 'shared/icons';
|
||||||
|
import Member from 'shared/components/Member';
|
||||||
|
|
||||||
type MemberManagerProps = {
|
type MemberManagerProps = {
|
||||||
availableMembers: Array<TaskUser>;
|
availableMembers: Array<TaskUser>;
|
||||||
@ -45,7 +46,10 @@ const MemberManager: React.FC<MemberManagerProps> = ({
|
|||||||
return (
|
return (
|
||||||
<BoardMembersListItem key={member.id}>
|
<BoardMembersListItem key={member.id}>
|
||||||
<BoardMemberListItemContent
|
<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;
|
const isActive = activeMembers.findIndex(m => m.id === member.id) !== -1;
|
||||||
if (isActive) {
|
if (isActive) {
|
||||||
setActiveMembers(activeMembers.filter(m => m.id !== member.id));
|
setActiveMembers(activeMembers.filter(m => m.id !== member.id));
|
||||||
@ -54,15 +58,7 @@ const MemberManager: React.FC<MemberManagerProps> = ({
|
|||||||
}
|
}
|
||||||
onMemberChange(member, !isActive);
|
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>
|
</BoardMembersListItem>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
@ -74,7 +74,13 @@ export const EditorButton = styled.div`
|
|||||||
margin: 0 0 4px 8px;
|
margin: 0 0 4px 8px;
|
||||||
padding: 6px 12px 6px 8px;
|
padding: 6px 12px 6px 8px;
|
||||||
text-decoration: none;
|
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`
|
export const CloseButton = styled.div`
|
||||||
|
@ -420,33 +420,35 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
|
|||||||
>
|
>
|
||||||
<Droppable direction="vertical" type="checklistItem" droppableId={checklist.id}>
|
<Droppable direction="vertical" type="checklistItem" droppableId={checklist.id}>
|
||||||
{checklistDrop => (
|
{checklistDrop => (
|
||||||
<ChecklistItems ref={checklistDrop.innerRef} {...checklistDrop.droppableProps}>
|
<>
|
||||||
{checklist.items
|
<ChecklistItems ref={checklistDrop.innerRef} {...checklistDrop.droppableProps}>
|
||||||
.slice()
|
{checklist.items
|
||||||
.sort((a, b) => a.position - b.position)
|
.slice()
|
||||||
.map((item, itemIdx) => (
|
.sort((a, b) => a.position - b.position)
|
||||||
<Draggable key={item.id} draggableId={item.id} index={itemIdx}>
|
.map((item, itemIdx) => (
|
||||||
{itemDrop => (
|
<Draggable key={item.id} draggableId={item.id} index={itemIdx}>
|
||||||
<ChecklistItem
|
{itemDrop => (
|
||||||
key={item.id}
|
<ChecklistItem
|
||||||
itemID={item.id}
|
key={item.id}
|
||||||
checklistID={item.taskChecklistID}
|
itemID={item.id}
|
||||||
ref={itemDrop.innerRef}
|
checklistID={item.taskChecklistID}
|
||||||
wrapperProps={itemDrop.draggableProps}
|
ref={itemDrop.innerRef}
|
||||||
handleProps={itemDrop.dragHandleProps}
|
wrapperProps={itemDrop.draggableProps}
|
||||||
name={item.name}
|
handleProps={itemDrop.dragHandleProps}
|
||||||
complete={item.complete}
|
name={item.name}
|
||||||
onDeleteItem={onDeleteItem}
|
complete={item.complete}
|
||||||
onChangeName={onChangeItemName}
|
onDeleteItem={onDeleteItem}
|
||||||
onToggleItem={(itemID, complete) =>
|
onChangeName={onChangeItemName}
|
||||||
onToggleChecklistItem(item.id, complete)
|
onToggleItem={(itemID, complete) =>
|
||||||
}
|
onToggleChecklistItem(item.id, complete)
|
||||||
/>
|
}
|
||||||
)}
|
/>
|
||||||
</Draggable>
|
)}
|
||||||
))}
|
</Draggable>
|
||||||
|
))}
|
||||||
|
</ChecklistItems>
|
||||||
{checklistDrop.placeholder}
|
{checklistDrop.placeholder}
|
||||||
</ChecklistItems>
|
</>
|
||||||
)}
|
)}
|
||||||
</Droppable>
|
</Droppable>
|
||||||
</Checklist>
|
</Checklist>
|
||||||
|
@ -923,6 +923,7 @@ export type LogoutUser = {
|
|||||||
|
|
||||||
export type DeleteUserAccount = {
|
export type DeleteUserAccount = {
|
||||||
userID: Scalars['UUID'];
|
userID: Scalars['UUID'];
|
||||||
|
newOwnerID?: Maybe<Scalars['UUID']>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DeleteUserAccountPayload = {
|
export type DeleteUserAccountPayload = {
|
||||||
@ -1886,12 +1887,31 @@ export type CreateUserAccountMutation = (
|
|||||||
), role: (
|
), role: (
|
||||||
{ __typename?: 'Role' }
|
{ __typename?: 'Role' }
|
||||||
& Pick<Role, 'code' | 'name'>
|
& 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 = {
|
export type DeleteUserAccountMutationVariables = {
|
||||||
userID: Scalars['UUID'];
|
userID: Scalars['UUID'];
|
||||||
|
newOwnerID?: Maybe<Scalars['UUID']>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -3851,6 +3871,26 @@ export const CreateUserAccountDocument = gql`
|
|||||||
code
|
code
|
||||||
name
|
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 CreateUserAccountMutationResult = ApolloReactCommon.MutationResult<CreateUserAccountMutation>;
|
||||||
export type CreateUserAccountMutationOptions = ApolloReactCommon.BaseMutationOptions<CreateUserAccountMutation, CreateUserAccountMutationVariables>;
|
export type CreateUserAccountMutationOptions = ApolloReactCommon.BaseMutationOptions<CreateUserAccountMutation, CreateUserAccountMutationVariables>;
|
||||||
export const DeleteUserAccountDocument = gql`
|
export const DeleteUserAccountDocument = gql`
|
||||||
mutation deleteUserAccount($userID: UUID!) {
|
mutation deleteUserAccount($userID: UUID!, $newOwnerID: UUID) {
|
||||||
deleteUserAccount(input: {userID: $userID}) {
|
deleteUserAccount(input: {userID: $userID, newOwnerID: $newOwnerID}) {
|
||||||
ok
|
ok
|
||||||
userAccount {
|
userAccount {
|
||||||
id
|
id
|
||||||
@ -3910,6 +3950,7 @@ export type DeleteUserAccountMutationFn = ApolloReactCommon.MutationFunction<Del
|
|||||||
* const [deleteUserAccountMutation, { data, loading, error }] = useDeleteUserAccountMutation({
|
* const [deleteUserAccountMutation, { data, loading, error }] = useDeleteUserAccountMutation({
|
||||||
* variables: {
|
* variables: {
|
||||||
* userID: // value for 'userID'
|
* userID: // value for 'userID'
|
||||||
|
* newOwnerID: // value for 'newOwnerID'
|
||||||
* },
|
* },
|
||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
|
@ -33,6 +33,26 @@ export const CREATE_USER_MUTATION = gql`
|
|||||||
code
|
code
|
||||||
name
|
name
|
||||||
}
|
}
|
||||||
|
owned {
|
||||||
|
teams {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
projects {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
member {
|
||||||
|
teams {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
projects {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import gql from 'graphql-tag';
|
import gql from 'graphql-tag';
|
||||||
|
|
||||||
export const DELETE_USER_MUTATION = gql`
|
export const DELETE_USER_MUTATION = gql`
|
||||||
mutation deleteUserAccount($userID: UUID!) {
|
mutation deleteUserAccount($userID: UUID!, $newOwnerID: UUID) {
|
||||||
deleteUserAccount(input: { userID: $userID }) {
|
deleteUserAccount(input: { userID: $userID, newOwnerID: $newOwnerID }) {
|
||||||
ok
|
ok
|
||||||
userAccount {
|
userAccount {
|
||||||
id
|
id
|
||||||
|
@ -3,8 +3,8 @@ import Icon, { IconProps } from './Icon';
|
|||||||
|
|
||||||
const Checkmark: React.FC<IconProps> = ({ width = '16px', height = '16px', className }) => {
|
const Checkmark: React.FC<IconProps> = ({ width = '16px', height = '16px', className }) => {
|
||||||
return (
|
return (
|
||||||
<Icon width={width} height={height} className={className} viewBox="0 0 16 16">
|
<Icon width={width} height={height} className={className} viewBox="0 0 512 512">
|
||||||
<path d="M13.5 2l-7.5 7.5-3.5-3.5-2.5 2.5 6 6 10-10z" />
|
<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>
|
</Icon>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -384,3 +384,35 @@ func (q *Queries) UpdateProjectNameByID(ctx context.Context, arg UpdateProjectNa
|
|||||||
)
|
)
|
||||||
return i, err
|
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)
|
UpdateProjectLabelName(ctx context.Context, arg UpdateProjectLabelNameParams) (ProjectLabel, error)
|
||||||
UpdateProjectMemberRole(ctx context.Context, arg UpdateProjectMemberRoleParams) (ProjectMember, error)
|
UpdateProjectMemberRole(ctx context.Context, arg UpdateProjectMemberRoleParams) (ProjectMember, error)
|
||||||
UpdateProjectNameByID(ctx context.Context, arg UpdateProjectNameByIDParams) (Project, 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)
|
UpdateTaskChecklistItemLocation(ctx context.Context, arg UpdateTaskChecklistItemLocationParams) (TaskChecklistItem, error)
|
||||||
UpdateTaskChecklistItemName(ctx context.Context, arg UpdateTaskChecklistItemNameParams) (TaskChecklistItem, error)
|
UpdateTaskChecklistItemName(ctx context.Context, arg UpdateTaskChecklistItemNameParams) (TaskChecklistItem, error)
|
||||||
UpdateTaskChecklistName(ctx context.Context, arg UpdateTaskChecklistNameParams) (TaskChecklist, error)
|
UpdateTaskChecklistName(ctx context.Context, arg UpdateTaskChecklistNameParams) (TaskChecklist, error)
|
||||||
@ -105,6 +106,7 @@ type Querier interface {
|
|||||||
UpdateTaskLocation(ctx context.Context, arg UpdateTaskLocationParams) (Task, error)
|
UpdateTaskLocation(ctx context.Context, arg UpdateTaskLocationParams) (Task, error)
|
||||||
UpdateTaskName(ctx context.Context, arg UpdateTaskNameParams) (Task, error)
|
UpdateTaskName(ctx context.Context, arg UpdateTaskNameParams) (Task, error)
|
||||||
UpdateTeamMemberRole(ctx context.Context, arg UpdateTeamMemberRoleParams) (TeamMember, 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)
|
UpdateUserAccountProfileAvatarURL(ctx context.Context, arg UpdateUserAccountProfileAvatarURLParams) (UserAccount, error)
|
||||||
UpdateUserRole(ctx context.Context, arg UpdateUserRoleParams) (UserAccount, error)
|
UpdateUserRole(ctx context.Context, arg UpdateUserRoleParams) (UserAccount, error)
|
||||||
}
|
}
|
||||||
|
@ -45,3 +45,6 @@ SELECT * FROM project WHERE owner = $1;
|
|||||||
|
|
||||||
-- name: GetMemberProjectIDsForUserID :many
|
-- name: GetMemberProjectIDsForUserID :many
|
||||||
SELECT project_id FROM project_member WHERE user_id = $1;
|
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
|
-- name: GetMemberTeamIDsForUserID :many
|
||||||
SELECT team_id FROM team_member WHERE user_id = $1;
|
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
|
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 {
|
input DeleteUserAccount {
|
||||||
userID: UUID!
|
userID: UUID!
|
||||||
|
newOwnerID: UUID
|
||||||
}
|
}
|
||||||
|
|
||||||
type DeleteUserAccountPayload {
|
type DeleteUserAccountPayload {
|
||||||
@ -12139,6 +12140,12 @@ func (ec *executionContext) unmarshalInputDeleteUserAccount(ctx context.Context,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return it, err
|
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 {
|
type DeleteUserAccount struct {
|
||||||
UserID uuid.UUID `json:"userID"`
|
UserID uuid.UUID `json:"userID"`
|
||||||
|
NewOwnerID *uuid.UUID `json:"newOwnerID"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DeleteUserAccountPayload struct {
|
type DeleteUserAccountPayload struct {
|
||||||
|
@ -625,6 +625,7 @@ input LogoutUser {
|
|||||||
|
|
||||||
input DeleteUserAccount {
|
input DeleteUserAccount {
|
||||||
userID: UUID!
|
userID: UUID!
|
||||||
|
newOwnerID: UUID
|
||||||
}
|
}
|
||||||
|
|
||||||
type DeleteUserAccountPayload {
|
type DeleteUserAccountPayload {
|
||||||
|
@ -756,6 +756,30 @@ func (r *mutationResolver) DeleteUserAccount(ctx context.Context, input DeleteUs
|
|||||||
return &DeleteUserAccountPayload{Ok: false}, err
|
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)
|
err = r.Repository.DeleteUserAccountByID(ctx, input.UserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &DeleteUserAccountPayload{Ok: false}, err
|
return &DeleteUserAccountPayload{Ok: false}, err
|
||||||
|
@ -47,6 +47,7 @@ input LogoutUser {
|
|||||||
|
|
||||||
input DeleteUserAccount {
|
input DeleteUserAccount {
|
||||||
userID: UUID!
|
userID: UUID!
|
||||||
|
newOwnerID: UUID
|
||||||
}
|
}
|
||||||
|
|
||||||
type DeleteUserAccountPayload {
|
type DeleteUserAccountPayload {
|
||||||
|
Loading…
Reference in New Issue
Block a user