forked from ParabolInc/parabol
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: managing teams (ParabolInc#9285)
Co-authored-by: Marcus Wermuth <[email protected]> Co-authored-by: Jordan Husney <[email protected]>
- Loading branch information
1 parent
af47966
commit f351cf9
Showing
34 changed files
with
1,058 additions
and
165 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import React, {useState} from 'react' | ||
import FlatPrimaryButton from './FlatPrimaryButton' | ||
import {Input} from '../ui/Input/Input' | ||
import {Dialog} from '../ui/Dialog/Dialog' | ||
import {DialogContent} from '../ui/Dialog/DialogContent' | ||
import {DialogTitle} from '../ui/Dialog/DialogTitle' | ||
import {DialogActions} from '../ui/Dialog/DialogActions' | ||
import useMutationProps from '../hooks/useMutationProps' | ||
import SecondaryButton from './SecondaryButton' | ||
import ArchiveTeamMutation from '../mutations/ArchiveTeamMutation' | ||
import useAtmosphere from '../hooks/useAtmosphere' | ||
import useRouter from '../hooks/useRouter' | ||
|
||
interface Props { | ||
isOpen: boolean | ||
onClose: () => void | ||
onDeleteTeam: (teamId: string) => void | ||
teamId: string | ||
teamName: string | ||
teamOrgId: string | ||
} | ||
|
||
const DeleteTeamDialog = (props: Props) => { | ||
const atmosphere = useAtmosphere() | ||
const {history} = useRouter() | ||
const {isOpen, onClose, teamId, teamName, teamOrgId, onDeleteTeam} = props | ||
|
||
const {submitting, onCompleted, onError, error, submitMutation} = useMutationProps() | ||
|
||
const [typedTeamName, setTypedTeamName] = useState(false) | ||
|
||
const handleDeleteTeam = () => { | ||
if (submitting) return | ||
submitMutation() | ||
ArchiveTeamMutation(atmosphere, {teamId}, {history, onError, onCompleted}) | ||
onDeleteTeam(teamId) | ||
history.push(`/me/organizations/${teamOrgId}/teams`) | ||
} | ||
|
||
return ( | ||
<Dialog isOpen={isOpen} onClose={onClose}> | ||
<DialogContent className='z-10'> | ||
<DialogTitle className='mb-4'>Delete Team</DialogTitle> | ||
|
||
<fieldset className='mx-0 mb-6 flex w-full flex-col p-0'> | ||
<label className='mb-3 text-left text-sm font-semibold text-slate-600'> | ||
Please type your team name to confirm. <b>This action can't be undone.</b> | ||
</label> | ||
<Input | ||
autoFocus | ||
onChange={(e) => { | ||
e.preventDefault() | ||
if (e.target.value === teamName) setTypedTeamName(true) | ||
else setTypedTeamName(false) | ||
}} | ||
placeholder={teamName} | ||
/> | ||
{error && ( | ||
<div className='mt-2 text-sm font-semibold text-tomato-500'>{error.message}</div> | ||
)} | ||
</fieldset> | ||
|
||
<DialogActions> | ||
<FlatPrimaryButton size='medium' onClick={handleDeleteTeam} disabled={!typedTeamName}> | ||
I understand the consequences, delete this team | ||
</FlatPrimaryButton> | ||
<SecondaryButton onClick={onClose}>Cancel</SecondaryButton> | ||
</DialogActions> | ||
</DialogContent> | ||
</Dialog> | ||
) | ||
} | ||
|
||
export default DeleteTeamDialog |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
import graphql from 'babel-plugin-relay/macro' | ||
import React from 'react' | ||
import {useFragment} from 'react-relay' | ||
import useAtmosphere from '~/hooks/useAtmosphere' | ||
import {MenuProps} from '../hooks/useMenu' | ||
import SetOrgUserRoleMutation from '../mutations/SetOrgUserRoleMutation' | ||
import useMutationProps from '../hooks/useMutationProps' | ||
import {OrgAdminActionMenu_organization$key} from '../__generated__/OrgAdminActionMenu_organization.graphql' | ||
import {OrgAdminActionMenu_organizationUser$key} from '../__generated__/OrgAdminActionMenu_organizationUser.graphql' | ||
import Menu from './Menu' | ||
import MenuItem from './MenuItem' | ||
|
||
interface Props { | ||
menuProps: MenuProps | ||
isViewerLastOrgAdmin: boolean | ||
organizationUser: OrgAdminActionMenu_organizationUser$key | ||
organization: OrgAdminActionMenu_organization$key | ||
toggleLeave: () => void | ||
toggleRemove: () => void | ||
} | ||
|
||
const OrgAdminActionMenu = (props: Props) => { | ||
const { | ||
menuProps, | ||
isViewerLastOrgAdmin, | ||
organizationUser: organizationUserRef, | ||
organization: organizationRef, | ||
toggleLeave, | ||
toggleRemove | ||
} = props | ||
const organization = useFragment( | ||
graphql` | ||
fragment OrgAdminActionMenu_organization on Organization { | ||
id | ||
} | ||
`, | ||
organizationRef | ||
) | ||
const organizationUser = useFragment( | ||
graphql` | ||
fragment OrgAdminActionMenu_organizationUser on OrganizationUser { | ||
role | ||
user { | ||
id | ||
} | ||
} | ||
`, | ||
organizationUserRef | ||
) | ||
const atmosphere = useAtmosphere() | ||
const {onError, onCompleted, submitting, submitMutation} = useMutationProps() | ||
const {id: orgId} = organization | ||
const {viewerId} = atmosphere | ||
const {role, user} = organizationUser | ||
const {id: userId} = user | ||
|
||
const setRole = | ||
(role: 'ORG_ADMIN' | 'BILLING_LEADER' | null = null) => | ||
() => { | ||
if (submitting) return | ||
submitMutation() | ||
const variables = {orgId, userId, role} | ||
SetOrgUserRoleMutation(atmosphere, variables, {onError, onCompleted}) | ||
} | ||
|
||
const isOrgAdmin = role === 'ORG_ADMIN' | ||
const isBillingLeader = role === 'BILLING_LEADER' | ||
const isSelf = viewerId === userId | ||
const canRemoveSelf = isSelf && !isViewerLastOrgAdmin | ||
const roleName = role === 'ORG_ADMIN' ? 'Org Admin' : 'Billing Leader' | ||
|
||
return ( | ||
<> | ||
<Menu ariaLabel={'Select your action'} {...menuProps}> | ||
{!isOrgAdmin && <MenuItem label='Promote to Org Admin' onClick={setRole('ORG_ADMIN')} />} | ||
{!isOrgAdmin && !isBillingLeader && ( | ||
<MenuItem label='Promote to Billing Leader' onClick={setRole('BILLING_LEADER')} /> | ||
)} | ||
{isOrgAdmin && !isSelf && ( | ||
<MenuItem label='Change to Billing Leader' onClick={setRole('BILLING_LEADER')} /> | ||
)} | ||
{((role && !isSelf) || canRemoveSelf) && ( | ||
<MenuItem label={`Remove ${roleName} role`} onClick={setRole(null)} /> | ||
)} | ||
{canRemoveSelf && <MenuItem label='Leave Organization' onClick={toggleLeave} />} | ||
{!isSelf && <MenuItem label='Remove from Organization' onClick={toggleRemove} />} | ||
{isSelf && !canRemoveSelf && <MenuItem label='Contact [email protected] to be removed' />} | ||
</Menu> | ||
</> | ||
) | ||
} | ||
|
||
export default OrgAdminActionMenu |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
71 changes: 71 additions & 0 deletions
71
packages/client/modules/userDashboard/components/OrgTeamMembers/OrgTeamMemberMenu.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import styled from '@emotion/styled' | ||
import React from 'react' | ||
import graphql from 'babel-plugin-relay/macro' | ||
import useAtmosphere from '~/hooks/useAtmosphere' | ||
import {useFragment} from 'react-relay' | ||
import {MenuProps} from '../../../../hooks/useMenu' | ||
import Menu from '../../../../components/Menu' | ||
import MenuItem from '../../../../components/MenuItem' | ||
import MenuItemLabel from '../../../../components/MenuItemLabel' | ||
import {OrgTeamMemberMenu_teamMember$key} from '../../../../__generated__/OrgTeamMemberMenu_teamMember.graphql' | ||
|
||
interface OrgTeamMemberMenuProps { | ||
isLead: boolean | ||
menuProps: MenuProps | ||
isViewerLead: boolean | ||
isViewerOrgAdmin: boolean | ||
manageTeamMemberId?: string | null | ||
teamMember: OrgTeamMemberMenu_teamMember$key | ||
handleNavigate?: () => void | ||
togglePromote: () => void | ||
toggleRemove: () => void | ||
} | ||
|
||
const StyledLabel = styled(MenuItemLabel)({ | ||
padding: '4px 16px' | ||
}) | ||
|
||
export const OrgTeamMemberMenu = (props: OrgTeamMemberMenuProps) => { | ||
const { | ||
isViewerLead, | ||
isViewerOrgAdmin, | ||
teamMember: teamMemberRef, | ||
menuProps, | ||
togglePromote, | ||
toggleRemove | ||
} = props | ||
const teamMember = useFragment( | ||
graphql` | ||
fragment OrgTeamMemberMenu_teamMember on TeamMember { | ||
isSelf | ||
preferredName | ||
userId | ||
isLead | ||
} | ||
`, | ||
teamMemberRef | ||
) | ||
const atmosphere = useAtmosphere() | ||
const {preferredName, userId} = teamMember | ||
const {viewerId} = atmosphere | ||
const isSelf = userId === viewerId | ||
const isViewerTeamAdmin = isViewerLead || isViewerOrgAdmin | ||
|
||
return ( | ||
<Menu ariaLabel={'Select your action'} {...menuProps}> | ||
{isViewerTeamAdmin && (!isSelf || !isViewerLead) && ( | ||
<MenuItem | ||
label={<StyledLabel>Promote {preferredName} to Team Lead</StyledLabel>} | ||
key='promote' | ||
onClick={togglePromote} | ||
/> | ||
)} | ||
{isViewerTeamAdmin && !isSelf && ( | ||
<MenuItem | ||
label={<StyledLabel>Remove {preferredName} from Team</StyledLabel>} | ||
onClick={toggleRemove} | ||
/> | ||
)} | ||
</Menu> | ||
) | ||
} |
Oops, something went wrong.