[web] Share modal redesign: invited people screen GitOrigin-RevId: bf7f83dccc245e41eca8087fc27c1b411f025b0d
195 lines
6.1 KiB
TypeScript
195 lines
6.1 KiB
TypeScript
import { useCallback } from 'react'
|
|
import { useShareProjectContext } from './share-project-modal'
|
|
import { useTranslation } from 'react-i18next'
|
|
import MemberPrivileges from './member-privileges'
|
|
import { resendInvite, revokeInvite } from '../utils/api'
|
|
import { useProjectContext } from '@/shared/context/project-context'
|
|
import { sendMB } from '@/infrastructure/event-tracking'
|
|
import OLRow from '@/shared/components/ol/ol-row'
|
|
import OLCol from '@/shared/components/ol/ol-col'
|
|
import OLTooltip from '@/shared/components/ol/ol-tooltip'
|
|
import OLButton from '@/shared/components/ol/ol-button'
|
|
import OLBadge from '@/shared/components/ol/ol-badge'
|
|
import OLDropdownMenuItem from '@/shared/components/ol/ol-dropdown-menu-item'
|
|
import {
|
|
Dropdown,
|
|
DropdownMenu,
|
|
DropdownToggle,
|
|
} from '@/shared/components/dropdown/dropdown-menu'
|
|
import ShareProjectModalRow from '@/features/share-project-modal/components/share-project-modal-row'
|
|
import MaterialIcon from '@/shared/components/material-icon'
|
|
import { ProjectMember } from '@/shared/context/types/project-metadata'
|
|
import { useFeatureFlag } from '@/shared/context/split-test-context'
|
|
|
|
export default function Invite({
|
|
invite,
|
|
isProjectOwner,
|
|
}: {
|
|
invite: ProjectMember
|
|
isProjectOwner: boolean
|
|
}) {
|
|
const isSharingUpdatesEnabled = useFeatureFlag('sharing-updates')
|
|
const { t } = useTranslation()
|
|
|
|
return isSharingUpdatesEnabled ? (
|
|
<ShareProjectModalRow>
|
|
<div className="d-inline-flex align-items-center h5 m-0 gap-2">
|
|
<MaterialIcon type="person" unfilled />
|
|
<div className="px-2">{invite.email}</div>
|
|
<OLBadge bg="light" text="dark">
|
|
{t('pending_invite')}
|
|
</OLBadge>
|
|
</div>
|
|
{isProjectOwner ? (
|
|
<Dropdown align="end" onSelect={() => {}}>
|
|
<DropdownToggle
|
|
variant="ghost"
|
|
className="d-flex align-items-center gap-2 no-default-caret"
|
|
>
|
|
<MemberPrivileges privileges={invite.privileges} />
|
|
<MaterialIcon type="keyboard_arrow_down" />
|
|
</DropdownToggle>
|
|
<DropdownMenu>
|
|
<ResendInvite invite={invite} />
|
|
<RevokeInvite invite={invite} />
|
|
</DropdownMenu>
|
|
</Dropdown>
|
|
) : (
|
|
<div className="h5 m-0 px-4 fw-semibold">
|
|
<div className="form-control-plaintext border-0">
|
|
<MemberPrivileges privileges={invite.privileges} />
|
|
</div>
|
|
</div>
|
|
)}
|
|
</ShareProjectModalRow>
|
|
) : (
|
|
<OLRow className="project-invite">
|
|
<OLCol xs={8}>
|
|
<div>{invite.email}</div>
|
|
<div className="small">
|
|
{t('invite_not_accepted')}
|
|
.
|
|
{isProjectOwner && <ResendInvite invite={invite} />}
|
|
</div>
|
|
</OLCol>
|
|
|
|
<OLCol xs={3} className="text-end">
|
|
<MemberPrivileges privileges={invite.privileges} />
|
|
</OLCol>
|
|
|
|
{isProjectOwner && (
|
|
<OLCol xs={1} className="text-center">
|
|
<RevokeInvite invite={invite} />
|
|
</OLCol>
|
|
)}
|
|
</OLRow>
|
|
)
|
|
}
|
|
|
|
function ResendInvite({ invite }: { invite: ProjectMember }) {
|
|
const isSharingUpdatesEnabled = useFeatureFlag('sharing-updates')
|
|
const { t } = useTranslation()
|
|
const { monitorRequest, setError, inFlight, setSuccessActionMessage } =
|
|
useShareProjectContext()
|
|
const { projectId } = useProjectContext()
|
|
|
|
// const buttonRef = useRef(null)
|
|
//
|
|
const handleClick = useCallback(
|
|
() =>
|
|
monitorRequest(() => resendInvite(projectId, invite))
|
|
.then(() => setSuccessActionMessage(t('invite_resent')))
|
|
.catch(error => {
|
|
if (error?.response?.status === 404) {
|
|
setError('invite_expired')
|
|
}
|
|
if (error?.response?.status === 429) {
|
|
setError('invite_resend_limit_hit')
|
|
}
|
|
})
|
|
.finally(() => {
|
|
// NOTE: disabled as react-bootstrap v0.33.1 isn't forwarding the ref to the `button`
|
|
// if (buttonRef.current) {
|
|
// buttonRef.current.blur()
|
|
// }
|
|
if (document.activeElement) {
|
|
;(document.activeElement as HTMLElement).blur()
|
|
}
|
|
}),
|
|
[invite, monitorRequest, projectId, setError, setSuccessActionMessage, t]
|
|
)
|
|
|
|
return isSharingUpdatesEnabled ? (
|
|
<OLDropdownMenuItem
|
|
as="button"
|
|
leadingIcon={<MaterialIcon type="mail" unfilled />}
|
|
onClick={handleClick}
|
|
disabled={inFlight}
|
|
>
|
|
{t('resend_invite')}
|
|
</OLDropdownMenuItem>
|
|
) : (
|
|
<OLButton
|
|
variant="link"
|
|
className="btn-inline-link"
|
|
onClick={handleClick}
|
|
disabled={inFlight}
|
|
// ref={buttonRef}
|
|
>
|
|
{t('resend')}
|
|
</OLButton>
|
|
)
|
|
}
|
|
|
|
function RevokeInvite({ invite }: { invite: ProjectMember }) {
|
|
const isSharingUpdatesEnabled = useFeatureFlag('sharing-updates')
|
|
const { t } = useTranslation()
|
|
const { monitorRequest, setSuccessActionMessage } = useShareProjectContext()
|
|
const { projectId, project, updateProject } = useProjectContext()
|
|
const { invites, members } = project || {}
|
|
|
|
function handleClick(event: React.MouseEvent) {
|
|
event.preventDefault()
|
|
|
|
monitorRequest(() => revokeInvite(projectId, invite)).then(() => {
|
|
const updatedInvites =
|
|
invites?.filter(existing => existing !== invite) || []
|
|
updateProject({
|
|
invites: updatedInvites,
|
|
})
|
|
sendMB('collaborator-invite-revoked', {
|
|
project_id: projectId,
|
|
current_invites_amount: updatedInvites.length,
|
|
current_collaborators_amount: members?.length || 0,
|
|
})
|
|
setSuccessActionMessage(t('invite_revoked'))
|
|
})
|
|
}
|
|
|
|
return isSharingUpdatesEnabled ? (
|
|
<OLDropdownMenuItem
|
|
as="button"
|
|
leadingIcon={<MaterialIcon type="block" unfilled />}
|
|
variant="danger"
|
|
onClick={handleClick}
|
|
>
|
|
{t('revoke_invite')}
|
|
</OLDropdownMenuItem>
|
|
) : (
|
|
<OLTooltip
|
|
id="revoke-invite"
|
|
description={t('revoke_invite')}
|
|
overlayProps={{ placement: 'bottom' }}
|
|
>
|
|
<OLButton
|
|
variant="link"
|
|
onClick={handleClick}
|
|
aria-label={t('revoke')}
|
|
className="btn-inline-link text-decoration-none"
|
|
>
|
|
<MaterialIcon type="clear" />
|
|
</OLButton>
|
|
</OLTooltip>
|
|
)
|
|
}
|