Work on manage tab to improve UI (#4366)

This commit is contained in:
Shailesh Parmar 2022-04-22 22:17:42 +05:30 committed by GitHub
parent 1c4bea4613
commit 8c60b22180
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 480 additions and 281 deletions

View File

@ -1,52 +0,0 @@
import React from 'react';
import { TITLE_FOR_NON_ADMIN_ACTION } from '../../constants/constants';
import { Button } from '../buttons/Button/Button';
import NonAdminAction from '../common/non-admin-action/NonAdminAction';
type DeleteWidgetBodyProps = {
header: string;
description: string;
buttonText: string;
isOwner?: boolean;
hasPermission: boolean;
onClick: () => void;
};
const DeleteWidgetBody = ({
header,
description,
buttonText,
isOwner,
hasPermission,
onClick,
}: DeleteWidgetBodyProps) => {
return (
<div className="tw-flex tw-justify-between tw-px-4 tw-py-2">
<div data-testid="danger-zone-text">
<h4 className="tw-text-base" data-testid="danger-zone-text-title">
{header}
</h4>
<p data-testid="danger-zone-text-para">{description}</p>
</div>
<NonAdminAction
className="tw-self-center"
html={<p>{TITLE_FOR_NON_ADMIN_ACTION}</p>}
isOwner={isOwner}
position="left">
<Button
className="tw-px-2 tw-py-1 tw-rounded tw-h-auto tw-self-center tw-shadow"
data-testid="delete-button"
disabled={!hasPermission}
size="custom"
theme="primary"
type="button"
variant="outlined"
onClick={onClick}>
{buttonText}
</Button>
</NonAdminAction>
</div>
);
};
export default DeleteWidgetBody;

View File

@ -11,36 +11,27 @@
* limitations under the License.
*/
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { AxiosError, AxiosResponse } from 'axios';
import classNames from 'classnames';
import { isUndefined } from 'lodash';
import { observer } from 'mobx-react';
import { TableDetail } from 'Models';
import React, { Fragment, FunctionComponent, useEffect, useState } from 'react';
import { useHistory } from 'react-router-dom';
import appState from '../../AppState';
import { useAuthContext } from '../../authentication/auth-provider/AuthProvider';
import { deleteEntity } from '../../axiosAPIs/miscAPI';
import { getCategory } from '../../axiosAPIs/tagAPI';
import { FQN_SEPARATOR_CHAR } from '../../constants/char.constants';
import { ENTITY_DELETE_STATE } from '../../constants/entity.constants';
import { EntityType } from '../../enums/entity.enum';
import { Operation } from '../../generated/entity/policies/accessControl/rule';
import { useAuth } from '../../hooks/authHooks';
import jsonData from '../../jsons/en';
import { getOwnerList } from '../../utils/ManageUtils';
import SVGIcons from '../../utils/SvgUtils';
import { showErrorToast, showSuccessToast } from '../../utils/ToastUtils';
import { Button } from '../buttons/Button/Button';
import { showErrorToast } from '../../utils/ToastUtils';
import CardListItem from '../card-list/CardListItem/CardWithListItems';
import { CardWithListItems } from '../card-list/CardListItem/CardWithListItems.interface';
import DeleteWidget from '../common/DeleteWidget/DeleteWidget';
import NonAdminAction from '../common/non-admin-action/NonAdminAction';
import ToggleSwitchV1 from '../common/toggle-switch/ToggleSwitchV1';
import DropDownList from '../dropdown/DropDownList';
import OwnerWidget from '../common/OwnerWidget/OwnerWidget';
import Loader from '../Loader/Loader';
import EntityDeleteModal from '../Modals/EntityDeleteModal/EntityDeleteModal';
import DeleteWidgetBody from './DeleteWidgetBody';
import { ManageProps, Status } from './ManageTab.interface';
const ManageTab: FunctionComponent<ManageProps> = ({
@ -58,9 +49,9 @@ const ManageTab: FunctionComponent<ManageProps> = ({
allowSoftDelete,
isRecursiveDelete,
deletEntityMessage,
manageSectionType,
handleIsJoinable,
}: ManageProps) => {
const history = useHistory();
const { userPermissions, isAdminUser } = useAuth();
const { isAuthDisabled } = useAuthContext();
@ -74,17 +65,11 @@ const ManageTab: FunctionComponent<ManageProps> = ({
const [listOwners, setListOwners] = useState(getOwnerList());
const [owner, setOwner] = useState(currentUser);
const [isLoadingTierData, setIsLoadingTierData] = useState<boolean>(false);
const [entityDeleteState, setEntityDeleteState] =
useState<typeof ENTITY_DELETE_STATE>(ENTITY_DELETE_STATE);
const getOwnerById = (): string => {
return listOwners.find((item) => item.value === owner)?.name || '';
};
const getOwnerGroup = () => {
return allowTeamOwner ? ['Teams', 'Users'] : ['Users'];
};
const setInitialOwnerLoadingState = () => {
setStatusOwner('initial');
};
@ -164,115 +149,20 @@ const ManageTab: FunctionComponent<ManageProps> = ({
setActiveTier(cardId);
};
const handleOnEntityDelete = (softDelete = false) => {
setEntityDeleteState((prev) => ({ ...prev, state: true, softDelete }));
};
const handleOnEntityDeleteCancel = () => {
setEntityDeleteState(ENTITY_DELETE_STATE);
};
const prepareEntityType = () => {
const services = [
EntityType.DASHBOARD_SERVICE,
EntityType.DATABASE_SERVICE,
EntityType.MESSAGING_SERVICE,
EntityType.PIPELINE_SERVICE,
];
if (services.includes((entityType || '') as EntityType)) {
return `services/${entityType}s`;
} else {
return `${entityType}s`;
}
};
const prepareDeleteMessage = () => {
return `Once you delete this ${entityType}, it will be removed permanently`;
};
const handleOnEntityDeleteConfirm = () => {
setEntityDeleteState((prev) => ({ ...prev, loading: 'waiting' }));
deleteEntity(
prepareEntityType(),
entityId,
isRecursiveDelete,
allowSoftDelete
)
.then((res: AxiosResponse) => {
if (res.status === 200) {
setTimeout(() => {
handleOnEntityDeleteCancel();
showSuccessToast(
jsonData['api-success-messages']['delete-entity-success']
);
setTimeout(() => {
history.push('/');
}, 500);
}, 1000);
} else {
showErrorToast(
jsonData['api-error-messages']['unexpected-server-response']
);
}
})
.catch((error: AxiosError) => {
showErrorToast(
error,
jsonData['api-error-messages']['delete-entity-error']
);
})
.finally(() => {
handleOnEntityDeleteCancel();
});
};
const getDeleteModal = () => {
if (allowDelete && entityDeleteState.state) {
return (
<EntityDeleteModal
bodyText={deletEntityMessage || prepareDeleteMessage()}
entityName={entityName as string}
entityType={entityType as string}
loadingState={entityDeleteState.loading}
softDelete={entityDeleteState.softDelete}
onCancel={handleOnEntityDeleteCancel}
onConfirm={handleOnEntityDeleteConfirm}
/>
);
} else {
return null;
}
};
const getDeleteEntityWidget = () => {
return allowDelete && entityId && entityName && entityType ? (
<div className="tw-mt-1" data-testid="danger-zone">
<hr className="tw-border-main tw-mb-4" />
<div className="tw-border tw-border-error tw-rounded tw-mt-3 tw-shadow">
{allowSoftDelete && (
<div className="tw-border-b tw-border-error">
<DeleteWidgetBody
buttonText={`Soft delete this ${entityType}`}
description={prepareDeleteMessage()}
<DeleteWidget
allowSoftDelete={allowSoftDelete}
deletEntityMessage={deletEntityMessage}
entityId={entityId}
entityName={entityName}
entityType={entityType}
hasPermission={isAdminUser || isAuthDisabled}
header={`Soft delete ${entityType} ${entityName}`}
isOwner={isAdminUser}
onClick={() => handleOnEntityDelete(true)}
isAdminUser={isAdminUser}
isRecursiveDelete={isRecursiveDelete}
/>
</div>
)}
<DeleteWidgetBody
buttonText={`Delete this ${entityType}`}
description={prepareDeleteMessage()}
hasPermission={isAdminUser || isAuthDisabled}
header={`Delete ${entityType} ${entityName}`}
isOwner={isAdminUser}
onClick={handleOnEntityDelete}
/>
</div>
</div>
) : null;
};
@ -311,29 +201,13 @@ const ManageTab: FunctionComponent<ManageProps> = ({
}
};
const getJoinableWidget = () => {
const isActionAllowed =
const isJoinableActionAllowed = () => {
return (
isAdminUser ||
isAuthDisabled ||
userPermissions[Operation.UpdateTeam] ||
!hasEditAccess;
const joinableSwitch =
isActionAllowed && !isUndefined(teamJoinable) ? (
<div className="tw-flex">
<label htmlFor="join-team">Open to join</label>
<ToggleSwitchV1
checked={teamJoinable}
handleCheck={() => {
handleIsJoinable?.(!teamJoinable);
}}
/>
</div>
) : null;
return !isUndefined(isJoinable) ? (
<div className="tw-mt-3 tw-mb-1">{joinableSwitch}</div>
) : null;
!hasEditAccess
);
};
const ownerName = getOwnerById();
@ -372,23 +246,6 @@ const ManageTab: FunctionComponent<ManageProps> = ({
});
};
const getOwnerUpdateLoader = () => {
return (
<span className="tw-ml-4">
{statusOwner === 'waiting' ? (
<Loader
className="tw-inline-block"
size="small"
style={{ marginBottom: '-4px' }}
type="default"
/>
) : statusOwner === 'success' ? (
<FontAwesomeIcon icon="check" />
) : null}
</span>
);
};
useEffect(() => {
if (!hideTier) {
getTierData();
@ -434,77 +291,33 @@ const ManageTab: FunctionComponent<ManageProps> = ({
className="tw-max-w-3xl tw-mx-auto"
data-testid="manage-tab"
id="manageTabDetails">
<p className="tw-text-base tw-font-medium">
Manage {manageSectionType ? manageSectionType : 'Section'}
</p>
<div
className={classNames('tw-mt-2 tw-pb-4', {
'tw-border-b tw-border-separator tw-mb-4': !hideTier,
})}>
<div>
<span className="tw-mr-2">Owner:</span>
<span className="tw-relative">
<NonAdminAction
html={
<Fragment>
<p>You do not have permissions to update the owner.</p>
</Fragment>
<OwnerWidget
allowTeamOwner={allowTeamOwner}
handleIsJoinable={handleIsJoinable}
handleOwnerSelection={handleOwnerSelection}
handleSelectOwnerDropdown={() =>
setListVisible((visible) => !visible)
}
isOwner={hasEditAccess}
permission={Operation.UpdateOwner}
position="left">
<Button
className={classNames('tw-underline', {
'tw-opacity-40':
!userPermissions[Operation.UpdateOwner] &&
!isAuthDisabled &&
!hasEditAccess,
})}
data-testid="owner-dropdown"
disabled={
!userPermissions[Operation.UpdateOwner] &&
!isAuthDisabled &&
!hasEditAccess
}
size="custom"
theme="primary"
variant="link"
onClick={() => setListVisible((visible) => !visible)}>
{ownerName ? (
<span
className={classNames('tw-truncate', {
'tw-w-52': ownerName.length > 32,
})}
title={ownerName}>
{ownerName}
</span>
) : (
'Select Owner'
)}
<SVGIcons
alt="edit"
className="tw-ml-1"
icon="icon-edit"
title="Edit"
width="12px"
hasEditAccess={hasEditAccess}
isAuthDisabled={isAuthDisabled}
isJoinableActionAllowed={isJoinableActionAllowed()}
listOwners={listOwners}
listVisible={listVisible}
owner={owner}
ownerName={ownerName}
statusOwner={statusOwner}
teamJoinable={teamJoinable}
/>
</Button>
</NonAdminAction>
{listVisible && (
<DropDownList
showSearchBar
dropDownList={listOwners}
groupType="tab"
listGroups={getOwnerGroup()}
value={owner}
onSelect={handleOwnerSelection}
/>
)}
{getOwnerUpdateLoader()}
</span>
</div>
{getJoinableWidget()}
</div>
{getTierCards()}
{getDeleteEntityWidget()}
{getDeleteModal()}
</div>
);
};

View File

@ -16,6 +16,7 @@ import { TableDetail } from 'Models';
export interface ManageProps {
currentTier?: string;
currentUser?: string;
manageSectionType?: string;
hideTier?: boolean;
isJoinable?: boolean;
allowSoftDelete?: boolean;

View File

@ -45,6 +45,10 @@ jest.mock('../common/toggle-switch/ToggleSwitchV1', () => {
return jest.fn().mockImplementation(() => <p>ToggleSwitchV1.Component</p>);
});
jest.mock('../common/DeleteWidget/DeleteWidget', () => {
return jest.fn().mockImplementation(() => <p>DeleteWidget.Component</p>);
});
const mockTierData = {
children: [
{
@ -121,7 +125,9 @@ describe('Test Manage tab Component', () => {
);
const dangerZone = await findByTestId(container, 'danger-zone');
const DeleteWidget = await findByText(container, 'DeleteWidget.Component');
expect(dangerZone).toBeInTheDocument();
expect(DeleteWidget).toBeInTheDocument();
});
});

View File

@ -85,7 +85,7 @@ const EntityDeleteModal: FC<Prop> = ({
data-testid="confirmation-text-input"
disabled={loadingState === 'waiting'}
name="entityName"
placeholder={`${entityType}/${entityName}`}
placeholder="DELETE"
type="text"
value={name}
onChange={handleOnChange}

View File

@ -493,7 +493,7 @@ const TeamDetails = ({
};
return (
<div>
<div className="tw-h-full tw-flex tw-flex-col tw-flex-grow">
{teams.length && currentTeam ? (
<Fragment>
<div
@ -568,11 +568,12 @@ const TeamDetails = ({
<div className="tw-flex tw-flex-col tw-flex-grow">
<TabsPane
activeTab={currentTab}
className="tw-px-6"
setActiveTab={(tab) => setCurrentTab(tab)}
tabs={tabs}
/>
<div className="tw-flex-grow tw-pt-4">
<div className="tw-flex-grow tw-flex tw-flex-col tw-pt-4">
{currentTab === 1 && getUserCards()}
{currentTab === 2 && getDatasetCards()}
@ -580,7 +581,7 @@ const TeamDetails = ({
{currentTab === 3 && getDefaultRoles()}
{currentTab === 4 && (
<div>
<div className="tw-bg-white tw-shadow-md tw-py-4 tw-flex-grow">
<ManageTab
allowDelete
allowSoftDelete
@ -593,6 +594,7 @@ const TeamDetails = ({
entityType="team"
handleIsJoinable={handleOpenToJoinToggle}
isJoinable={currentTeam.isJoinable}
manageSectionType="Team"
onSave={handleManageSave}
/>
</div>

View File

@ -0,0 +1,170 @@
/*
* Copyright 2021 Collate
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { AxiosError, AxiosResponse } from 'axios';
import React, { Fragment, useState } from 'react';
import { useHistory } from 'react-router-dom';
import { deleteEntity } from '../../../axiosAPIs/miscAPI';
import { ENTITY_DELETE_STATE } from '../../../constants/entity.constants';
import { EntityType } from '../../../enums/entity.enum';
import jsonData from '../../../jsons/en';
import { showErrorToast, showSuccessToast } from '../../../utils/ToastUtils';
import EntityDeleteModal from '../../Modals/EntityDeleteModal/EntityDeleteModal';
import DeleteWidgetBody from './DeleteWidgetBody';
interface DeleteSectionProps {
allowSoftDelete?: boolean;
entityName: string;
entityType: string;
deletEntityMessage?: string;
hasPermission: boolean;
isAdminUser?: boolean;
entityId: string;
isRecursiveDelete?: boolean;
}
const DeleteWidget = ({
allowSoftDelete,
entityName,
entityType,
hasPermission,
isAdminUser,
deletEntityMessage,
entityId,
isRecursiveDelete,
}: DeleteSectionProps) => {
const history = useHistory();
const [entityDeleteState, setEntityDeleteState] =
useState<typeof ENTITY_DELETE_STATE>(ENTITY_DELETE_STATE);
const prepareDeleteMessage = (softDelete = false) => {
const softDeleteText = `Soft deleting will deactivate the ${entityName}. This will disable any discovery, read or write operations on ${entityName}`;
const hardDeleteText = `Once you delete this ${entityType}, it will be removed permanently`;
return softDelete ? softDeleteText : hardDeleteText;
};
const handleOnEntityDelete = (softDelete = false) => {
setEntityDeleteState((prev) => ({ ...prev, state: true, softDelete }));
};
const handleOnEntityDeleteCancel = () => {
setEntityDeleteState(ENTITY_DELETE_STATE);
};
const prepareEntityType = () => {
const services = [
EntityType.DASHBOARD_SERVICE,
EntityType.DATABASE_SERVICE,
EntityType.MESSAGING_SERVICE,
EntityType.PIPELINE_SERVICE,
];
if (services.includes((entityType || '') as EntityType)) {
return `services/${entityType}s`;
} else {
return `${entityType}s`;
}
};
const handleOnEntityDeleteConfirm = () => {
setEntityDeleteState((prev) => ({ ...prev, loading: 'waiting' }));
deleteEntity(
prepareEntityType(),
entityId,
isRecursiveDelete,
entityDeleteState.softDelete
)
.then((res: AxiosResponse) => {
if (res.status === 200) {
setTimeout(() => {
handleOnEntityDeleteCancel();
showSuccessToast(
jsonData['api-success-messages']['delete-entity-success']
);
setTimeout(() => {
history.push('/');
}, 500);
}, 1000);
} else {
showErrorToast(
jsonData['api-error-messages']['unexpected-server-response']
);
}
})
.catch((error: AxiosError) => {
showErrorToast(
error,
jsonData['api-error-messages']['delete-entity-error']
);
})
.finally(() => {
handleOnEntityDeleteCancel();
});
};
const getDeleteModal = () => {
if (entityDeleteState.state) {
return (
<EntityDeleteModal
bodyText={
deletEntityMessage ||
prepareDeleteMessage(entityDeleteState.softDelete)
}
entityName={entityName}
entityType={entityType}
loadingState={entityDeleteState.loading}
softDelete={entityDeleteState.softDelete}
onCancel={handleOnEntityDeleteCancel}
onConfirm={handleOnEntityDeleteConfirm}
/>
);
} else {
return null;
}
};
return (
<Fragment>
<p className="tw-text-base tw-font-medium">Delete section</p>
<div className="tw-mt-1 tw-bg-white" data-testid="danger-zone">
<div className="tw-border tw-border-error tw-rounded tw-mt-3 tw-shadow">
{allowSoftDelete && (
<div className="tw-border-b">
<DeleteWidgetBody
buttonText="Soft delete"
description={prepareDeleteMessage(true)}
hasPermission={hasPermission}
header={`Soft delete ${entityType} ${entityName}`}
isOwner={isAdminUser}
onClick={() => handleOnEntityDelete(true)}
/>
</div>
)}
<DeleteWidgetBody
buttonText="Delete"
description={prepareDeleteMessage()}
hasPermission={hasPermission}
header={`Delete ${entityType} ${entityName}`}
isOwner={isAdminUser}
onClick={() => handleOnEntityDelete(false)}
/>
</div>
</div>
{getDeleteModal()}
</Fragment>
);
};
export default DeleteWidget;

View File

@ -0,0 +1,66 @@
/*
* Copyright 2021 Collate
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import { TITLE_FOR_NON_ADMIN_ACTION } from '../../../constants/constants';
import NonAdminAction from '../non-admin-action/NonAdminAction';
type DeleteWidgetBodyProps = {
header: string;
description: string;
buttonText: string;
isOwner?: boolean;
hasPermission: boolean;
onClick: () => void;
};
const DeleteWidgetBody = ({
header,
description,
buttonText,
isOwner,
hasPermission,
onClick,
}: DeleteWidgetBodyProps) => {
return (
<div className="tw-flex tw-justify-between tw-px-5 tw-py-3">
<div className="tw-w-10/12" data-testid="danger-zone-text">
<p
className="tw-text-sm tw-mb-1 tw-font-medium"
data-testid="danger-zone-text-title">
{header}
</p>
<p
className="tw-text-grey-muted tw-text-xs"
data-testid="danger-zone-text-para">
{description}
</p>
</div>
<NonAdminAction
className="tw-self-center"
html={<p>{TITLE_FOR_NON_ADMIN_ACTION}</p>}
isOwner={isOwner}
position="left">
<button
className="tw-px-3 tw-py-1 tw-rounded tw-h-auto tw-self-center tw-font-medium tw-delete-outline-button "
data-testid="delete-button"
disabled={!hasPermission}
onClick={onClick}>
{buttonText}
</button>
</NonAdminAction>
</div>
);
};
export default DeleteWidgetBody;

View File

@ -0,0 +1,186 @@
/*
* Copyright 2021 Collate
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import classNames from 'classnames';
import { isUndefined } from 'lodash';
import React, { Fragment } from 'react';
import { Operation } from '../../../generated/entity/policies/policy';
import { useAuth } from '../../../hooks/authHooks';
import { Button } from '../../buttons/Button/Button';
import DropDownList from '../../dropdown/DropDownList';
import Loader from '../../Loader/Loader';
import { Status } from '../../ManageTab/ManageTab.interface';
import NonAdminAction from '../non-admin-action/NonAdminAction';
import ToggleSwitchV1 from '../toggle-switch/ToggleSwitchV1';
interface OwnerWidgetProps {
isJoinableActionAllowed: boolean;
hasEditAccess: boolean;
isAuthDisabled: boolean;
listVisible: boolean;
teamJoinable?: boolean;
allowTeamOwner?: boolean;
ownerName: string;
statusOwner: Status;
owner: string;
listOwners: {
name: string;
value: string;
group: string;
type: string;
}[];
handleIsJoinable?: (bool: boolean) => void;
handleSelectOwnerDropdown: () => void;
handleOwnerSelection: (
_e: React.MouseEvent<HTMLElement, MouseEvent>,
value?: string | undefined
) => void;
}
const OwnerWidget = ({
isJoinableActionAllowed,
teamJoinable,
isAuthDisabled,
hasEditAccess,
ownerName,
listVisible,
owner,
allowTeamOwner,
statusOwner,
listOwners,
handleIsJoinable,
handleSelectOwnerDropdown,
handleOwnerSelection,
}: OwnerWidgetProps) => {
const { userPermissions } = useAuth();
const getOwnerGroup = () => {
return allowTeamOwner ? ['Teams', 'Users'] : ['Users'];
};
const getOwnerUpdateLoader = () => {
switch (statusOwner) {
case 'waiting':
return (
<Loader
className="tw-inline-block tw-ml-2"
size="small"
style={{ marginBottom: '-4px' }}
type="default"
/>
);
case 'success':
return <FontAwesomeIcon className="tw-ml-2" icon="check" />;
default:
return <></>;
}
};
return (
<Fragment>
<div className="tw-mt-1 tw-bg-white">
<div className="tw-border tw-border-main tw-rounded tw-mt-3 tw-shadow">
<div className="tw-flex tw-justify-between tw-items-center tw-px-5 tw-py-3">
<div className="tw-w-10/12">
<p className="tw-text-sm tw-mb-1 tw-font-medium">Owner</p>
<p className="tw-text-grey-muted tw-text-xs">
Lorem ipsum dolor, sit amet consectetur adipisicing elit.
Necessitatibus, sint.
</p>
</div>
<span className="tw-relative">
<NonAdminAction
html={
<Fragment>
<p>You do not have permissions to update the owner.</p>
</Fragment>
}
isOwner={hasEditAccess}
permission={Operation.UpdateOwner}
position="left">
<Button
className={classNames('tw-underline', {
'tw-opacity-40':
!userPermissions[Operation.UpdateOwner] &&
!isAuthDisabled &&
!hasEditAccess,
})}
data-testid="owner-dropdown"
disabled={
!userPermissions[Operation.UpdateOwner] &&
!isAuthDisabled &&
!hasEditAccess
}
size="custom"
theme="primary"
variant="link"
onClick={handleSelectOwnerDropdown}>
{ownerName ? (
<span
className={classNames('tw-truncate', {
'tw-w-52': ownerName.length > 32,
})}
title={ownerName}>
{ownerName}
</span>
) : (
'Add Owner'
)}
{getOwnerUpdateLoader()}
</Button>
</NonAdminAction>
{listVisible && (
<DropDownList
horzPosRight
showSearchBar
dropDownList={listOwners}
groupType="tab"
listGroups={getOwnerGroup()}
value={owner}
onSelect={handleOwnerSelection}
/>
)}
</span>
</div>
{isJoinableActionAllowed && !isUndefined(teamJoinable) && (
<div className="tw-flex tw-justify-between tw-px-5 tw-py-3 tw-border-t">
<div className="tw-w-10/12">
<p className="tw-text-sm tw-mb-1 tw-font-medium">
Open to join
</p>
<p className="tw-text-grey-muted tw-text-xs">
Lorem ipsum dolor, sit amet consectetur adipisicing elit.
Necessitatibus, sint.
</p>
</div>
<div className="tw-flex tw-items-center">
<ToggleSwitchV1
checked={teamJoinable}
handleCheck={() => {
handleIsJoinable?.(!teamJoinable);
}}
/>
</div>
</div>
)}
</div>
</div>
</Fragment>
);
};
export default OwnerWidget;

View File

@ -457,4 +457,11 @@
.Toastify__toast-body {
@apply tw-items-start;
}
/* delete button style */
.tw-delete-outline-button {
@apply tw-border-gray-300 tw-border tw-text-error focus:tw-outline-none focus:tw-bg-transparent hover:tw-border-error focus:tw-border-error
hover:tw-text-error focus:tw-text-error focus:tw-ring focus:tw-ring-error;
}
}