mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-04-08 02:52:39 -05:00
Updated wiring for user detail modal in admin-x
refs https://github.com/TryGhost/Team/issues/3151 - updates role info for user details modal for owner user, hides option to change role for owner - adds saving/saved state for user detail modal - removes alerts from invite and detail modal - adds wiring for remaining user detail modal settings
This commit is contained in:
parent
87df8754ee
commit
19b2112f8b
4 changed files with 137 additions and 60 deletions
|
@ -13,9 +13,19 @@ import useStaffUsers from '../../../hooks/useStaffUsers';
|
|||
import {User} from '../../../types/api';
|
||||
import {generateAvatarColor, getInitials} from '../../../utils/helpers';
|
||||
|
||||
const Owner: React.FC<{user: User}> = ({user}) => {
|
||||
interface OwnerProps {
|
||||
user: User;
|
||||
updateUser?: (user: User) => void;
|
||||
}
|
||||
|
||||
interface UsersListProps {
|
||||
users: User[];
|
||||
updateUser?: (user: User) => void;
|
||||
}
|
||||
|
||||
const Owner: React.FC<OwnerProps> = ({user, updateUser}) => {
|
||||
const showDetailModal = () => {
|
||||
NiceModal.show(UserDetailModal, {user});
|
||||
NiceModal.show(UserDetailModal, {user, updateUser});
|
||||
};
|
||||
|
||||
if (!user) {
|
||||
|
@ -33,11 +43,6 @@ const Owner: React.FC<{user: User}> = ({user}) => {
|
|||
);
|
||||
};
|
||||
|
||||
interface UsersListProps {
|
||||
users: User[];
|
||||
updateUser?: (user: User) => void;
|
||||
}
|
||||
|
||||
const UsersList: React.FC<UsersListProps> = ({users, updateUser}) => {
|
||||
const showDetailModal = (user: User) => {
|
||||
NiceModal.show(UserDetailModal, {user, updateUser});
|
||||
|
@ -123,7 +128,7 @@ const Users: React.FC = () => {
|
|||
customButtons={buttons}
|
||||
title='Users and permissions'
|
||||
>
|
||||
<Owner user={ownerUser} />
|
||||
<Owner updateUser={updateUser} user={ownerUser} />
|
||||
<TabView tabs={tabs} />
|
||||
</SettingGroup>
|
||||
);
|
||||
|
|
|
@ -3,11 +3,11 @@ import NiceModal from '@ebay/nice-modal-react';
|
|||
|
||||
const InviteUserModal = NiceModal.create(() => {
|
||||
return (
|
||||
<Modal
|
||||
<Modal
|
||||
size='md'
|
||||
title='Invite users'
|
||||
onOk={() => {
|
||||
alert('Clicked OK');
|
||||
// Handle invite user
|
||||
}}
|
||||
>
|
||||
<div className='py-4'>
|
||||
|
|
|
@ -4,14 +4,14 @@ import Heading from '../../../../admin-x-ds/global/Heading';
|
|||
import Modal from '../../../../admin-x-ds/global/Modal';
|
||||
import NiceModal from '@ebay/nice-modal-react';
|
||||
import Radio from '../../../../admin-x-ds/global/Radio';
|
||||
import React, {useState} from 'react';
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import SettingGroup from '../../../../admin-x-ds/settings/SettingGroup';
|
||||
import SettingGroupContent from '../../../../admin-x-ds/settings/SettingGroupContent';
|
||||
import TextField from '../../../../admin-x-ds/global/TextField';
|
||||
import Toggle from '../../../../admin-x-ds/global/Toggle';
|
||||
import useRoles from '../../../../hooks/useRoles';
|
||||
import {User} from '../../../../types/api';
|
||||
import {generateAvatarColor, getInitials} from '../../../../utils/helpers';
|
||||
import {generateAvatarColor, getInitials, isOwnerUser} from '../../../../utils/helpers';
|
||||
|
||||
interface CustomHeadingProps {
|
||||
children?: React.ReactNode;
|
||||
|
@ -27,8 +27,57 @@ const CustomHeader: React.FC<CustomHeadingProps> = ({children}) => {
|
|||
<Heading level={4} separator={true}>{children}</Heading>
|
||||
);
|
||||
};
|
||||
const BasicInputs: React.FC<UserDetailProps> = ({user, setUserData}) => {
|
||||
|
||||
const RoleSelector: React.FC<UserDetailProps> = ({user, setUserData}) => {
|
||||
const {roles} = useRoles();
|
||||
if (isOwnerUser(user)) {
|
||||
return (
|
||||
<>
|
||||
<Heading level={6}>Role</Heading>
|
||||
<div>
|
||||
This user is the owner of the site. To change their role, you need to transfer the ownership first.
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Radio
|
||||
defaultSelectedOption={user.roles[0].name.toLowerCase()}
|
||||
id='role'
|
||||
options={[
|
||||
{
|
||||
hint: 'Can create and edit their own posts, but cannot publish. An Editor needs to approve and publish for them.',
|
||||
label: 'Contributor',
|
||||
value: 'contributor'
|
||||
},
|
||||
{
|
||||
hint: 'A trusted user who can create, edit and publish their own posts, but can’t modify others.',
|
||||
label: 'Author',
|
||||
value: 'author'
|
||||
},
|
||||
{
|
||||
hint: 'Can invite and manage other Authors and Contributors, as well as edit and publish any posts on the site.',
|
||||
label: 'Editor',
|
||||
value: 'editor'
|
||||
},
|
||||
{
|
||||
hint: 'Trusted staff user who should be able to manage all content and users, as well as site settings and options.',
|
||||
label: 'Administrator',
|
||||
value: 'administrator'
|
||||
}
|
||||
]}
|
||||
title="Role"
|
||||
onSelect={(value) => {
|
||||
const role = roles?.find(r => r.name.toLowerCase() === value.toLowerCase());
|
||||
if (role) {
|
||||
setUserData?.({...user, roles: [role]});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
const BasicInputs: React.FC<UserDetailProps> = ({user, setUserData}) => {
|
||||
return (
|
||||
<SettingGroupContent>
|
||||
<TextField
|
||||
|
@ -46,39 +95,7 @@ const BasicInputs: React.FC<UserDetailProps> = ({user, setUserData}) => {
|
|||
setUserData?.({...user, email: e.target.value});
|
||||
}}
|
||||
/>
|
||||
<Radio
|
||||
defaultSelectedOption={user.roles[0].name.toLowerCase()}
|
||||
id='role'
|
||||
options={[
|
||||
{
|
||||
hint: 'Can create and edit their own posts, but cannot publish. An Editor needs to approve and publish for them.',
|
||||
label: 'Contributor',
|
||||
value: 'contributor'
|
||||
},
|
||||
{
|
||||
hint: 'A trusted user who can create, edit and publish their own posts, but can’t modify others.',
|
||||
label: 'Author',
|
||||
value: 'author'
|
||||
},
|
||||
{
|
||||
hint: 'Can invite and manage other Authors and Contributors, as well as edit and publish any posts on the site.',
|
||||
label: 'Editor',
|
||||
value: 'editor'
|
||||
},
|
||||
{
|
||||
hint: 'Trusted staff user who should be able to manage all content and users, as well as site settings and options.',
|
||||
label: 'Administrator',
|
||||
value: 'administrator'
|
||||
}
|
||||
]}
|
||||
title="Role"
|
||||
onSelect={(value) => {
|
||||
const role = roles?.find(r => r.name.toLowerCase() === value.toLowerCase());
|
||||
if (role) {
|
||||
setUserData?.({...user, roles: [role]});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<RoleSelector setUserData={setUserData} user={user} />
|
||||
</SettingGroupContent>
|
||||
);
|
||||
};
|
||||
|
@ -95,52 +112,70 @@ const Basic: React.FC<UserDetailProps> = ({user, setUserData}) => {
|
|||
);
|
||||
};
|
||||
|
||||
const DetailsInputs: React.FC<UserDetailProps> = ({user}) => {
|
||||
const DetailsInputs: React.FC<UserDetailProps> = ({user, setUserData}) => {
|
||||
return (
|
||||
<SettingGroupContent>
|
||||
<TextField
|
||||
hint="https://example.com/author"
|
||||
title="Slug"
|
||||
value={user.slug}
|
||||
onChange={(e) => {
|
||||
setUserData?.({...user, slug: e.target.value});
|
||||
}}
|
||||
/>
|
||||
<TextField
|
||||
title="Location"
|
||||
value={user.location}
|
||||
onChange={(e) => {
|
||||
setUserData?.({...user, location: e.target.value});
|
||||
}}
|
||||
/>
|
||||
<TextField
|
||||
title="Website"
|
||||
value={user.website}
|
||||
onChange={(e) => {
|
||||
setUserData?.({...user, website: e.target.value});
|
||||
}}
|
||||
/>
|
||||
<TextField
|
||||
title="Facebook profile"
|
||||
value={user.facebook}
|
||||
onChange={(e) => {
|
||||
setUserData?.({...user, facebook: e.target.value});
|
||||
}}
|
||||
/>
|
||||
<TextField
|
||||
title="Twitter profile"
|
||||
value={user.twitter}
|
||||
onChange={(e) => {
|
||||
setUserData?.({...user, twitter: e.target.value});
|
||||
}}
|
||||
/>
|
||||
<TextField
|
||||
hint="Recommended: 200 characters."
|
||||
title="Bio"
|
||||
value={user.bio}
|
||||
onChange={(e) => {
|
||||
setUserData?.({...user, bio: e.target.value});
|
||||
}}
|
||||
/>
|
||||
</SettingGroupContent>
|
||||
);
|
||||
};
|
||||
|
||||
const Details: React.FC<UserDetailProps> = ({user}) => {
|
||||
const Details: React.FC<UserDetailProps> = ({user, setUserData}) => {
|
||||
return (
|
||||
<SettingGroup
|
||||
border={false}
|
||||
customHeader={<CustomHeader>Details</CustomHeader>}
|
||||
title='Details'
|
||||
>
|
||||
<DetailsInputs user={user} />
|
||||
<DetailsInputs setUserData={setUserData} user={user} />
|
||||
</SettingGroup>
|
||||
);
|
||||
};
|
||||
|
||||
const EmailNotificationsInputs: React.FC<UserDetailProps> = ({user}) => {
|
||||
const EmailNotificationsInputs: React.FC<UserDetailProps> = ({user, setUserData}) => {
|
||||
return (
|
||||
<SettingGroupContent>
|
||||
<Toggle
|
||||
|
@ -149,6 +184,9 @@ const EmailNotificationsInputs: React.FC<UserDetailProps> = ({user}) => {
|
|||
hint='Every time a member comments on one of your posts'
|
||||
id='comments'
|
||||
label='Comments'
|
||||
onChange={(e) => {
|
||||
setUserData?.({...user, comment_notifications: e.target.checked});
|
||||
}}
|
||||
/>
|
||||
<Toggle
|
||||
checked={user.free_member_signup_notification}
|
||||
|
@ -156,6 +194,9 @@ const EmailNotificationsInputs: React.FC<UserDetailProps> = ({user}) => {
|
|||
hint='Every time a new free member signs up'
|
||||
id='new-signups'
|
||||
label='New signups'
|
||||
onChange={(e) => {
|
||||
setUserData?.({...user, free_member_signup_notification: e.target.checked});
|
||||
}}
|
||||
/>
|
||||
<Toggle
|
||||
checked={user.paid_subscription_started_notification}
|
||||
|
@ -163,6 +204,9 @@ const EmailNotificationsInputs: React.FC<UserDetailProps> = ({user}) => {
|
|||
hint='Every time a member starts a new paid subscription'
|
||||
id='new-paid-members'
|
||||
label='New paid members'
|
||||
onChange={(e) => {
|
||||
setUserData?.({...user, paid_subscription_started_notification: e.target.checked});
|
||||
}}
|
||||
/>
|
||||
<Toggle
|
||||
checked={user.paid_subscription_canceled_notification}
|
||||
|
@ -170,6 +214,9 @@ const EmailNotificationsInputs: React.FC<UserDetailProps> = ({user}) => {
|
|||
hint='Every time a member cancels their paid subscription'
|
||||
id='paid-member-cancellations'
|
||||
label='Paid member cancellations'
|
||||
onChange={(e) => {
|
||||
setUserData?.({...user, paid_subscription_canceled_notification: e.target.checked});
|
||||
}}
|
||||
/>
|
||||
<Toggle
|
||||
checked={user.milestone_notifications}
|
||||
|
@ -177,19 +224,23 @@ const EmailNotificationsInputs: React.FC<UserDetailProps> = ({user}) => {
|
|||
hint='Occasional summaries of your audience & revenue growth'
|
||||
id='milestones'
|
||||
label='Milestones'
|
||||
onChange={(e) => {
|
||||
setUserData?.({...user, milestone_notifications: e.target.checked});
|
||||
}}
|
||||
/>
|
||||
</SettingGroupContent>
|
||||
);
|
||||
};
|
||||
|
||||
const EmailNotifications: React.FC<UserDetailProps> = ({user}) => {
|
||||
const EmailNotifications: React.FC<UserDetailProps> = ({user, setUserData}) => {
|
||||
return (
|
||||
<SettingGroup
|
||||
border={false}
|
||||
customHeader={<CustomHeader>Email notifications</CustomHeader>}
|
||||
title='Email notifications'
|
||||
|
||||
>
|
||||
<EmailNotificationsInputs user={user} />
|
||||
<EmailNotificationsInputs setUserData={setUserData} user={user} />
|
||||
</SettingGroup>
|
||||
);
|
||||
};
|
||||
|
@ -242,14 +293,31 @@ interface UserDetailModalProps {
|
|||
|
||||
const UserDetailModal:React.FC<UserDetailModalProps> = ({user, updateUser}) => {
|
||||
const [userData, setUserData] = useState(user);
|
||||
const [saveState, setSaveState] = useState('');
|
||||
|
||||
let okLabel = saveState === 'saved' ? 'Saved' : 'Save';
|
||||
if (saveState === 'saving') {
|
||||
okLabel = 'Saving...';
|
||||
}
|
||||
|
||||
// remove saved state after 2 seconds
|
||||
useEffect(() => {
|
||||
if (saveState === 'saved') {
|
||||
setTimeout(() => {
|
||||
setSaveState('');
|
||||
}, 2000);
|
||||
}
|
||||
}, [saveState]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
okColor='green'
|
||||
okLabel='Save'
|
||||
okLabel={okLabel}
|
||||
size='lg'
|
||||
onOk={() => {
|
||||
alert('Clicked OK');
|
||||
updateUser?.(userData);
|
||||
onOk={async () => {
|
||||
setSaveState('saving');
|
||||
await updateUser?.(userData);
|
||||
setSaveState('saved');
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
|
@ -267,8 +335,8 @@ const UserDetailModal:React.FC<UserDetailModalProps> = ({user, updateUser}) => {
|
|||
</div>
|
||||
<div className='mt-10 grid grid-cols-2 gap-x-12 gap-y-20 pb-10'>
|
||||
<Basic setUserData={setUserData} user={userData} />
|
||||
<Details user={userData} />
|
||||
<EmailNotifications user={userData} />
|
||||
<Details setUserData={setUserData} user={userData} />
|
||||
<EmailNotifications setUserData={setUserData} user={userData} />
|
||||
<Password />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {Setting, SettingValue} from '../types/api';
|
||||
import {Setting, SettingValue, User} from '../types/api';
|
||||
|
||||
export interface IGhostPaths {
|
||||
adminRoot: string;
|
||||
|
@ -57,4 +57,8 @@ export function generateAvatarColor(name: string) {
|
|||
|
||||
const h = hash % 360;
|
||||
return 'hsl(' + h + ', ' + s + '%, ' + l + '%)';
|
||||
}
|
||||
}
|
||||
|
||||
export function isOwnerUser(user: User) {
|
||||
return user.roles.some(role => role.name === 'Owner');
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue