diff --git a/.vscode/launch.json b/.vscode/launch.json
index 2d3b6382f4e..a7fe78cf299 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -30,7 +30,7 @@
{
"name": "Launch Chrome",
"request": "launch",
- "type": "pwa-chrome",
+ "type": "chrome",
"url": "http://localhost:3000",
"webRoot": "${workspaceFolder}"
},
@@ -38,7 +38,7 @@
"type": "node",
"request": "launch",
"name": "Debug Executor",
- "program": "scripts/runExecutor.js",
+ "program": "scripts/runExecutor.js"
},
{
"type": "node",
diff --git a/packages/client/components/DeleteTeamDialog.tsx b/packages/client/components/DeleteTeamDialog.tsx
new file mode 100644
index 00000000000..d6bb993bbde
--- /dev/null
+++ b/packages/client/components/DeleteTeamDialog.tsx
@@ -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 (
+
+ )
+}
+
+export default DeleteTeamDialog
diff --git a/packages/client/components/OrgAdminActionMenu.tsx b/packages/client/components/OrgAdminActionMenu.tsx
new file mode 100644
index 00000000000..19799e08abb
--- /dev/null
+++ b/packages/client/components/OrgAdminActionMenu.tsx
@@ -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 (
+ <>
+
+ >
+ )
+}
+
+export default OrgAdminActionMenu
diff --git a/packages/client/modules/userDashboard/components/OrgBilling/Organization.tsx b/packages/client/modules/userDashboard/components/OrgBilling/Organization.tsx
index 36ea78f60e3..ceab6eb6e09 100644
--- a/packages/client/modules/userDashboard/components/OrgBilling/Organization.tsx
+++ b/packages/client/modules/userDashboard/components/OrgBilling/Organization.tsx
@@ -21,6 +21,10 @@ const OrgMembers = lazy(
import(/* webpackChunkName: 'OrgMembersRoot' */ '../../containers/OrgMembers/OrgMembersRoot')
)
+const OrgTeamMembers = lazy(
+ () => import(/* webpackChunkName: 'OrgTeamMembers' */ '../OrgTeamMembers/OrgTeamMembersRoot')
+)
+
const OrgDetails = lazy(() => import(/* webpackChunkName: 'OrgDetails' */ './OrgDetails'))
const Authentication = lazy(
() =>
@@ -70,13 +74,7 @@ const Organization = (props: Props) => {
path={`${match.url}/${BILLING_PAGE}`}
render={() => }
/>
- {isBillingLeader && (
- }
- />
- )}
+
{
path={`${match.url}/${AUTHENTICATION_PAGE}`}
render={(p) => }
/>
+ {isBillingLeader && (
+ <>
+ }
+ />
+
+ >
+ )}
)
diff --git a/packages/client/modules/userDashboard/components/OrgMembers/OrgMembers.tsx b/packages/client/modules/userDashboard/components/OrgMembers/OrgMembers.tsx
index a4d365a8e91..e56be6deb45 100644
--- a/packages/client/modules/userDashboard/components/OrgMembers/OrgMembers.tsx
+++ b/packages/client/modules/userDashboard/components/OrgMembers/OrgMembers.tsx
@@ -75,6 +75,10 @@ const OrgMembers = (props: Props) => {
['BILLING_LEADER', 'ORG_ADMIN'].includes(node.role ?? '') ? count + 1 : count,
0
)
+ const orgAdminCount = organizationUsers.edges.reduce(
+ (count, {node}) => (['ORG_ADMIN'].includes(node.role ?? '') ? count + 1 : count),
+ 0
+ )
const exportToCSV = async () => {
const rows = organizationUsers.edges.map((orgUser, idx) => {
@@ -117,6 +121,7 @@ const OrgMembers = (props: Props) => {
diff --git a/packages/client/modules/userDashboard/components/OrgTeamMembers/OrgTeamMemberMenu.tsx b/packages/client/modules/userDashboard/components/OrgTeamMembers/OrgTeamMemberMenu.tsx
new file mode 100644
index 00000000000..7526eb55775
--- /dev/null
+++ b/packages/client/modules/userDashboard/components/OrgTeamMembers/OrgTeamMemberMenu.tsx
@@ -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 (
+
+ )
+}
diff --git a/packages/client/modules/userDashboard/components/OrgTeamMembers/OrgTeamMembers.tsx b/packages/client/modules/userDashboard/components/OrgTeamMembers/OrgTeamMembers.tsx
new file mode 100644
index 00000000000..602a4b48ee5
--- /dev/null
+++ b/packages/client/modules/userDashboard/components/OrgTeamMembers/OrgTeamMembers.tsx
@@ -0,0 +1,104 @@
+import React from 'react'
+import graphql from 'babel-plugin-relay/macro'
+import {ArrowBack} from '@mui/icons-material'
+import {PreloadedQuery, usePreloadedQuery} from 'react-relay'
+import {OrgTeamMembersQuery} from '../../../../__generated__/OrgTeamMembersQuery.graphql'
+import {OrgTeamMembersRow} from './OrgTeamMembersRow'
+import {Button} from '../../../../ui/Button/Button'
+import {Link} from 'react-router-dom'
+import {ORGANIZATIONS} from '../../../../utils/constants'
+import {MenuPosition} from '../../../../hooks/useCoords'
+import useMenu from '../../../../hooks/useMenu'
+import {OrgTeamMembersMenu} from './OrgTeamMembersMenu'
+import {useDialogState} from '../../../../ui/Dialog/useDialogState'
+import DeleteTeamDialog from '../../../../components/DeleteTeamDialog'
+
+interface Props {
+ queryRef: PreloadedQuery
+}
+
+const query = graphql`
+ query OrgTeamMembersQuery($teamId: ID!) {
+ viewer {
+ team(teamId: $teamId) {
+ ...ArchiveTeam_team
+ id
+ billingTier
+ isOrgAdmin
+ isViewerLead
+ name
+ orgId
+ teamMembers(sortBy: "preferredName") {
+ id
+ isNotRemoved
+ ...AddTeamMemberModal_teamMembers
+ ...OrgTeamMembersRow_teamMember
+ }
+ tier
+ }
+ }
+ }
+`
+
+export const OrgTeamMembers = (props: Props) => {
+ const {queryRef} = props
+ const data = usePreloadedQuery(query, queryRef)
+ const {viewer} = data
+ const {team} = viewer
+ const {menuPortal, menuProps} = useMenu(MenuPosition.UPPER_RIGHT)
+
+ const {
+ open: openDeleteTeamDialog,
+ close: closeDeleteTeamDialog,
+ isOpen: isDeleteTeamDialogOpened
+ } = useDialogState()
+
+ if (!team) return null
+ const {isViewerLead, isOrgAdmin: isViewerOrgAdmin, teamMembers} = team
+
+ return (
+
+
+
+
+
{team.name}
+
+
+
+
+
+
+
{teamMembers.length} Active
+
+
+ {teamMembers.map((teamMember) => (
+
+ ))}
+
+
+ {menuPortal(
+
+ )}
+
+ {isDeleteTeamDialogOpened ? (
+
+ ) : null}
+
+ )
+}
diff --git a/packages/client/modules/userDashboard/components/OrgTeamMembers/OrgTeamMembersMenu.tsx b/packages/client/modules/userDashboard/components/OrgTeamMembers/OrgTeamMembersMenu.tsx
new file mode 100644
index 00000000000..bc53031bfc8
--- /dev/null
+++ b/packages/client/modules/userDashboard/components/OrgTeamMembers/OrgTeamMembersMenu.tsx
@@ -0,0 +1,26 @@
+import React from 'react'
+import {MenuProps} from '../../../../hooks/useMenu'
+import Menu from '../../../../components/Menu'
+import MenuItem from '../../../../components/MenuItem'
+
+interface OrgTeamMembersMenuProps {
+ menuProps: MenuProps
+ openDeleteTeamModal: () => void
+}
+
+export const OrgTeamMembersMenu = (props: OrgTeamMembersMenuProps) => {
+ const {menuProps, openDeleteTeamModal} = props
+ const {closePortal} = menuProps
+
+ return (
+
+ )
+}
diff --git a/packages/client/modules/userDashboard/components/OrgTeamMembers/OrgTeamMembersRoot.tsx b/packages/client/modules/userDashboard/components/OrgTeamMembers/OrgTeamMembersRoot.tsx
new file mode 100644
index 00000000000..c0c959d084c
--- /dev/null
+++ b/packages/client/modules/userDashboard/components/OrgTeamMembers/OrgTeamMembersRoot.tsx
@@ -0,0 +1,25 @@
+import React, {Suspense} from 'react'
+import orgTeamMembersQuery, {OrgTeamMembersQuery} from '~/__generated__/OrgTeamMembersQuery.graphql'
+import useQueryLoaderNow from '../../../../hooks/useQueryLoaderNow'
+import {LoaderSize} from '../../../../types/constEnums'
+import {Loader} from '../../../../utils/relay/renderLoader'
+import {OrgTeamMembers} from './OrgTeamMembers'
+import useRouter from '../../../../hooks/useRouter'
+
+const OrgTeamMembersRoot = () => {
+ const {match} = useRouter<{teamId: string}>()
+ const {
+ params: {teamId}
+ } = match
+ const queryRef = useQueryLoaderNow(orgTeamMembersQuery, {
+ teamId
+ })
+
+ return (
+ }>
+ {queryRef && }
+
+ )
+}
+
+export default OrgTeamMembersRoot
diff --git a/packages/client/modules/userDashboard/components/OrgTeamMembers/OrgTeamMembersRow.tsx b/packages/client/modules/userDashboard/components/OrgTeamMembers/OrgTeamMembersRow.tsx
new file mode 100644
index 00000000000..02b643f744f
--- /dev/null
+++ b/packages/client/modules/userDashboard/components/OrgTeamMembers/OrgTeamMembersRow.tsx
@@ -0,0 +1,117 @@
+import React from 'react'
+import graphql from 'babel-plugin-relay/macro'
+import {useFragment} from 'react-relay'
+import {OrgTeamMembersRow_teamMember$key} from '../../../../__generated__/OrgTeamMembersRow_teamMember.graphql'
+import {Avatar} from '../../../../ui/Avatar/Avatar'
+import {AvatarFallback} from '../../../../ui/Avatar/AvatarFallback'
+import {AvatarImage} from '../../../../ui/Avatar/AvatarImage'
+import {Button} from '../../../../ui/Button/Button'
+import {MoreVert} from '@mui/icons-material'
+import {OrgTeamMemberMenu} from './OrgTeamMemberMenu'
+import {MenuPosition} from '../../../../hooks/useCoords'
+import useMenu from '../../../../hooks/useMenu'
+import PromoteTeamMemberModal from '../../../teamDashboard/components/PromoteTeamMemberModal/PromoteTeamMemberModal'
+import RemoveTeamMemberModal from '../../../teamDashboard/components/RemoveTeamMemberModal/RemoveTeamMemberModal'
+import useModal from '../../../../hooks/useModal'
+import useAtmosphere from '../../../../hooks/useAtmosphere'
+
+type Props = {
+ teamMemberRef: OrgTeamMembersRow_teamMember$key
+ isViewerLead: boolean
+ isViewerOrgAdmin: boolean
+}
+
+export const OrgTeamMembersRow = (props: Props) => {
+ const {teamMemberRef} = props
+ const teamMember = useFragment(
+ graphql`
+ fragment OrgTeamMembersRow_teamMember on TeamMember {
+ ...OrgTeamMemberMenu_teamMember
+ userId
+ picture
+ preferredName
+ isLead
+ isOrgAdmin
+ isSelf
+ email
+ ...PromoteTeamMemberModal_teamMember
+ ...RemoveTeamMemberModal_teamMember
+ }
+ `,
+ teamMemberRef
+ )
+
+ const {isViewerLead, isViewerOrgAdmin} = props
+ const {isLead, isOrgAdmin, userId} = teamMember
+
+ const atmosphere = useAtmosphere()
+ const {viewerId} = atmosphere
+ const isSelf = userId === viewerId
+
+ const showMenuButton = (isViewerOrgAdmin && !isLead) || (isViewerLead && !isSelf && !isOrgAdmin)
+
+ const {togglePortal, originRef, menuPortal, menuProps} = useMenu(MenuPosition.UPPER_RIGHT)
+ const {
+ closePortal: closePromote,
+ togglePortal: togglePromote,
+ modalPortal: portalPromote
+ } = useModal()
+ const {
+ closePortal: closeRemove,
+ togglePortal: toggleRemove,
+ modalPortal: portalRemove
+ } = useModal()
+
+ return (
+
+
+
+
+ {teamMember.preferredName.substring(0, 2)}
+
+
+
+
+ {teamMember.preferredName}{' '}
+ {teamMember.isLead ? (
+
+ Team Lead
+
+ ) : null}
+
+
+
+
+ {showMenuButton && (
+
+ )}
+ {menuPortal(
+
+ )}
+
+
+ {portalPromote(
)}
+ {portalRemove(
)}
+
+ )
+}
diff --git a/packages/client/modules/userDashboard/components/OrgTeams/OrgTeams.tsx b/packages/client/modules/userDashboard/components/OrgTeams/OrgTeams.tsx
index e9bd39f71d7..3f544d7dd27 100644
--- a/packages/client/modules/userDashboard/components/OrgTeams/OrgTeams.tsx
+++ b/packages/client/modules/userDashboard/components/OrgTeams/OrgTeams.tsx
@@ -1,16 +1,12 @@
import React from 'react'
import graphql from 'babel-plugin-relay/macro'
-import styled from '@emotion/styled'
-import Row from '../../../../components/Row/Row'
-import Panel from '../../../../components/Panel/Panel'
-import {ElementWidth} from '../../../../types/constEnums'
import {useFragment} from 'react-relay'
import OrgTeamsRow from './OrgTeamsRow'
import {OrgTeams_organization$key} from '../../../../__generated__/OrgTeams_organization.graphql'
-
-const StyledPanel = styled(Panel)({
- maxWidth: ElementWidth.PANEL_WIDTH
-})
+import AddTeamDialogRoot from '../../../../components/AddTeamDialogRoot'
+import {Button} from '../../../../ui/Button/Button'
+import {useDialogState} from '../../../../ui/Dialog/useDialogState'
+import plural from '../../../../utils/plural'
type Props = {
organizationRef: OrgTeams_organization$key
@@ -31,23 +27,48 @@ const OrgTeams = (props: Props) => {
`,
organizationRef
)
+ const {
+ open: openAddTeamDialog,
+ close: closeAddTeamDialog,
+ isOpen: isAddTeamDialogOpened
+ } = useDialogState()
+
const {allTeams, isBillingLeader} = organization
if (!isBillingLeader) return null
+
return (
- <>
- {'Teams'}
-
-
-
-
Team Name
-
Lead
+
+
+
Teams
+
+
+
+
+
+
+
+
+
+ {allTeams.length} {plural(allTeams.length, 'Team')}
+
-
+
{allTeams.map((team) => (
))}
-
- >
+
+
+ {isAddTeamDialogOpened ? (
+
+ ) : null}
+
)
}
diff --git a/packages/client/modules/userDashboard/components/OrgTeams/OrgTeamsRow.tsx b/packages/client/modules/userDashboard/components/OrgTeams/OrgTeamsRow.tsx
index 00123a43b5d..d6da2e5e6c6 100644
--- a/packages/client/modules/userDashboard/components/OrgTeams/OrgTeamsRow.tsx
+++ b/packages/client/modules/userDashboard/components/OrgTeams/OrgTeamsRow.tsx
@@ -1,8 +1,9 @@
import React from 'react'
import {Link} from 'react-router-dom'
import graphql from 'babel-plugin-relay/macro'
-import Row from '../../../../components/Row/Row'
import {useFragment} from 'react-relay'
+import {ChevronRight} from '@mui/icons-material'
+
import plural from '../../../../utils/plural'
import {OrgTeamsRow_team$key} from '../../../../__generated__/OrgTeamsRow_team.graphql'
@@ -30,42 +31,26 @@ const OrgTeamsRow = (props: Props) => {
)
const {id: teamId, teamMembers, name} = team
const teamMembersCount = teamMembers.length
- const teamLeadEmail = teamMembers.find((member) => member.isLead)?.email ?? ''
- const isViewerTeamLead = teamMembers.some(
- (member) => member.isSelf && (member.isLead || member.isOrgAdmin)
- )
+
return (
-
-
-
{name}
-
-
- {`${teamMembersCount} ${plural(teamMembersCount, 'member')}`}
- {isViewerTeamLead && (
- <>
-
•
-
- {'Manage Team'}
-
- >
- )}
+
+
-
+
)
}
diff --git a/packages/client/modules/userDashboard/components/OrgUserRow/OrgMemberRow.tsx b/packages/client/modules/userDashboard/components/OrgUserRow/OrgMemberRow.tsx
index eda83c7b6dc..8a15e046096 100644
--- a/packages/client/modules/userDashboard/components/OrgUserRow/OrgMemberRow.tsx
+++ b/packages/client/modules/userDashboard/components/OrgUserRow/OrgMemberRow.tsx
@@ -18,13 +18,18 @@ import RoleTag from '../../../../components/Tag/RoleTag'
import {MenuPosition} from '../../../../hooks/useCoords'
import useMenu from '../../../../hooks/useMenu'
import useModal from '../../../../hooks/useModal'
-import useTooltip from '../../../../hooks/useTooltip'
import defaultUserAvatar from '../../../../styles/theme/images/avatar-user.svg'
import {Breakpoint} from '../../../../types/constEnums'
import lazyPreload from '../../../../utils/lazyPreload'
import withMutationProps, {WithMutationProps} from '../../../../utils/relay/withMutationProps'
-import {OrgMemberRow_organization$key} from '../../../../__generated__/OrgMemberRow_organization.graphql'
-import {OrgMemberRow_organizationUser$key} from '../../../../__generated__/OrgMemberRow_organizationUser.graphql'
+import {
+ OrgMemberRow_organization$key,
+ OrgMemberRow_organization$data
+} from '../../../../__generated__/OrgMemberRow_organization.graphql'
+import {
+ OrgMemberRow_organizationUser$key,
+ OrgMemberRow_organizationUser$data
+} from '../../../../__generated__/OrgMemberRow_organizationUser.graphql'
import BaseTag from '../../../../components/Tag/BaseTag'
const AvatarBlock = styled('div')({
@@ -59,6 +64,7 @@ const MenuToggleBlock = styled('div')({
interface Props extends WithMutationProps {
billingLeaderCount: number
+ orgAdminCount: number
organizationUser: OrgMemberRow_organizationUser$key
organization: OrgMemberRow_organization$key
}
@@ -90,29 +96,157 @@ const BillingLeaderActionMenu = lazyPreload(
/* webpackChunkName: 'BillingLeaderActionMenu' */ '../../../../components/BillingLeaderActionMenu'
)
)
+const OrgAdminActionMenu = lazyPreload(
+ () =>
+ import(/* webpackChunkName: 'OrgAdminActionMenu' */ '../../../../components/OrgAdminActionMenu')
+)
const RemoveFromOrgModal = lazyPreload(
() =>
import(/* webpackChunkName: 'RemoveFromOrgModal' */ '../RemoveFromOrgModal/RemoveFromOrgModal')
)
+interface UserAvatarProps {
+ picture?: string
+}
+
+const UserAvatar: React.FC
= ({picture}) => (
+
+ {picture ? (
+
+ ) : (
+
+ )}
+
+)
+
+interface UserInfoProps {
+ preferredName: string
+ email: string
+ isBillingLeader: boolean
+ isOrgAdmin: boolean
+ inactive: boolean | null
+ newUserUntil: string
+}
+
+const UserInfo: React.FC = ({
+ preferredName,
+ email,
+ isBillingLeader,
+ isOrgAdmin,
+ inactive,
+ newUserUntil
+}) => (
+
+
+ {preferredName}
+ {isBillingLeader && Billing Leader}
+ {isOrgAdmin && Org Admin}
+ {inactive && !isBillingLeader && !isOrgAdmin && Inactive}
+ {new Date(newUserUntil) > new Date() && New}
+
+
+ {email}
+
+
+)
+
+interface UserActionsProps {
+ isViewerOrgAdmin: boolean
+ isViewerBillingLeader: boolean
+ isViewerLastOrgAdmin: boolean
+ isViewerLastBillingLeader: boolean
+ organization: OrgMemberRow_organization$data
+ organizationUser: OrgMemberRow_organizationUser$data
+ preferredName: string
+ viewerId: string
+}
+
+const UserActions: React.FC = ({
+ isViewerOrgAdmin,
+ isViewerBillingLeader,
+ isViewerLastOrgAdmin,
+ isViewerLastBillingLeader,
+ organizationUser,
+ organization,
+ preferredName,
+ viewerId
+}) => {
+ const {orgId} = organization
+ const {
+ user: {userId}
+ } = organizationUser
+ const {togglePortal, originRef, menuPortal, menuProps} = useMenu(MenuPosition.UPPER_RIGHT)
+ const {togglePortal: toggleLeave, modalPortal: leaveModal} = useModal()
+ const {togglePortal: toggleRemove, modalPortal: removeModal} = useModal()
+ const actionMenuProps = {
+ menuProps,
+ originRef,
+ togglePortal,
+ toggleLeave,
+ toggleRemove,
+ isViewerLastOrgAdmin,
+ isViewerLastBillingLeader,
+ organization,
+ organizationUser
+ }
+
+ const showLeaveButton = !isViewerOrgAdmin && !isViewerBillingLeader && viewerId === userId
+
+ return (
+
+
+ {showLeaveButton && (
+
+ Leave Organization
+
+ )}
+ {(isViewerOrgAdmin || (isViewerBillingLeader && !isViewerLastBillingLeader)) && (
+
+
+
+ )}
+ {isViewerOrgAdmin && menuPortal()}
+ {!isViewerOrgAdmin &&
+ isViewerBillingLeader &&
+ menuPortal()}
+ {leaveModal()}
+ {removeModal(
+
+ )}
+
+
+ )
+}
+
const OrgMemberRow = (props: Props) => {
const atmosphere = useAtmosphere()
const {
billingLeaderCount,
+ orgAdminCount,
organizationUser: organizationUserRef,
organization: organizationRef
} = props
+
const organization = useFragment(
graphql`
fragment OrgMemberRow_organization on Organization {
isViewerBillingLeader: isBillingLeader
+ isViewerOrgAdmin: isOrgAdmin
orgId: id
...BillingLeaderActionMenu_organization
+ ...OrgAdminActionMenu_organization
}
`,
organizationRef
)
+
const organizationUser = useFragment(
graphql`
fragment OrgMemberRow_organizationUser on OrganizationUser {
@@ -126,103 +260,49 @@ const OrgMemberRow = (props: Props) => {
role
newUserUntil
...BillingLeaderActionMenu_organizationUser
+ ...OrgAdminActionMenu_organizationUser
}
`,
organizationUserRef
)
- const {orgId, isViewerBillingLeader} = organization
- const {newUserUntil, user, role} = organizationUser
+
+ const {isViewerBillingLeader, isViewerOrgAdmin} = organization
+
+ const {
+ newUserUntil,
+ user: {email, inactive, picture, preferredName},
+ role
+ } = organizationUser
+
+ const {viewerId} = atmosphere
+
const isBillingLeader = role === 'BILLING_LEADER'
const isOrgAdmin = role === 'ORG_ADMIN'
- const {email, inactive, picture, preferredName, userId} = user
const isViewerLastBillingLeader =
isViewerBillingLeader && isBillingLeader && billingLeaderCount === 1
- const {viewerId} = atmosphere
- const {togglePortal, originRef, menuPortal, menuProps} = useMenu(MenuPosition.UPPER_RIGHT)
- const {togglePortal: toggleLeave, modalPortal: leaveModal} = useModal()
- const {togglePortal: toggleRemove, modalPortal: removeModal} = useModal()
- const {
- tooltipPortal,
- openTooltip,
- closeTooltip,
- originRef: tooltipRef
- } = useTooltip(MenuPosition.LOWER_RIGHT)
- const canViewMenu = !isViewerLastBillingLeader && organizationUser.role !== 'ORG_ADMIN'
+ const isViewerLastOrgAdmin = isViewerOrgAdmin && isOrgAdmin && orgAdminCount === 1
return (
-
- {picture ? (
-
- ) : (
-
- )}
-
-
-
- {preferredName}
- {isBillingLeader && {'Billing Leader'}}
- {isOrgAdmin && {'Org Admin'}}
- {inactive && !isBillingLeader && !isOrgAdmin && {'Inactive'}}
- {new Date(newUserUntil) > new Date() && {'New'}}
-
-
- {email}
-
-
-
-
- {!isBillingLeader && !isOrgAdmin && viewerId === userId && (
-
- Leave Organization
-
- )}
- {!canViewMenu && (
-
- {tooltipPortal(
- isViewerLastBillingLeader ? (
-
- {'You need to promote another Billing Leader'}
-
- {'before you can remove this role.'}
-
- ) : (
- Contact support (love@parabol.co) to remove the Org Admin role
- )
- )}
-
-
- )}
- {isViewerBillingLeader && canViewMenu && (
-
-
-
- )}
- {menuPortal(
-
- )}
- {leaveModal()}
- {removeModal(
-
- )}
-
-
+
+
+
)
}
diff --git a/packages/client/modules/userDashboard/containers/OrgMembers/OrgMembersRoot.tsx b/packages/client/modules/userDashboard/containers/OrgMembers/OrgMembersRoot.tsx
index b0a7ab43373..8242ebf2b8e 100644
--- a/packages/client/modules/userDashboard/containers/OrgMembers/OrgMembersRoot.tsx
+++ b/packages/client/modules/userDashboard/containers/OrgMembers/OrgMembersRoot.tsx
@@ -15,6 +15,7 @@ const OrgMembersRoot = (props: Props) => {
orgId,
first: 10000
})
+
return (
}>
{queryRef && }
diff --git a/packages/client/mutations/ArchiveTeamMutation.ts b/packages/client/mutations/ArchiveTeamMutation.ts
index 15afd828d4b..218fdd0c7c1 100644
--- a/packages/client/mutations/ArchiveTeamMutation.ts
+++ b/packages/client/mutations/ArchiveTeamMutation.ts
@@ -30,6 +30,14 @@ graphql`
activeMeetings {
id
}
+ organization {
+ allTeams {
+ id
+ }
+ viewerTeams {
+ id
+ }
+ }
}
teamTemplateIds
}
diff --git a/packages/client/package.json b/packages/client/package.json
index 364c5dbd367..81691d805d0 100644
--- a/packages/client/package.json
+++ b/packages/client/package.json
@@ -82,6 +82,8 @@
"@radix-ui/react-select": "^1.2.2",
"@radix-ui/react-tooltip": "^1.0.7",
"@radix-ui/react-slot": "^1.0.2",
+ "@radix-ui/react-avatar": "^1.0.4",
+ "@radix-ui/react-alert-dialog": "1.0.5",
"@sentry/browser": "^5.8.0",
"@stripe/react-stripe-js": "^1.16.5",
"@stripe/stripe-js": "^1.47.0",
diff --git a/packages/client/ui/AlertDialog/AlertDialog.tsx b/packages/client/ui/AlertDialog/AlertDialog.tsx
new file mode 100644
index 00000000000..8b4fdf6a396
--- /dev/null
+++ b/packages/client/ui/AlertDialog/AlertDialog.tsx
@@ -0,0 +1,5 @@
+import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog'
+
+const AlertDialog = AlertDialogPrimitive.Root
+
+export {AlertDialog}
diff --git a/packages/client/ui/AlertDialog/AlertDialogAction.tsx b/packages/client/ui/AlertDialog/AlertDialogAction.tsx
new file mode 100644
index 00000000000..75b93fb02a9
--- /dev/null
+++ b/packages/client/ui/AlertDialog/AlertDialogAction.tsx
@@ -0,0 +1,18 @@
+import * as React from 'react'
+import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog'
+import clsx from 'clsx'
+
+const AlertDialogAction = React.forwardRef<
+ HTMLButtonElement,
+ React.ComponentPropsWithoutRef
+>(({className, ...props}, ref) => (
+
+))
+AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
diff --git a/packages/client/ui/AlertDialog/AlertDialogCancel.tsx b/packages/client/ui/AlertDialog/AlertDialogCancel.tsx
new file mode 100644
index 00000000000..201ca648679
--- /dev/null
+++ b/packages/client/ui/AlertDialog/AlertDialogCancel.tsx
@@ -0,0 +1,18 @@
+import * as React from 'react'
+import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog'
+import clsx from 'clsx'
+
+const AlertDialogCancel = React.forwardRef<
+ HTMLButtonElement,
+ React.ComponentPropsWithoutRef
+>(({className, ...props}, ref) => (
+
+))
+AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
diff --git a/packages/client/ui/AlertDialog/AlertDialogContent.tsx b/packages/client/ui/AlertDialog/AlertDialogContent.tsx
new file mode 100644
index 00000000000..dbcd0de0582
--- /dev/null
+++ b/packages/client/ui/AlertDialog/AlertDialogContent.tsx
@@ -0,0 +1,23 @@
+import * as React from 'react'
+import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog'
+import clsx from 'clsx'
+import {AlertDialogOverlay} from './AlertDialogOverlay'
+import {AlertDialogPortal} from './AlertDialog'
+
+const AlertDialogContent = React.forwardRef<
+ HTMLDivElement,
+ React.ComponentPropsWithoutRef
+>(({className, ...props}, ref) => (
+
+
+
+
+))
+AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
diff --git a/packages/client/ui/AlertDialog/AlertDialogDescription.tsx b/packages/client/ui/AlertDialog/AlertDialogDescription.tsx
new file mode 100644
index 00000000000..7c940b02fec
--- /dev/null
+++ b/packages/client/ui/AlertDialog/AlertDialogDescription.tsx
@@ -0,0 +1,15 @@
+import * as React from 'react'
+import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog'
+import clsx from 'clsx'
+
+const AlertDialogDescription = React.forwardRef<
+ HTMLParagraphElement,
+ React.ComponentPropsWithoutRef
+>(({className, ...props}, ref) => (
+
+))
+AlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName
diff --git a/packages/client/ui/AlertDialog/AlertDialogFooter.tsx b/packages/client/ui/AlertDialog/AlertDialogFooter.tsx
new file mode 100644
index 00000000000..0148d9a8c39
--- /dev/null
+++ b/packages/client/ui/AlertDialog/AlertDialogFooter.tsx
@@ -0,0 +1,10 @@
+import * as React from 'react'
+import clsx from 'clsx'
+
+const AlertDialogFooter = ({className, ...props}: React.HTMLAttributes) => (
+
+)
+AlertDialogFooter.displayName = 'AlertDialogFooter'
diff --git a/packages/client/ui/AlertDialog/AlertDialogHeader.tsx b/packages/client/ui/AlertDialog/AlertDialogHeader.tsx
new file mode 100644
index 00000000000..869b09ccfc3
--- /dev/null
+++ b/packages/client/ui/AlertDialog/AlertDialogHeader.tsx
@@ -0,0 +1,7 @@
+import * as React from 'react'
+import clsx from 'clsx'
+
+const AlertDialogHeader = ({className, ...props}: React.HTMLAttributes) => (
+
+)
+AlertDialogHeader.displayName = 'AlertDialogHeader'
diff --git a/packages/client/ui/AlertDialog/AlertDialogOverlay.tsx b/packages/client/ui/AlertDialog/AlertDialogOverlay.tsx
new file mode 100644
index 00000000000..e867c7ed363
--- /dev/null
+++ b/packages/client/ui/AlertDialog/AlertDialogOverlay.tsx
@@ -0,0 +1,18 @@
+import * as React from 'react'
+import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog'
+import clsx from 'clsx'
+
+export const AlertDialogOverlay = React.forwardRef<
+ HTMLDivElement,
+ React.ComponentPropsWithoutRef
+>(({className, ...props}, ref) => (
+
+))
+AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
diff --git a/packages/client/ui/AlertDialog/AlertDialogPortal.tsx b/packages/client/ui/AlertDialog/AlertDialogPortal.tsx
new file mode 100644
index 00000000000..c4e8d56916f
--- /dev/null
+++ b/packages/client/ui/AlertDialog/AlertDialogPortal.tsx
@@ -0,0 +1,3 @@
+import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog'
+
+const AlertDialogPortal = AlertDialogPrimitive.Portal
diff --git a/packages/client/ui/AlertDialog/AlertDialogTitle.tsx b/packages/client/ui/AlertDialog/AlertDialogTitle.tsx
new file mode 100644
index 00000000000..c7c14e011c5
--- /dev/null
+++ b/packages/client/ui/AlertDialog/AlertDialogTitle.tsx
@@ -0,0 +1,15 @@
+import * as React from 'react'
+import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog'
+import clsx from 'clsx'
+
+const AlertDialogTitle = React.forwardRef<
+ HTMLHeadingElement,
+ React.ComponentPropsWithoutRef
+>(({className, ...props}, ref) => (
+
+))
+AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
diff --git a/packages/client/ui/AlertDialog/AlertDialogTrigger.tsx b/packages/client/ui/AlertDialog/AlertDialogTrigger.tsx
new file mode 100644
index 00000000000..0a20feddfc1
--- /dev/null
+++ b/packages/client/ui/AlertDialog/AlertDialogTrigger.tsx
@@ -0,0 +1,3 @@
+import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog'
+
+const AlertDialogTrigger = AlertDialogPrimitive.Trigger
diff --git a/packages/client/ui/Avatar/Avatar.tsx b/packages/client/ui/Avatar/Avatar.tsx
new file mode 100644
index 00000000000..23bbecd22a6
--- /dev/null
+++ b/packages/client/ui/Avatar/Avatar.tsx
@@ -0,0 +1,16 @@
+import React from 'react'
+import * as AvatarPrimitive from '@radix-ui/react-avatar'
+import clsx from 'clsx'
+
+export const Avatar = React.forwardRef<
+ HTMLSpanElement,
+ React.ComponentPropsWithoutRef
+>(({className, ...props}, ref) => (
+
+))
+
+Avatar.displayName = AvatarPrimitive.Root.displayName
diff --git a/packages/client/ui/Avatar/AvatarFallback.tsx b/packages/client/ui/Avatar/AvatarFallback.tsx
new file mode 100644
index 00000000000..482648102a8
--- /dev/null
+++ b/packages/client/ui/Avatar/AvatarFallback.tsx
@@ -0,0 +1,19 @@
+import React from 'react'
+import * as AvatarPrimitive from '@radix-ui/react-avatar'
+import clsx from 'clsx'
+
+export const AvatarFallback = React.forwardRef<
+ HTMLSpanElement,
+ React.ComponentPropsWithoutRef
+>(({className, ...props}, ref) => (
+
+))
+
+AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
diff --git a/packages/client/ui/Avatar/AvatarImage.tsx b/packages/client/ui/Avatar/AvatarImage.tsx
new file mode 100644
index 00000000000..66f41abad5d
--- /dev/null
+++ b/packages/client/ui/Avatar/AvatarImage.tsx
@@ -0,0 +1,16 @@
+import React from 'react'
+import * as AvatarPrimitive from '@radix-ui/react-avatar'
+import clsx from 'clsx'
+
+export const AvatarImage = React.forwardRef<
+ HTMLImageElement,
+ React.ComponentPropsWithoutRef
+>(({className, ...props}, ref) => (
+
+))
+
+AvatarImage.displayName = AvatarPrimitive.Image.displayName
diff --git a/packages/client/ui/Button/Button.tsx b/packages/client/ui/Button/Button.tsx
index a60c67700d7..90554dc662e 100644
--- a/packages/client/ui/Button/Button.tsx
+++ b/packages/client/ui/Button/Button.tsx
@@ -7,20 +7,21 @@ type Size = 'sm' | 'md' | 'lg' | 'default'
type Shape = 'pill' | 'circle' | 'default'
const BASE_STYLES =
- 'cursor-pointer inline-flex items-center justify-center whitespace-nowrap font-semibold transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50'
+ 'cursor-pointer inline-flex items-center justify-center whitespace-nowrap transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50'
// TODO: make sure the styles match the designs
const VARIANT_STYLES: Record = {
- primary: 'bg-primary text-white hover:bg-primary/90',
- destructive: 'bg-tomato-500 text-white hover:bg-tomato-500/90',
- outline: 'text-slate-900 border border-slate-400 hover:bg-slate-200 px-2.5 py-1 bg-transparent',
- secondary: 'bg-sky-500 text-white hover:bg-sky-500/80',
- ghost: 'hover:bg-accent',
+ primary: 'bg-gradient-to-r from-tomato-600 to-rose-500 text-white font-semibold hover:opacity-90',
+ destructive: 'bg-tomato-500 text-white font-semibold hover:bg-tomato-500/90',
+ outline:
+ 'text-slate-900 border border-slate-400 hover:bg-slate-200 px-2.5 py-1 bg-transparent font-semibold',
+ secondary: 'bg-sky-500 text-white hover:bg-sky-500/80 font-semibold',
+ ghost: 'hover:opacity-80 bg-transparent font-semibold',
link: 'text-primary underline-offset-4 hover:underline'
}
const SIZE_STYLES: Record = {
- default: 'px-4 py-2 text-xs',
+ default: '',
sm: 'h-7 px-3 text-xs',
md: 'h-9 px-4 text-sm',
lg: 'h-11 px-8 text-base'
@@ -29,18 +30,18 @@ const SIZE_STYLES: Record = {
const SHAPE_STYLES: Record = {
pill: 'rounded-full',
circle: 'rounded-full aspect-square',
- default: 'rounded-md'
+ default: ''
}
export interface ButtonProps extends React.ButtonHTMLAttributes {
asChild?: boolean
variant: Variant
size?: Size
- shape: Shape
+ shape?: Shape
}
const Button = React.forwardRef(
- ({className, variant, size, shape, asChild = false, ...props}, ref) => {
+ ({className, variant, size = 'default', shape = 'default', asChild = false, ...props}, ref) => {
const Comp = asChild ? Slot : 'button'
return (
= new GraphQLObjectType {
const viewerId = getUserId(authToken)
return isUserBillingLeader(viewerId, orgId, dataLoader)
}
},
+ isOrgAdmin: {
+ type: new GraphQLNonNull(GraphQLBoolean),
+ description: 'true if the viewer holds the the org admin role on the org',
+ resolve: async ({id: orgId}, _args: unknown, {authToken, dataLoader}) => {
+ const viewerId = getUserId(authToken)
+ return isUserOrgAdmin(viewerId, orgId, dataLoader)
+ }
+ },
name: {
type: new GraphQLNonNull(GraphQLString),
description: 'The name of the organization'
diff --git a/packages/server/utils/authorization.ts b/packages/server/utils/authorization.ts
index 4537f03e77c..dd873e6c885 100644
--- a/packages/server/utils/authorization.ts
+++ b/packages/server/utils/authorization.ts
@@ -4,6 +4,7 @@ import AuthToken from '../database/types/AuthToken'
import OrganizationUser from '../database/types/OrganizationUser'
import {DataLoaderWorker} from '../graphql/graphql'
import {RDatum} from '../database/stricterR'
+import {OrgUserRole} from '../database/types/OrganizationUser'
export const getUserId = (authToken: any) => {
return authToken && typeof authToken === 'object' ? (authToken.sub as string) : ''
@@ -51,10 +52,12 @@ export const isTeamLead = async (userId: string, teamId: string, dataLoader: Dat
interface Options {
clearCache?: boolean
}
-export const isUserBillingLeader = async (
+
+const isUserAnyRoleIn = async (
userId: string,
orgId: string,
dataLoader: DataLoaderWorker,
+ roles: OrgUserRole[],
options?: Options
) => {
const organizationUser = await dataLoader
@@ -63,9 +66,23 @@ export const isUserBillingLeader = async (
if (options && options.clearCache) {
dataLoader.get('organizationUsersByUserId').clear(userId)
}
- return organizationUser
- ? organizationUser.role === 'BILLING_LEADER' || organizationUser.role === 'ORG_ADMIN'
- : false
+ return organizationUser && organizationUser.role ? roles.includes(organizationUser.role) : false
+}
+export const isUserBillingLeader = async (
+ userId: string,
+ orgId: string,
+ dataLoader: DataLoaderWorker,
+ options?: Options
+) => {
+ return isUserAnyRoleIn(userId, orgId, dataLoader, ['BILLING_LEADER', 'ORG_ADMIN'], options)
+}
+export const isUserOrgAdmin = async (
+ userId: string,
+ orgId: string,
+ dataLoader: DataLoaderWorker,
+ options?: Options
+) => {
+ return isUserAnyRoleIn(userId, orgId, dataLoader, ['ORG_ADMIN'], options)
}
export const isUserInOrg = async (userId: string, orgId: string, dataLoader: DataLoaderWorker) => {
diff --git a/yarn.lock b/yarn.lock
index af959f0896f..ffa47f0a625 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5704,6 +5704,19 @@
dependencies:
"@babel/runtime" "^7.13.10"
+"@radix-ui/react-alert-dialog@1.0.5":
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.0.5.tgz#70dd529cbf1e4bff386814d3776901fcaa131b8c"
+ integrity sha512-OrVIOcZL0tl6xibeuGt5/+UxoT2N27KCFOPjFyfXMnchxSHZ/OW7cCX2nGlIYJrbHK/fczPcFzAwvNBB6XBNMA==
+ dependencies:
+ "@babel/runtime" "^7.13.10"
+ "@radix-ui/primitive" "1.0.1"
+ "@radix-ui/react-compose-refs" "1.0.1"
+ "@radix-ui/react-context" "1.0.1"
+ "@radix-ui/react-dialog" "1.0.5"
+ "@radix-ui/react-primitive" "1.0.3"
+ "@radix-ui/react-slot" "1.0.2"
+
"@radix-ui/react-arrow@1.0.3":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@radix-ui/react-arrow/-/react-arrow-1.0.3.tgz#c24f7968996ed934d57fe6cde5d6ec7266e1d25d"
@@ -5712,6 +5725,17 @@
"@babel/runtime" "^7.13.10"
"@radix-ui/react-primitive" "1.0.3"
+"@radix-ui/react-avatar@^1.0.4":
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-avatar/-/react-avatar-1.0.4.tgz#de9a5349d9e3de7bbe990334c4d2011acbbb9623"
+ integrity sha512-kVK2K7ZD3wwj3qhle0ElXhOjbezIgyl2hVvgwfIdexL3rN6zJmy5AqqIf+D31lxVppdzV8CjAfZ6PklkmInZLw==
+ dependencies:
+ "@babel/runtime" "^7.13.10"
+ "@radix-ui/react-context" "1.0.1"
+ "@radix-ui/react-primitive" "1.0.3"
+ "@radix-ui/react-use-callback-ref" "1.0.1"
+ "@radix-ui/react-use-layout-effect" "1.0.1"
+
"@radix-ui/react-collection@1.0.2":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@radix-ui/react-collection/-/react-collection-1.0.2.tgz#d50da00bfa2ac14585319efdbbb081d4c5a29a97"
@@ -5762,6 +5786,27 @@
dependencies:
"@babel/runtime" "^7.13.10"
+"@radix-ui/react-dialog@1.0.5":
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-1.0.5.tgz#71657b1b116de6c7a0b03242d7d43e01062c7300"
+ integrity sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==
+ dependencies:
+ "@babel/runtime" "^7.13.10"
+ "@radix-ui/primitive" "1.0.1"
+ "@radix-ui/react-compose-refs" "1.0.1"
+ "@radix-ui/react-context" "1.0.1"
+ "@radix-ui/react-dismissable-layer" "1.0.5"
+ "@radix-ui/react-focus-guards" "1.0.1"
+ "@radix-ui/react-focus-scope" "1.0.4"
+ "@radix-ui/react-id" "1.0.1"
+ "@radix-ui/react-portal" "1.0.4"
+ "@radix-ui/react-presence" "1.0.1"
+ "@radix-ui/react-primitive" "1.0.3"
+ "@radix-ui/react-slot" "1.0.2"
+ "@radix-ui/react-use-controllable-state" "1.0.1"
+ aria-hidden "^1.1.1"
+ react-remove-scroll "2.5.5"
+
"@radix-ui/react-dialog@^1.0.4":
version "1.0.4"
resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-1.0.4.tgz#06bce6c16bb93eb36d7a8589e665a20f4c1c52c1"
@@ -5852,6 +5897,16 @@
"@radix-ui/react-primitive" "1.0.3"
"@radix-ui/react-use-callback-ref" "1.0.1"
+"@radix-ui/react-focus-scope@1.0.4":
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.4.tgz#2ac45fce8c5bb33eb18419cdc1905ef4f1906525"
+ integrity sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA==
+ dependencies:
+ "@babel/runtime" "^7.13.10"
+ "@radix-ui/react-compose-refs" "1.0.1"
+ "@radix-ui/react-primitive" "1.0.3"
+ "@radix-ui/react-use-callback-ref" "1.0.1"
+
"@radix-ui/react-id@1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-1.0.0.tgz#8d43224910741870a45a8c9d092f25887bb6d11e"
@@ -11230,6 +11285,7 @@ draft-js-utils@^1.4.0:
"draft-js@https://github.com/mattkrick/draft-js/tarball/559a21968370c4944511657817d601a6c4ade0f6":
version "0.10.5"
+ uid "025fddba56f21aaf3383aee778e0b17025c9a7bc"
resolved "https://github.com/mattkrick/draft-js/tarball/559a21968370c4944511657817d601a6c4ade0f6#025fddba56f21aaf3383aee778e0b17025c9a7bc"
dependencies:
fbjs "^0.8.15"