mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-04-08 02:52:39 -05:00
Improved staff empty display (#19810)
ref https://linear.app/tryghost/issue/DES-84 - changed display to not show tabs when there's no staff users (only owner) - automatically switch to Invites tab in the Staff section after sending an invite - updated toast messages on failure --------- Co-authored-by: Steve Larson <9larsons@gmail.com>
This commit is contained in:
parent
f8a55de743
commit
7dcddb2e75
5 changed files with 54 additions and 23 deletions
|
@ -105,9 +105,11 @@ export interface TabViewProps<ID = string> {
|
|||
buttonBorder?: boolean;
|
||||
width?: TabWidth;
|
||||
containerClassName?: string;
|
||||
testId?: string;
|
||||
}
|
||||
|
||||
function TabView<ID extends string = string>({
|
||||
testId,
|
||||
tabs,
|
||||
onTabChange,
|
||||
selectedTab,
|
||||
|
@ -130,7 +132,7 @@ function TabView<ID extends string = string>({
|
|||
};
|
||||
|
||||
return (
|
||||
<section className={containerClassName}>
|
||||
<section className={containerClassName} data-testid={testId}>
|
||||
<TabList
|
||||
border={border}
|
||||
buttonBorder={buttonBorder}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import NiceModal from '@ebay/nice-modal-react';
|
||||
import validator from 'validator';
|
||||
import {APIError} from '@tryghost/admin-x-framework/errors';
|
||||
import {HostLimitError, useLimiter} from '../../../hooks/useLimiter';
|
||||
import {Modal, Radio, TextField, showToast} from '@tryghost/admin-x-design-system';
|
||||
import {useAddInvite, useBrowseInvites} from '@tryghost/admin-x-framework/api/invites';
|
||||
|
@ -127,13 +128,21 @@ const InviteUserModal = NiceModal.create(() => {
|
|||
});
|
||||
|
||||
modal.remove();
|
||||
updateRoute('staff');
|
||||
updateRoute('staff?tab=invited');
|
||||
} catch (e) {
|
||||
setSaveState('error');
|
||||
|
||||
let message = (<span><strong>Your invitation failed to send.</strong><br/>If the problem persists, <a href="https://ghost.org/contact"><u>contact support</u>.</a>.</span>);
|
||||
if (e instanceof APIError) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let data = e.data as any; // we have unknown data types in the APIError/error classes
|
||||
if (data?.errors?.[0]?.type === 'EmailError') {
|
||||
message = (<span><strong>Your invitation failed to send</strong><br/>Please check your Mailgun configuration. If the problem persists, <a href="https://ghost.org/contact"><u>contact support</u>.</a></span>);
|
||||
}
|
||||
}
|
||||
showToast({
|
||||
message: `Failed to send invitation to ${email}`,
|
||||
type: 'error'
|
||||
message,
|
||||
type: 'neutral',
|
||||
icon: 'warning'
|
||||
});
|
||||
handleError(e, {withToast: false});
|
||||
return;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React, {useState} from 'react';
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import TopLevelGroup from '../../TopLevelGroup';
|
||||
import clsx from 'clsx';
|
||||
import useQueryParams from '../../../hooks/useQueryParams';
|
||||
import useStaffUsers from '../../../hooks/useStaffUsers';
|
||||
import {Avatar, Button, List, ListItem, NoValueLabel, TabView, showToast, withErrorBoundary} from '@tryghost/admin-x-design-system';
|
||||
import {User, hasAdminAccess, isContributorUser, isEditorUser} from '@tryghost/admin-x-framework/api/users';
|
||||
|
@ -223,33 +224,51 @@ const Users: React.FC<{ keywords: string[], highlight?: boolean }> = ({keywords,
|
|||
}} />
|
||||
);
|
||||
|
||||
const [selectedTab, setSelectedTab] = useState('users-admins');
|
||||
const tabParam = useQueryParams().getParam('tab');
|
||||
const defaultTab = tabParam || 'administrators';
|
||||
const [selectedTab, setSelectedTab] = useState(defaultTab);
|
||||
|
||||
useEffect(() => {
|
||||
if (tabParam) {
|
||||
setSelectedTab(tabParam);
|
||||
}
|
||||
}, [tabParam]);
|
||||
|
||||
const updateSelectedTab = (newTab: string) => {
|
||||
updateRoute(`staff?tab=${newTab}`);
|
||||
setSelectedTab(newTab);
|
||||
};
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
id: 'users-admins',
|
||||
id: 'administrators',
|
||||
title: 'Administrators',
|
||||
contents: (<UsersList groupname='administrators' users={adminUsers} />)
|
||||
contents: (<UsersList groupname='administrators' users={adminUsers} />),
|
||||
counter: adminUsers.length ? adminUsers.length : undefined
|
||||
},
|
||||
{
|
||||
id: 'users-editors',
|
||||
id: 'editors',
|
||||
title: 'Editors',
|
||||
contents: (<UsersList groupname='editors' users={editorUsers} />)
|
||||
contents: (<UsersList groupname='editors' users={editorUsers} />),
|
||||
counter: editorUsers.length ? editorUsers.length : undefined
|
||||
},
|
||||
{
|
||||
id: 'users-authors',
|
||||
id: 'authors',
|
||||
title: 'Authors',
|
||||
contents: (<UsersList groupname='authors' users={authorUsers} />)
|
||||
contents: (<UsersList groupname='authors' users={authorUsers} />),
|
||||
counter: authorUsers.length ? authorUsers.length : undefined
|
||||
},
|
||||
{
|
||||
id: 'users-contributors',
|
||||
id: 'contributors',
|
||||
title: 'Contributors',
|
||||
contents: (<UsersList groupname='contributors' users={contributorUsers} />)
|
||||
contents: (<UsersList groupname='contributors' users={contributorUsers} />),
|
||||
counter: contributorUsers.length ? contributorUsers.length : undefined
|
||||
},
|
||||
{
|
||||
id: 'users-invited',
|
||||
id: 'invited',
|
||||
title: 'Invited',
|
||||
contents: (<InvitesUserList users={invites} />)
|
||||
contents: (<InvitesUserList users={invites} />),
|
||||
counter: invites.length ? invites.length : undefined
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -263,7 +282,8 @@ const Users: React.FC<{ keywords: string[], highlight?: boolean }> = ({keywords,
|
|||
title='Staff'
|
||||
>
|
||||
<Owner user={ownerUser} />
|
||||
<TabView selectedTab={selectedTab} tabs={tabs} onTabChange={setSelectedTab} />
|
||||
{/* if there are no users besides the owner user, hide the tabs*/}
|
||||
{(users.length > 1 || invites.length > 0) && <TabView selectedTab={selectedTab} tabs={tabs} testId='user-tabview' onTabChange={updateSelectedTab} />}
|
||||
{hasNextPage && <Button
|
||||
label={`Load more (showing ${users.length}/${totalUsers} users)`}
|
||||
link
|
||||
|
|
|
@ -128,7 +128,7 @@ test.describe('User actions', async () => {
|
|||
await confirmation.getByRole('button', {name: 'Delete user'}).click();
|
||||
|
||||
await expect(page.getByTestId('toast-success')).toHaveText(/User deleted/);
|
||||
await expect(activeTab.getByTestId('user-list-item')).toHaveCount(0);
|
||||
await expect(activeTab.getByTestId('user-tabview')).toHaveCount(0);
|
||||
|
||||
expect(lastApiRequests.deleteUser?.url).toMatch(new RegExp(`/users/${authorUser.id}`));
|
||||
});
|
||||
|
|
|
@ -16,10 +16,10 @@ test.describe('User roles', async () => {
|
|||
await expect(section.getByTestId('owner-user')).toHaveText(/owner@test\.com/);
|
||||
|
||||
await expect(section.getByRole('tab')).toHaveText([
|
||||
'Administrators',
|
||||
'Editors',
|
||||
'Authors',
|
||||
'Contributors',
|
||||
'Administrators1',
|
||||
'Editors1',
|
||||
'Authors1',
|
||||
'Contributors1',
|
||||
'Invited'
|
||||
]);
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue