import { useCallback, useMemo } from 'react'
import { Link, useLocation, useParams, useSearchParams } from 'react-router-dom'
import { Form, Formik } from 'formik'
import { UserAccessPermission } from '@prisma/client'
import classNames from 'classnames'
import { trpc, inferQueryOutput } from '~/utils/trpc'
import IVButton from '~/components/IVButton'
import IVAvatar from '~/components/IVAvatar'
import { userAccessPermissionToString } from '~/utils/text'
import { notify } from '~/components/NotificationCenter'
import useDashboard, { useHasPermission } from '~/components/DashboardContext'
import { getPrimaryRole } from '~/utils/permissions'
import IconChevronDown from '~/icons/compiled/ChevronDown'
import IconChevronRight from '~/icons/compiled/ChevronRight'
import IVDialog, { useDialogState } from '~/components/IVDialog'
import PermissionSelector from '~/components/PermissionSelector'
import IVTooltip from '~/components/IVTooltip'
import KeysList from '~/components/KeysList'
export default function UsersList({
data,
onChange,
}: {
data: inferQueryOutput<'dashboard.users.index'>
onChange?: () => void
}) {
const [searchParams] = useSearchParams()
const location = useLocation()
const { users, keys } = data ?? { users: [], keys: [] }
const userKeyMap = useMemo(
() =>
keys.reduce((map, key) => {
let arr = map.get(key.userId)
if (!arr) {
arr = []
map.set(key.userId, arr)
}
arr.push(key)
return map
}, new Map()) ?? new Map(),
[keys]
)
if (!data) return null
const selectedUserId = searchParams.get('userId')
return (
{users.map(access => (
-
{access.user.firstName} {access.user.lastName}
{access.user.email}
{userAccessPermissionToString(
getPrimaryRole(access.permissions) ??
'ACTION_RUNNER'
)}
{access.user.emailConfirmToken?.email === null && (
Awaiting email confirmation
)}
{selectedUserId === access.user.id ? (
) : (
)}
{selectedUserId === access.user.id && (
)}
))}
)
}
type UserAccess = inferQueryOutput<'dashboard.users.index'>['users'][0]
function getCurrentGroup(userAccess: UserAccess, groupId?: string) {
if (!groupId) return null
const group = userAccess.groupMemberships.find(
gm => gm.group.id === groupId
)?.group
return group || null
}
function ExpandedUser({
userAccess,
keys,
onChange,
}: {
userAccess: UserAccess
keys: inferQueryOutput<'dashboard.users.index'>['keys']
onChange?: () => void
}) {
const { groupId } = useParams<{ groupId: string | undefined }>()
const { me, organization } = useDashboard()
const editUserAccess = trpc.useMutation('organization.edit-user-access')
const canWriteUsers = useHasPermission('WRITE_USERS')
const canViewUserKeys = useHasPermission('READ_ORG_USER_API_KEY_EXISTENCE')
const ctx = trpc.useContext()
const removeUserDialog = useDialogState()
const removeUserMutation = trpc.useMutation('organization.remove-user')
const removeUserMembershipDialog = useDialogState()
const removeUserMembershipMutation = trpc.useMutation('group.users.remove')
const isUserExternallyManaged =
userAccess.user.idpId && organization.sso?.workosOrganizationId
// Don't allow editing your own or the owner's permissions
const canEditPermissions =
userAccess.user.id !== me.id && userAccess.user.id !== organization.ownerId
const group = getCurrentGroup(userAccess, groupId)
const canRemoveUserFromOrg = canWriteUsers && !isUserExternallyManaged
const canRemoveUserFromGroup = canWriteUsers && !group?.scimGroupId
const role = getPrimaryRole(userAccess.permissions) ?? 'ADMIN'
const onRemoveUser = useCallback(
(event: React.FormEvent) => {
event.preventDefault()
removeUserMutation.mutate(
{ id: userAccess.id },
{
onSuccess() {
notify.success(
`${userAccess.user.email} was removed from the organization.`
)
if (onChange) {
onChange()
}
},
}
)
},
[onChange, removeUserMutation, userAccess]
)
const onRemoveUserMembership = useCallback(
(event: React.FormEvent) => {
event.preventDefault()
if (!groupId) return
removeUserMembershipMutation.mutate(
{
groupId,
userOrganizationAccessId: userAccess.id,
},
{
onSuccess() {
notify.success(
`${userAccess.user.email} was removed from the team.`
)
if (onChange) {
onChange()
}
},
}
)
},
[onChange, removeUserMembershipMutation, groupId, userAccess]
)
return (
{canWriteUsers && (
Organization access
initialValues={{
role,
}}
onSubmit={async ({ role }) => {
if (!canEditPermissions || editUserAccess.isLoading) return
editUserAccess.mutate(
{
id: userAccess.id,
data: {
permissions: [role],
},
},
{
onSuccess() {
notify.success('User role has been updated.')
if (onChange) {
onChange()
}
},
}
)
}}
>
)}
{keys.length > 0 && canViewUserKeys && (
API Keys
ctx.refetchQueries(['dashboard.users.index'])}
/>
)}
{canRemoveUserFromOrg && (
)}
{canRemoveUserFromGroup && groupId && (
)}
)
}