123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194 |
- import {cloneElement, Fragment} from 'react';
- import type {RouteComponentProps} from 'react-router';
- import {openInviteMembersModal} from 'sentry/actionCreators/modal';
- import FeatureDisabled from 'sentry/components/acl/featureDisabled';
- import {Button} from 'sentry/components/button';
- import HookOrDefault from 'sentry/components/hookOrDefault';
- import {Hovercard} from 'sentry/components/hovercard';
- import {Tooltip} from 'sentry/components/tooltip';
- import {IconMail} from 'sentry/icons';
- import {t} from 'sentry/locale';
- import type {Member, Organization} from 'sentry/types/organization';
- import routeTitleGen from 'sentry/utils/routeTitle';
- import withOrganization from 'sentry/utils/withOrganization';
- import DeprecatedAsyncView from 'sentry/views/deprecatedAsyncView';
- import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader';
- type Props = {
- organization: Organization;
- children?: any;
- } & RouteComponentProps<{}, {}>;
- type State = DeprecatedAsyncView['state'] & {
- inviteRequests: Member[];
- };
- const InviteMembersButtonHook = HookOrDefault({
- hookName: 'member-invite-button:customization',
- defaultComponent: ({children, organization, onTriggerModal}) => {
- const isSsoRequired = organization.requiresSso;
- const disabled = isSsoRequired || !organization.features.includes('invite-members');
- return children({disabled, isSsoRequired, onTriggerModal});
- },
- });
- class OrganizationMembersWrapper extends DeprecatedAsyncView<Props, State> {
- getEndpoints(): ReturnType<DeprecatedAsyncView['getEndpoints']> {
- const {organization} = this.props;
- return [
- ['inviteRequests', `/organizations/${organization.slug}/invite-requests/`],
- ['requestList', `/organizations/${organization.slug}/access-requests/`],
- ];
- }
- getTitle() {
- const {organization} = this.props;
- return routeTitleGen(t('Members'), organization.slug, false);
- }
- get onRequestsTab() {
- return location.pathname.includes('/requests/');
- }
- get hasWriteAccess() {
- const {organization} = this.props;
- if (!organization || !organization.access) {
- return false;
- }
- return organization.access.includes('member:write');
- }
- get showInviteRequests() {
- return this.hasWriteAccess;
- }
- get showNavTabs() {
- const {requestList} = this.state;
- // show the requests tab if there are pending team requests,
- // or if the user has access to approve or deny invite requests
- return (requestList && requestList.length > 0) || this.showInviteRequests;
- }
- get requestCount() {
- const {requestList, inviteRequests} = this.state;
- let count = requestList.length;
- // if the user can't see the invite requests panel,
- // exclude those requests from the total count
- if (this.showInviteRequests) {
- count += inviteRequests.length;
- }
- return count ? count.toString() : null;
- }
- removeAccessRequest = (id: string) =>
- this.setState(state => ({
- requestList: state.requestList.filter(request => request.id !== id),
- }));
- removeInviteRequest = (id: string) =>
- this.setState(state => ({
- inviteRequests: state.inviteRequests.filter(request => request.id !== id),
- }));
- updateInviteRequest = (id: string, data: Partial<Member>) =>
- this.setState(state => {
- const inviteRequests = [...state.inviteRequests];
- const inviteIndex = inviteRequests.findIndex(request => request.id === id);
- inviteRequests[inviteIndex] = {...inviteRequests[inviteIndex], ...data};
- return {inviteRequests};
- });
- renderBody() {
- const {children, organization} = this.props;
- const {requestList, inviteRequests} = this.state;
- const action = (
- <InviteMembersButtonHook
- organization={organization}
- onTriggerModal={() =>
- openInviteMembersModal({
- onClose: () => {
- this.fetchData();
- },
- source: 'members_settings',
- })
- }
- >
- {renderInviteMembersButton}
- </InviteMembersButtonHook>
- );
- return (
- <Fragment>
- <SettingsPageHeader title="Members" action={action} />
- {children &&
- cloneElement(children, {
- requestList,
- inviteRequests,
- onRemoveInviteRequest: this.removeInviteRequest,
- onUpdateInviteRequest: this.updateInviteRequest,
- onRemoveAccessRequest: this.removeAccessRequest,
- showInviteRequests: this.showInviteRequests,
- })}
- </Fragment>
- );
- }
- }
- function renderInviteMembersButton({
- disabled,
- isSsoRequired,
- onTriggerModal,
- }: {
- onTriggerModal: () => void;
- disabled?: boolean;
- isSsoRequired?: boolean;
- }) {
- const action = (
- <Button
- priority="primary"
- size="sm"
- onClick={onTriggerModal}
- data-test-id="email-invite"
- icon={<IconMail />}
- disabled={disabled}
- >
- {t('Invite Members')}
- </Button>
- );
- return disabled ? (
- isSsoRequired ? (
- <Tooltip
- skipWrapper
- title={t(
- `Your organization must use its single sign-on provider to register new members.`
- )}
- >
- {action}
- </Tooltip>
- ) : (
- <Hovercard
- body={
- <FeatureDisabled
- featureName={t('Invite Members')}
- features="organizations:invite-members"
- hideHelpToggle
- />
- }
- >
- {action}
- </Hovercard>
- )
- ) : (
- action
- );
- }
- export default withOrganization(OrganizationMembersWrapper);
|