mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-03 23:00:14 -05:00
Updated AdminX features for different roles (#18131)
refs https://github.com/TryGhost/Product/issues/3832 --- This pull request introduces a new `MainContent` component that handles the role-based access and rendering of the settings page and the sidebar. It also refactors and improves the UI and logic of the `UserDetailModal` and the `Users` components, and updates the footer component to use the new settings page and profile modal for editors. Additionally, it removes unused code and adds new helper functions for checking the user's roles and permissions.
This commit is contained in:
parent
a1f056ee86
commit
00e598b365
9 changed files with 189 additions and 113 deletions
|
@ -1,10 +1,7 @@
|
|||
import ExitSettingsButton from './components/ExitSettingsButton';
|
||||
import GlobalDataProvider from './components/providers/GlobalDataProvider';
|
||||
import Heading from './admin-x-ds/global/Heading';
|
||||
import MainContent from './MainContent';
|
||||
import NiceModal from '@ebay/nice-modal-react';
|
||||
import RoutingProvider, {ExternalLink} from './components/providers/RoutingProvider';
|
||||
import Settings from './components/Settings';
|
||||
import Sidebar from './components/Sidebar';
|
||||
import clsx from 'clsx';
|
||||
import {GlobalDirtyStateProvider} from './hooks/useGlobalDirtyState';
|
||||
import {OfficialTheme, ServicesProvider} from './components/providers/ServiceProvider';
|
||||
|
@ -49,27 +46,7 @@ function App({ghostVersion, officialThemes, zapierTemplates, externalNavigate, d
|
|||
>
|
||||
<Toaster />
|
||||
<NiceModal.Provider>
|
||||
<div className='relative z-20 px-6 py-4 tablet:fixed'>
|
||||
<ExitSettingsButton />
|
||||
</div>
|
||||
|
||||
{/* Main container */}
|
||||
<div className="mx-auto flex max-w-[1080px] flex-col px-[5vmin] py-[12vmin] tablet:flex-row tablet:items-start tablet:gap-x-10 tablet:py-[8vmin]" id="admin-x-settings-content">
|
||||
|
||||
{/* Sidebar */}
|
||||
<div className="sticky top-[-42px] z-20 min-w-[260px] grow-0 md:top-[-52px] tablet:fixed tablet:top-[8vmin] tablet:basis-[260px]">
|
||||
<div className='-mx-6 h-[84px] bg-white px-6 tablet:m-0 tablet:bg-transparent tablet:p-0'>
|
||||
<Heading>Settings</Heading>
|
||||
</div>
|
||||
<div className="relative mt-[-32px] w-full overflow-x-hidden after:absolute after:inset-x-0 after:top-0 after:hidden after:h-[40px] after:bg-gradient-to-b after:from-white after:to-transparent after:content-[''] dark:after:from-black tablet:w-[260px] tablet:after:!visible tablet:after:!block">
|
||||
<Sidebar />
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative flex-auto pt-[3vmin] tablet:ml-[300px] tablet:pt-[85px]">
|
||||
{/* <div className='pointer-events-none fixed inset-x-0 top-0 z-[5] hidden h-[80px] bg-gradient-to-t from-transparent to-white to-60% dark:to-black tablet:!visible tablet:!block'></div> */}
|
||||
<Settings />
|
||||
</div>
|
||||
</div>
|
||||
<MainContent />
|
||||
</NiceModal.Provider>
|
||||
</div>
|
||||
</GlobalDirtyStateProvider>
|
||||
|
|
67
apps/admin-x-settings/src/MainContent.tsx
Normal file
67
apps/admin-x-settings/src/MainContent.tsx
Normal file
|
@ -0,0 +1,67 @@
|
|||
import ExitSettingsButton from './components/ExitSettingsButton';
|
||||
import Heading from './admin-x-ds/global/Heading';
|
||||
import Settings from './components/Settings';
|
||||
import Sidebar from './components/Sidebar';
|
||||
import Users from './components/settings/general/Users';
|
||||
import useRouting from './hooks/useRouting';
|
||||
import {ReactNode, useEffect} from 'react';
|
||||
import {canAccessSettings, isEditorUser} from './api/users';
|
||||
import {useGlobalData} from './components/providers/GlobalDataProvider';
|
||||
|
||||
const Page: React.FC<{children: ReactNode}> = ({children}) => {
|
||||
return <>
|
||||
<div className='relative z-20 px-6 py-4 tablet:fixed'>
|
||||
<ExitSettingsButton />
|
||||
</div>
|
||||
|
||||
<div className="mx-auto flex max-w-[1080px] flex-col px-[5vmin] py-[12vmin] tablet:flex-row tablet:items-start tablet:gap-x-10 tablet:py-[8vmin]" id="admin-x-settings-content">
|
||||
{children}
|
||||
</div>
|
||||
</>;
|
||||
};
|
||||
|
||||
const MainContent: React.FC = () => {
|
||||
const {currentUser} = useGlobalData();
|
||||
const {route, updateRoute} = useRouting();
|
||||
|
||||
useEffect(() => {
|
||||
if (!canAccessSettings(currentUser) && route !== `users/show/${currentUser.slug}`) {
|
||||
updateRoute(`users/show/${currentUser.slug}`);
|
||||
}
|
||||
}, [currentUser, route, updateRoute]);
|
||||
|
||||
if (!canAccessSettings(currentUser)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isEditorUser(currentUser)) {
|
||||
return (
|
||||
<Page>
|
||||
<div className='w-full'>
|
||||
<Heading className='mb-10'>Settings</Heading>
|
||||
<Users keywords={[]} />
|
||||
</div>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Page>
|
||||
{/* Sidebar */}
|
||||
<div className="sticky top-[-42px] z-20 min-w-[260px] grow-0 md:top-[-52px] tablet:fixed tablet:top-[8vmin] tablet:basis-[260px]">
|
||||
<div className='-mx-6 h-[84px] bg-white px-6 tablet:m-0 tablet:bg-transparent tablet:p-0'>
|
||||
<Heading>Settings</Heading>
|
||||
</div>
|
||||
<div className="relative mt-[-32px] w-full overflow-x-hidden after:absolute after:inset-x-0 after:top-0 after:hidden after:h-[40px] after:bg-gradient-to-b after:from-white after:to-transparent after:content-[''] dark:after:from-black tablet:w-[260px] tablet:after:!visible tablet:after:!block">
|
||||
<Sidebar />
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative flex-auto pt-[3vmin] tablet:ml-[300px] tablet:pt-[85px]">
|
||||
{/* <div className='pointer-events-none fixed inset-x-0 top-0 z-[5] hidden h-[80px] bg-gradient-to-t from-transparent to-white to-60% dark:to-black tablet:!visible tablet:!block'></div> */}
|
||||
<Settings />
|
||||
</div>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
export default MainContent;
|
|
@ -145,3 +145,19 @@ export function isAdminUser(user: User) {
|
|||
export function isEditorUser(user: User) {
|
||||
return user.roles.some(role => role.name === 'Editor');
|
||||
}
|
||||
|
||||
export function isAuthorUser(user: User) {
|
||||
return user.roles.some(role => role.name === 'Author');
|
||||
}
|
||||
|
||||
export function isContributorUser(user: User) {
|
||||
return user.roles.some(role => role.name === 'Contributor');
|
||||
}
|
||||
|
||||
export function canAccessSettings(user: User) {
|
||||
return isOwnerUser(user) || isAdminUser(user) || isEditorUser(user);
|
||||
}
|
||||
|
||||
export function hasAdminAccess(user: User) {
|
||||
return isOwnerUser(user) || isAdminUser(user);
|
||||
}
|
||||
|
|
|
@ -5,24 +5,15 @@ import EmailSettings from './settings/email/EmailSettings';
|
|||
import GeneralSettings from './settings/general/GeneralSettings';
|
||||
import MembershipSettings from './settings/membership/MembershipSettings';
|
||||
import SiteSettings from './settings/site/SiteSettings';
|
||||
import Users from './settings/general/Users';
|
||||
import {isEditorUser} from '../api/users';
|
||||
import {useGlobalData} from './providers/GlobalDataProvider';
|
||||
|
||||
const Settings: React.FC = () => {
|
||||
const {currentUser} = useGlobalData();
|
||||
|
||||
return (
|
||||
<div className='mb-[40vh]'>
|
||||
{isEditorUser(currentUser) ?
|
||||
<Users keywords={[]} />
|
||||
: <>
|
||||
<GeneralSettings />
|
||||
<SiteSettings />
|
||||
<MembershipSettings />
|
||||
<EmailSettings />
|
||||
<AdvancedSettings />
|
||||
</>}
|
||||
<GeneralSettings />
|
||||
<SiteSettings />
|
||||
<MembershipSettings />
|
||||
<EmailSettings />
|
||||
<AdvancedSettings />
|
||||
<div className='mt-40 text-sm'>
|
||||
<a className='text-green' href="/ghost/#/settings">Click here</a> to open the original Admin settings.
|
||||
</div>
|
||||
|
|
|
@ -9,7 +9,6 @@ import {searchKeywords as advancedSearchKeywords} from './settings/advanced/Adva
|
|||
import {searchKeywords as emailSearchKeywords} from './settings/email/EmailSettings';
|
||||
import {searchKeywords as generalSearchKeywords} from './settings/general/GeneralSettings';
|
||||
import {getSettingValues} from '../api/settings';
|
||||
import {isEditorUser} from '../api/users';
|
||||
import {searchKeywords as membershipSearchKeywords} from './settings/membership/MembershipSettings';
|
||||
import {searchKeywords as siteSearchKeywords} from './settings/site/SiteSettings';
|
||||
import {useGlobalData} from './providers/GlobalDataProvider';
|
||||
|
@ -19,7 +18,7 @@ const Sidebar: React.FC = () => {
|
|||
const {filter, setFilter} = useSearch();
|
||||
const {updateRoute} = useRouting();
|
||||
|
||||
const {settings, config, currentUser} = useGlobalData();
|
||||
const {settings, config} = useGlobalData();
|
||||
const [newslettersEnabled] = getSettingValues(settings, ['editor_default_email_recipients']) as [string];
|
||||
|
||||
const handleSectionClick = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
|
@ -38,11 +37,6 @@ const Sidebar: React.FC = () => {
|
|||
}
|
||||
};
|
||||
|
||||
// Editors can only see staff settings, so no point in showing navigation
|
||||
if (isEditorUser(currentUser)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='no-scrollbar tablet:h-[calc(100vh-5vmin-84px)] tablet:w-[240px] tablet:overflow-y-scroll'>
|
||||
<div className='relative mb-10 md:pt-4 tablet:pt-[32px]'>
|
||||
|
|
|
@ -8,7 +8,7 @@ import Menu, {MenuItem} from '../../../admin-x-ds/global/Menu';
|
|||
import Modal from '../../../admin-x-ds/global/modal/Modal';
|
||||
import NiceModal, {useModal} from '@ebay/nice-modal-react';
|
||||
import Radio from '../../../admin-x-ds/global/form/Radio';
|
||||
import React, {useEffect, useRef, useState} from 'react';
|
||||
import React, {useCallback, useEffect, useRef, useState} from 'react';
|
||||
import SettingGroup from '../../../admin-x-ds/settings/SettingGroup';
|
||||
import SettingGroupContent from '../../../admin-x-ds/settings/SettingGroupContent';
|
||||
import TextArea from '../../../admin-x-ds/global/form/TextArea';
|
||||
|
@ -21,7 +21,7 @@ import useStaffUsers from '../../../hooks/useStaffUsers';
|
|||
import validator from 'validator';
|
||||
import {HostLimitError, useLimiter} from '../../../hooks/useLimiter';
|
||||
import {RoutingModalProps} from '../../providers/RoutingProvider';
|
||||
import {User, isAdminUser, isOwnerUser, useDeleteUser, useEditUser, useMakeOwner, useUpdatePassword} from '../../../api/users';
|
||||
import {User, canAccessSettings, hasAdminAccess, isAdminUser, isOwnerUser, useDeleteUser, useEditUser, useMakeOwner, useUpdatePassword} from '../../../api/users';
|
||||
import {genStaffToken, getStaffToken} from '../../../api/staffToken';
|
||||
import {getImageUrl, useUploadImage} from '../../../api/images';
|
||||
import {getSettingValues} from '../../../api/settings';
|
||||
|
@ -108,6 +108,8 @@ const RoleSelector: React.FC<UserDetailProps> = ({user, setUserData}) => {
|
|||
};
|
||||
|
||||
const BasicInputs: React.FC<UserDetailProps> = ({errors, validators, user, setUserData}) => {
|
||||
const {currentUser} = useGlobalData();
|
||||
|
||||
return (
|
||||
<SettingGroupContent>
|
||||
<TextField
|
||||
|
@ -134,7 +136,7 @@ const BasicInputs: React.FC<UserDetailProps> = ({errors, validators, user, setUs
|
|||
setUserData?.({...user, email: e.target.value});
|
||||
}}
|
||||
/>
|
||||
<RoleSelector setUserData={setUserData} user={user} />
|
||||
{hasAdminAccess(currentUser) && <RoleSelector setUserData={setUserData} user={user} />}
|
||||
</SettingGroupContent>
|
||||
);
|
||||
};
|
||||
|
@ -227,6 +229,7 @@ const Details: React.FC<UserDetailProps> = ({errors, validators, user, setUserDa
|
|||
|
||||
const EmailNotificationsInputs: React.FC<UserDetailProps> = ({user, setUserData}) => {
|
||||
const hasWebmentions = useFeatureFlag('webmentions');
|
||||
const {currentUser} = useGlobalData();
|
||||
|
||||
return (
|
||||
<SettingGroupContent>
|
||||
|
@ -239,51 +242,53 @@ const EmailNotificationsInputs: React.FC<UserDetailProps> = ({user, setUserData}
|
|||
setUserData?.({...user, comment_notifications: e.target.checked});
|
||||
}}
|
||||
/>
|
||||
{hasWebmentions && <Toggle
|
||||
checked={user.mention_notifications}
|
||||
direction='rtl'
|
||||
hint='Every time another site links to your work'
|
||||
label='Mentions'
|
||||
onChange={(e) => {
|
||||
setUserData?.({...user, mention_notifications: e.target.checked});
|
||||
}}
|
||||
/>}
|
||||
<Toggle
|
||||
checked={user.free_member_signup_notification}
|
||||
direction='rtl'
|
||||
hint='Every time a new free member signs up'
|
||||
label='New signups'
|
||||
onChange={(e) => {
|
||||
setUserData?.({...user, free_member_signup_notification: e.target.checked});
|
||||
}}
|
||||
/>
|
||||
<Toggle
|
||||
checked={user.paid_subscription_started_notification}
|
||||
direction='rtl'
|
||||
hint='Every time a member starts a new paid subscription'
|
||||
label='New paid members'
|
||||
onChange={(e) => {
|
||||
setUserData?.({...user, paid_subscription_started_notification: e.target.checked});
|
||||
}}
|
||||
/>
|
||||
<Toggle
|
||||
checked={user.paid_subscription_canceled_notification}
|
||||
direction='rtl'
|
||||
hint='Every time a member cancels their paid subscription'
|
||||
label='Paid member cancellations'
|
||||
onChange={(e) => {
|
||||
setUserData?.({...user, paid_subscription_canceled_notification: e.target.checked});
|
||||
}}
|
||||
/>
|
||||
<Toggle
|
||||
checked={user.milestone_notifications}
|
||||
direction='rtl'
|
||||
hint='Occasional summaries of your audience & revenue growth'
|
||||
label='Milestones'
|
||||
onChange={(e) => {
|
||||
setUserData?.({...user, milestone_notifications: e.target.checked});
|
||||
}}
|
||||
/>
|
||||
{hasAdminAccess(currentUser) && <>
|
||||
{hasWebmentions && <Toggle
|
||||
checked={user.mention_notifications}
|
||||
direction='rtl'
|
||||
hint='Every time another site links to your work'
|
||||
label='Mentions'
|
||||
onChange={(e) => {
|
||||
setUserData?.({...user, mention_notifications: e.target.checked});
|
||||
}}
|
||||
/>}
|
||||
<Toggle
|
||||
checked={user.free_member_signup_notification}
|
||||
direction='rtl'
|
||||
hint='Every time a new free member signs up'
|
||||
label='New signups'
|
||||
onChange={(e) => {
|
||||
setUserData?.({...user, free_member_signup_notification: e.target.checked});
|
||||
}}
|
||||
/>
|
||||
<Toggle
|
||||
checked={user.paid_subscription_started_notification}
|
||||
direction='rtl'
|
||||
hint='Every time a member starts a new paid subscription'
|
||||
label='New paid members'
|
||||
onChange={(e) => {
|
||||
setUserData?.({...user, paid_subscription_started_notification: e.target.checked});
|
||||
}}
|
||||
/>
|
||||
<Toggle
|
||||
checked={user.paid_subscription_canceled_notification}
|
||||
direction='rtl'
|
||||
hint='Every time a member cancels their paid subscription'
|
||||
label='Paid member cancellations'
|
||||
onChange={(e) => {
|
||||
setUserData?.({...user, paid_subscription_canceled_notification: e.target.checked});
|
||||
}}
|
||||
/>
|
||||
<Toggle
|
||||
checked={user.milestone_notifications}
|
||||
direction='rtl'
|
||||
hint='Occasional summaries of your audience & revenue growth'
|
||||
label='Milestones'
|
||||
onChange={(e) => {
|
||||
setUserData?.({...user, milestone_notifications: e.target.checked});
|
||||
}}
|
||||
/>
|
||||
</>}
|
||||
</SettingGroupContent>
|
||||
);
|
||||
};
|
||||
|
@ -439,7 +444,7 @@ const StaffToken: React.FC<UserDetailProps> = () => {
|
|||
const [token, setToken] = useState('');
|
||||
const {mutateAsync: newApiKey} = genStaffToken();
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
|
||||
const copyToClipboard = () => {
|
||||
navigator.clipboard.writeText(token);
|
||||
setCopied(true);
|
||||
|
@ -496,6 +501,7 @@ const UserMenuTrigger = () => (
|
|||
const UserDetailModalContent: React.FC<{user: User}> = ({user}) => {
|
||||
const {updateRoute} = useRouting();
|
||||
const {ownerUser} = useStaffUsers();
|
||||
const {currentUser} = useGlobalData();
|
||||
const [userData, _setUserData] = useState(user);
|
||||
const [saveState, setSaveState] = useState<'' | 'unsaved' | 'saving' | 'saved'>('');
|
||||
const [errors, setErrors] = useState<{
|
||||
|
@ -531,14 +537,22 @@ const UserDetailModalContent: React.FC<{user: User}> = ({user}) => {
|
|||
disabled: !pinturaEnabled}
|
||||
);
|
||||
|
||||
const navigateOnClose = useCallback(() => {
|
||||
if (canAccessSettings(currentUser)) {
|
||||
updateRoute('users');
|
||||
} else {
|
||||
updateRoute({isExternal: true, route: 'dashboard'});
|
||||
}
|
||||
}, [currentUser, updateRoute]);
|
||||
|
||||
useEffect(() => {
|
||||
if (saveState === 'saved') {
|
||||
setTimeout(() => {
|
||||
mainModal.remove();
|
||||
updateRoute('users');
|
||||
navigateOnClose();
|
||||
}, 300);
|
||||
}
|
||||
}, [mainModal, saveState, updateRoute]);
|
||||
}, [mainModal, navigateOnClose, saveState, updateRoute]);
|
||||
|
||||
const confirmSuspend = async (_user: User) => {
|
||||
if (_user.status === 'inactive' && _user.roles[0].name !== 'Contributor') {
|
||||
|
@ -741,10 +755,12 @@ const UserDetailModalContent: React.FC<{user: User}> = ({user}) => {
|
|||
|
||||
return (
|
||||
<Modal
|
||||
afterClose={() => updateRoute('users')}
|
||||
afterClose={navigateOnClose}
|
||||
animate={canAccessSettings(currentUser)}
|
||||
backDrop={canAccessSettings(currentUser)}
|
||||
dirty={saveState === 'unsaved'}
|
||||
okLabel={okLabel}
|
||||
size='lg'
|
||||
size={canAccessSettings(currentUser) ? 'lg' : 'full'}
|
||||
stickyFooter={true}
|
||||
testId='user-detail-modal'
|
||||
onOk={async () => {
|
||||
|
|
|
@ -6,12 +6,14 @@ import NoValueLabel from '../../../admin-x-ds/global/NoValueLabel';
|
|||
import React, {useState} from 'react';
|
||||
import SettingGroup from '../../../admin-x-ds/settings/SettingGroup';
|
||||
import TabView from '../../../admin-x-ds/global/TabView';
|
||||
import clsx from 'clsx';
|
||||
import useRouting from '../../../hooks/useRouting';
|
||||
import useStaffUsers from '../../../hooks/useStaffUsers';
|
||||
import {User} from '../../../api/users';
|
||||
import {User, hasAdminAccess, isContributorUser, isEditorUser} from '../../../api/users';
|
||||
import {UserInvite, useAddInvite, useDeleteInvite} from '../../../api/invites';
|
||||
import {generateAvatarColor, getInitials} from '../../../utils/helpers';
|
||||
import {showToast} from '../../../admin-x-ds/global/Toast';
|
||||
import {useGlobalData} from '../../providers/GlobalDataProvider';
|
||||
|
||||
interface OwnerProps {
|
||||
user: User;
|
||||
|
@ -31,9 +33,12 @@ interface InviteListProps {
|
|||
|
||||
const Owner: React.FC<OwnerProps> = ({user}) => {
|
||||
const {updateRoute} = useRouting();
|
||||
const {currentUser} = useGlobalData();
|
||||
|
||||
const showDetailModal = () => {
|
||||
updateRoute({route: `users/show/${user.slug}`});
|
||||
if (hasAdminAccess(currentUser)) {
|
||||
updateRoute({route: `users/show/${user.slug}`});
|
||||
}
|
||||
};
|
||||
|
||||
if (!user) {
|
||||
|
@ -41,10 +46,10 @@ const Owner: React.FC<OwnerProps> = ({user}) => {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className='group flex gap-3 hover:cursor-pointer' data-testid='owner-user' onClick={showDetailModal}>
|
||||
<div className={clsx('group flex gap-3', hasAdminAccess(currentUser) && 'cursor-pointer')} data-testid='owner-user' onClick={showDetailModal}>
|
||||
<Avatar bgColor={generateAvatarColor((user.name ? user.name : user.email))} image={user.profile_image} label={getInitials(user.name)} labelColor='white' size='lg' />
|
||||
<div className='flex flex-col'>
|
||||
<span>{user.name} — <strong>Owner</strong> <button className='ml-2 inline-block text-sm font-bold text-green group-hover:visible md:invisible' type='button'>View profile</button></span>
|
||||
<span>{user.name} — <strong>Owner</strong> {hasAdminAccess(currentUser) && <button className='ml-2 inline-block text-sm font-bold text-green group-hover:visible md:invisible' type='button'>View profile</button>}</span>
|
||||
<span className='text-xs text-grey-700'>{user.email}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -53,6 +58,7 @@ const Owner: React.FC<OwnerProps> = ({user}) => {
|
|||
|
||||
const UsersList: React.FC<UsersListProps> = ({users, groupname}) => {
|
||||
const {updateRoute} = useRouting();
|
||||
const {currentUser} = useGlobalData();
|
||||
|
||||
const showDetailModal = (user: User) => {
|
||||
updateRoute({route: `users/show/${user.slug}`});
|
||||
|
@ -73,11 +79,17 @@ const UsersList: React.FC<UsersListProps> = ({users, groupname}) => {
|
|||
if (user.status === 'inactive') {
|
||||
title = `${title} (Suspended)`;
|
||||
}
|
||||
|
||||
const canEdit = hasAdminAccess(currentUser) ||
|
||||
(isEditorUser(currentUser) && isContributorUser(user)) ||
|
||||
currentUser.id === user.id;
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
key={user.id}
|
||||
action={<Button color='green' label='Edit' link={true} onClick={() => showDetailModal(user)}/>}
|
||||
action={canEdit && <Button color='green' label='Edit' link={true} onClick={() => showDetailModal(user)}/>}
|
||||
avatar={(<Avatar bgColor={generateAvatarColor((user.name ? user.name : user.email))} image={user.profile_image} label={getInitials(user.name)} labelColor='white' />)}
|
||||
bgOnHover={canEdit}
|
||||
className='min-h-[64px]'
|
||||
detail={user.email}
|
||||
hideActions={true}
|
||||
|
@ -85,7 +97,7 @@ const UsersList: React.FC<UsersListProps> = ({users, groupname}) => {
|
|||
separator={false}
|
||||
testId='user-list-item'
|
||||
title={title}
|
||||
onClick={() => showDetailModal(user)} />
|
||||
onClick={() => canEdit && showDetailModal(user)} />
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
|
@ -189,7 +201,6 @@ const Users: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
|||
contributorUsers,
|
||||
invites
|
||||
} = useStaffUsers();
|
||||
|
||||
const {updateRoute} = useRouting();
|
||||
|
||||
const showInviteModal = () => {
|
||||
|
|
|
@ -588,3 +588,4 @@ remove|ember-template-lint|no-action|465|46|465|46|f2f0f3f512f141fdd821333c873f5
|
|||
remove|ember-template-lint|no-unused-block-params|1|0|1|0|e25f7866ab4ee682b08edf3b29a1351e4079538e|1688342400000|1698714000000|1703898000000|lib/koenig-editor/addon/components/koenig-card-header.hbs
|
||||
remove|ember-template-lint|no-invalid-interactive|7|32|7|32|508e64575a985432d0588f3291a126c4b62e68d8|1688342400000|1698714000000|1703898000000|app/components/gh-nav-menu/design.hbs
|
||||
add|ember-template-lint|no-invalid-interactive|7|32|7|32|2da5baf637c4f17f4acd498968b6022ffc0f3105|1692316800000|1702688400000|1707872400000|app/components/gh-nav-menu/design.hbs
|
||||
add|ember-template-lint|no-unknown-arguments-for-builtin-components|99|93|99|93|156670ca427c49c51f0a94f862b286ccc9466d92|1694649600000|1705021200000|1710205200000|app/components/gh-nav-menu/footer.hbs
|
||||
|
|
|
@ -55,9 +55,15 @@
|
|||
{{/if}}
|
||||
|
||||
<li>
|
||||
<LinkTo @route="settings.staff.user" @model={{this.session.user.slug}} class="dropdown-item" @role="menuitem" tabindex="-1" data-test-nav="user-profile">
|
||||
Your profile
|
||||
</LinkTo>
|
||||
{{#if (feature "adminXSettings")}}
|
||||
<LinkTo @route="settings-x.settings-x" @model="users/show/{{this.session.user.slug}}" class="dropdown-item" @role="menuitem" tabindex="-1" data-test-nav="user-profile">
|
||||
Your profile
|
||||
</LinkTo>
|
||||
{{else}}
|
||||
<LinkTo @route="settings.staff.user" @model={{this.session.user.slug}} class="dropdown-item" @role="menuitem" tabindex="-1" data-test-nav="user-profile">
|
||||
Your profile
|
||||
</LinkTo>
|
||||
{{/if}}
|
||||
</li>
|
||||
|
||||
{{#unless this.session.user.isContributor}}
|
||||
|
@ -99,16 +105,13 @@
|
|||
</GhBasicDropdown>
|
||||
</div>
|
||||
<div class="flex items-center pe-all">
|
||||
{{#if (gh-user-can-admin this.session.user)}}
|
||||
{{#if (or (gh-user-can-admin this.session.user) this.session.user.isEditor)}}
|
||||
{{#if (feature "adminXSettings")}}
|
||||
<LinkTo class="gh-nav-bottom-tabicon" @route="settings-x" @current-when={{this.isSettingsRoute}} data-test-nav="settings">{{svg-jar "settings"}}</LinkTo>
|
||||
{{else}}
|
||||
<LinkTo class="gh-nav-bottom-tabicon" @route="settings" @current-when={{this.isSettingsRoute}} data-test-nav="settings">{{svg-jar "settings"}}</LinkTo>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{#if this.session.user.isEditor}}
|
||||
<LinkTo class="gh-nav-bottom-tabicon" @route="settings.staff" @current-when={{this.isSettingsRoute}} data-test-nav="settings">{{svg-jar "settings"}}</LinkTo>
|
||||
{{/if}}
|
||||
<div class="nightshift-toggle-container">
|
||||
<div class="nightshift-toggle {{if this.feature.nightShift "on"}}" {{action (toggle "nightShift" this.feature)}}>
|
||||
<div class="sun">{{svg-jar "sun"}}</div>
|
||||
|
|
Loading…
Add table
Reference in a new issue