mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-28 14:03:48 +03:00
Update settings for roles in AdminX (#18147)
refs. https://github.com/TryGhost/Product/issues/3349 - updated user modal for various roles
This commit is contained in:
parent
32eb4635cf
commit
bcb543d039
@ -40,7 +40,7 @@ const MainContent: React.FC = () => {
|
|||||||
<Page>
|
<Page>
|
||||||
<div className='w-full'>
|
<div className='w-full'>
|
||||||
<Heading className='mb-10'>Settings</Heading>
|
<Heading className='mb-10'>Settings</Heading>
|
||||||
<Users keywords={[]} />
|
<Users highlight={false} keywords={[]} />
|
||||||
</div>
|
</div>
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
|
@ -57,7 +57,7 @@ const Heading: React.FC<Heading1to5Props | Heading6Props | HeadingLabelProps> =
|
|||||||
if (!useLabelTag) {
|
if (!useLabelTag) {
|
||||||
switch (level) {
|
switch (level) {
|
||||||
case 1:
|
case 1:
|
||||||
styles += ' md:text-5xl';
|
styles += ' md:text-5xl leading-tight';
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
styles += ' md:text-3xl';
|
styles += ' md:text-3xl';
|
||||||
|
@ -22,6 +22,7 @@ interface ImageUploadProps {
|
|||||||
editButtonClassName?: string;
|
editButtonClassName?: string;
|
||||||
editButtonContent?: React.ReactNode;
|
editButtonContent?: React.ReactNode;
|
||||||
editButtonUnstyled?: boolean;
|
editButtonUnstyled?: boolean;
|
||||||
|
buttonContainerClassName?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes all the classnames from all elements so you can set a completely custom styling
|
* Removes all the classnames from all elements so you can set a completely custom styling
|
||||||
@ -61,7 +62,8 @@ const ImageUpload: React.FC<ImageUploadProps> = ({
|
|||||||
pintura,
|
pintura,
|
||||||
editButtonClassName,
|
editButtonClassName,
|
||||||
editButtonContent,
|
editButtonContent,
|
||||||
editButtonUnstyled = false
|
editButtonUnstyled = false,
|
||||||
|
buttonContainerClassName
|
||||||
}) => {
|
}) => {
|
||||||
if (!unstyled) {
|
if (!unstyled) {
|
||||||
imageContainerClassName = clsx(
|
imageContainerClassName = clsx(
|
||||||
@ -112,15 +114,17 @@ const ImageUpload: React.FC<ImageUploadProps> = ({
|
|||||||
width: (unstyled ? '' : width || '100%'),
|
width: (unstyled ? '' : width || '100%'),
|
||||||
height: (unstyled ? '' : height || 'auto')
|
height: (unstyled ? '' : height || 'auto')
|
||||||
}} onClick={onImageClick} />
|
}} onClick={onImageClick} />
|
||||||
{
|
<div className={buttonContainerClassName}>
|
||||||
pintura?.isEnabled && pintura?.openEditor &&
|
{
|
||||||
|
pintura?.isEnabled && pintura?.openEditor &&
|
||||||
<button className={editButtonClassName} type='button' onClick={pintura.openEditor}>
|
<button className={editButtonClassName} type='button' onClick={pintura.openEditor}>
|
||||||
{editButtonContent}
|
{editButtonContent}
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
<button className={deleteButtonClassName} type='button' onClick={onDelete}>
|
<button className={deleteButtonClassName} type='button' onClick={onDelete}>
|
||||||
{deleteButtonContent}
|
{deleteButtonContent}
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -146,14 +150,16 @@ const ImageUpload: React.FC<ImageUploadProps> = ({
|
|||||||
return image;
|
return image;
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<FileUpload className={fileUploadClassName} id={id} style={
|
<div className={buttonContainerClassName}>
|
||||||
{
|
<FileUpload className={fileUploadClassName} id={id} style={
|
||||||
width: (unstyled ? '' : width),
|
{
|
||||||
height: (unstyled ? '' : height)
|
width: (unstyled ? '' : width),
|
||||||
}
|
height: (unstyled ? '' : height)
|
||||||
} unstyled={unstyled} onUpload={onUpload}>
|
}
|
||||||
<span className='text-center'>{children}</span>
|
} unstyled={unstyled} onUpload={onUpload}>
|
||||||
</FileUpload>
|
<span className='text-center'>{children}</span>
|
||||||
|
</FileUpload>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -146,7 +146,8 @@ const Modal: React.FC<ModalProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
let modalClasses = clsx(
|
let modalClasses = clsx(
|
||||||
'relative z-50 mx-auto flex max-h-[100%] w-full flex-col justify-between overflow-x-hidden rounded bg-white dark:bg-black',
|
'relative z-50 mx-auto flex max-h-[100%] w-full flex-col justify-between overflow-x-hidden bg-white dark:bg-black',
|
||||||
|
size !== 'bleed' && 'rounded',
|
||||||
formSheet ? 'shadow-md' : 'shadow-xl',
|
formSheet ? 'shadow-md' : 'shadow-xl',
|
||||||
(animate && !formSheet && !animationFinished) && 'animate-modal-in',
|
(animate && !formSheet && !animationFinished) && 'animate-modal-in',
|
||||||
(formSheet && !animationFinished) && 'animate-modal-in-reverse',
|
(formSheet && !animationFinished) && 'animate-modal-in-reverse',
|
||||||
|
@ -22,6 +22,11 @@ interface SettingGroupProps {
|
|||||||
hideEditButton?: boolean;
|
hideEditButton?: boolean;
|
||||||
alwaysShowSaveButton?: boolean;
|
alwaysShowSaveButton?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show a green outline in case the modal that's been triggered from the group is closed
|
||||||
|
*/
|
||||||
|
highlightOnModalClose?: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove borders and paddings
|
* Remove borders and paddings
|
||||||
*/
|
*/
|
||||||
@ -50,6 +55,7 @@ const SettingGroup: React.FC<SettingGroupProps> = ({
|
|||||||
hideEditButton,
|
hideEditButton,
|
||||||
alwaysShowSaveButton = true,
|
alwaysShowSaveButton = true,
|
||||||
border = true,
|
border = true,
|
||||||
|
highlightOnModalClose = true,
|
||||||
styles,
|
styles,
|
||||||
onEditingChange,
|
onEditingChange,
|
||||||
onSave,
|
onSave,
|
||||||
@ -146,7 +152,7 @@ const SettingGroup: React.FC<SettingGroupProps> = ({
|
|||||||
'relative flex-col gap-6 rounded',
|
'relative flex-col gap-6 rounded',
|
||||||
border && 'border p-5 md:p-7',
|
border && 'border p-5 md:p-7',
|
||||||
!checkVisible(keywords) ? 'hidden' : 'flex',
|
!checkVisible(keywords) ? 'hidden' : 'flex',
|
||||||
highlight && 'before:pointer-events-none before:absolute before:inset-[1px] before:animate-setting-highlight-fade-out before:rounded before:shadow-[0_0_0_3px_rgba(48,207,67,0.45)]',
|
(highlight && highlightOnModalClose) && 'before:pointer-events-none before:absolute before:inset-[1px] before:animate-setting-highlight-fade-out before:rounded before:shadow-[0_0_0_3px_rgba(48,207,67,0.45)]',
|
||||||
!isEditing && 'is-not-editing group/setting-group',
|
!isEditing && 'is-not-editing group/setting-group',
|
||||||
styles
|
styles
|
||||||
);
|
);
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import Button from '../../../../admin-x-ds/global/Button';
|
import Button from '../../../../admin-x-ds/global/Button';
|
||||||
import React, {ReactNode, useState} from 'react';
|
import React, {ReactNode, useState} from 'react';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
|
||||||
export interface APIKeyFieldProps {
|
export interface APIKeyFieldProps {
|
||||||
label: string;
|
label?: string;
|
||||||
text?: string;
|
text?: string;
|
||||||
hint?: ReactNode;
|
hint?: ReactNode;
|
||||||
onRegenerate?: () => void;
|
onRegenerate?: () => void;
|
||||||
@ -17,9 +18,14 @@ const APIKeyField: React.FC<APIKeyFieldProps> = ({label, text = '', hint, onRege
|
|||||||
setTimeout(() => setCopied(false), 2000);
|
setTimeout(() => setCopied(false), 2000);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const containerClasses = clsx(
|
||||||
|
'group/api-keys relative mb-3 w-full overflow-hidden rounded py-1 text-sm hover:bg-grey-50 dark:hover:bg-black md:mb-0',
|
||||||
|
label ? 'md:p-1' : 'md:px-0'
|
||||||
|
);
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
<div className='p-0 pr-4 text-sm text-grey-600 md:py-1'>{label}</div>
|
{label && <div className='p-0 pr-4 text-sm text-grey-600 md:py-1'>{label}</div>}
|
||||||
<div className='group/api-keys relative mb-3 overflow-hidden rounded py-1 text-sm hover:bg-grey-50 dark:hover:bg-grey-900 md:mb-0 md:p-1'>
|
<div className={containerClasses}>
|
||||||
{text}
|
{text}
|
||||||
{hint}
|
{hint}
|
||||||
<div className='visible absolute right-0 top-[50%] flex translate-y-[-50%] gap-1 bg-white pl-1 text-sm group-hover/api-keys:visible dark:bg-black md:invisible'>
|
<div className='visible absolute right-0 top-[50%] flex translate-y-[-50%] gap-1 bg-white pl-1 text-sm group-hover/api-keys:visible dark:bg-black md:invisible'>
|
||||||
@ -30,9 +36,9 @@ const APIKeyField: React.FC<APIKeyFieldProps> = ({label, text = '', hint, onRege
|
|||||||
</>;
|
</>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const APIKeys: React.FC<{keys: APIKeyFieldProps[]}> = ({keys}) => {
|
const APIKeys: React.FC<{hasLabel?: boolean; keys: APIKeyFieldProps[];}> = ({hasLabel = true, keys}) => {
|
||||||
return (
|
return (
|
||||||
<div className='grid grid-cols-1 md:grid-cols-[max-content_1fr]'>
|
<div className={hasLabel ? 'grid grid-cols-1 md:grid-cols-[max-content_1fr]' : ''}>
|
||||||
{keys.map(key => <APIKeyField key={key.label} {...key} />)}
|
{keys.map(key => <APIKeyField key={key.label} {...key} />)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -0,0 +1,51 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import SettingGroupContent from '../../../admin-x-ds/settings/SettingGroupContent';
|
||||||
|
import TextArea from '../../../admin-x-ds/global/form/TextArea';
|
||||||
|
import TextField from '../../../admin-x-ds/global/form/TextField';
|
||||||
|
import {UserDetailProps} from './UserDetailModal';
|
||||||
|
|
||||||
|
export const DetailsInputs: React.FC<UserDetailProps> = ({errors, validators, user, setUserData}) => {
|
||||||
|
return (
|
||||||
|
<SettingGroupContent>
|
||||||
|
<TextField
|
||||||
|
hint="Where in the world do you live?"
|
||||||
|
title="Location"
|
||||||
|
value={user.location}
|
||||||
|
onChange={(e) => {
|
||||||
|
setUserData?.({...user, location: e.target.value});
|
||||||
|
}} />
|
||||||
|
<TextField
|
||||||
|
error={!!errors?.url}
|
||||||
|
hint={errors?.url || 'Have a website or blog other than this one? Link it!'}
|
||||||
|
title="Website"
|
||||||
|
value={user.website}
|
||||||
|
onBlur={(e) => {
|
||||||
|
validators?.url(e.target.value);
|
||||||
|
}}
|
||||||
|
onChange={(e) => {
|
||||||
|
setUserData?.({...user, website: e.target.value});
|
||||||
|
}} />
|
||||||
|
<TextField
|
||||||
|
hint='URL of your personal Facebook Profile'
|
||||||
|
title="Facebook profile"
|
||||||
|
value={user.facebook}
|
||||||
|
onChange={(e) => {
|
||||||
|
setUserData?.({...user, facebook: e.target.value});
|
||||||
|
}} />
|
||||||
|
<TextField
|
||||||
|
hint='URL of your personal Twitter profile'
|
||||||
|
title="Twitter profile"
|
||||||
|
value={user.twitter}
|
||||||
|
onChange={(e) => {
|
||||||
|
setUserData?.({...user, twitter: e.target.value});
|
||||||
|
}} />
|
||||||
|
<TextArea
|
||||||
|
hint={<>Recommended: 200 characters. You‘ve used <span className='font-bold'>{user.bio?.length || 0}</span></>}
|
||||||
|
title="Bio"
|
||||||
|
value={user.bio || ''}
|
||||||
|
onChange={(e) => {
|
||||||
|
setUserData?.({...user, bio: e.target.value});
|
||||||
|
}} />
|
||||||
|
</SettingGroupContent>
|
||||||
|
);
|
||||||
|
};
|
@ -1,3 +1,4 @@
|
|||||||
|
import APIKeys from '../advanced/integrations/APIKeys';
|
||||||
import Button from '../../../admin-x-ds/global/Button';
|
import Button from '../../../admin-x-ds/global/Button';
|
||||||
import ConfirmationModal from '../../../admin-x-ds/global/modal/ConfirmationModal';
|
import ConfirmationModal from '../../../admin-x-ds/global/modal/ConfirmationModal';
|
||||||
import Heading from '../../../admin-x-ds/global/Heading';
|
import Heading from '../../../admin-x-ds/global/Heading';
|
||||||
@ -11,14 +12,15 @@ import Radio from '../../../admin-x-ds/global/form/Radio';
|
|||||||
import React, {useCallback, useEffect, useRef, useState} from 'react';
|
import React, {useCallback, useEffect, useRef, useState} from 'react';
|
||||||
import SettingGroup from '../../../admin-x-ds/settings/SettingGroup';
|
import SettingGroup from '../../../admin-x-ds/settings/SettingGroup';
|
||||||
import SettingGroupContent from '../../../admin-x-ds/settings/SettingGroupContent';
|
import SettingGroupContent from '../../../admin-x-ds/settings/SettingGroupContent';
|
||||||
import TextArea from '../../../admin-x-ds/global/form/TextArea';
|
|
||||||
import TextField from '../../../admin-x-ds/global/form/TextField';
|
import TextField from '../../../admin-x-ds/global/form/TextField';
|
||||||
import Toggle from '../../../admin-x-ds/global/form/Toggle';
|
import Toggle from '../../../admin-x-ds/global/form/Toggle';
|
||||||
|
import clsx from 'clsx';
|
||||||
import useFeatureFlag from '../../../hooks/useFeatureFlag';
|
import useFeatureFlag from '../../../hooks/useFeatureFlag';
|
||||||
import usePinturaEditor from '../../../hooks/usePinturaEditor';
|
import usePinturaEditor from '../../../hooks/usePinturaEditor';
|
||||||
import useRouting from '../../../hooks/useRouting';
|
import useRouting from '../../../hooks/useRouting';
|
||||||
import useStaffUsers from '../../../hooks/useStaffUsers';
|
import useStaffUsers from '../../../hooks/useStaffUsers';
|
||||||
import validator from 'validator';
|
import validator from 'validator';
|
||||||
|
import {DetailsInputs} from './DetailsInputs';
|
||||||
import {HostLimitError, useLimiter} from '../../../hooks/useLimiter';
|
import {HostLimitError, useLimiter} from '../../../hooks/useLimiter';
|
||||||
import {RoutingModalProps} from '../../providers/RoutingProvider';
|
import {RoutingModalProps} from '../../providers/RoutingProvider';
|
||||||
import {User, canAccessSettings, hasAdminAccess, isAdminUser, isOwnerUser, useDeleteUser, useEditUser, useMakeOwner, useUpdatePassword} from '../../../api/users';
|
import {User, canAccessSettings, hasAdminAccess, isAdminUser, isOwnerUser, useDeleteUser, useEditUser, useMakeOwner, useUpdatePassword} from '../../../api/users';
|
||||||
@ -34,7 +36,7 @@ interface CustomHeadingProps {
|
|||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UserDetailProps {
|
export interface UserDetailProps {
|
||||||
user: User;
|
user: User;
|
||||||
setUserData?: (user: User) => void;
|
setUserData?: (user: User) => void;
|
||||||
errors?: {
|
errors?: {
|
||||||
@ -136,6 +138,14 @@ const BasicInputs: React.FC<UserDetailProps> = ({errors, validators, user, setUs
|
|||||||
setUserData?.({...user, email: e.target.value});
|
setUserData?.({...user, email: e.target.value});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<TextField
|
||||||
|
hint="https://example.com/author"
|
||||||
|
title="Slug"
|
||||||
|
value={user.slug}
|
||||||
|
onChange={(e) => {
|
||||||
|
setUserData?.({...user, slug: e.target.value});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
{hasAdminAccess(currentUser) && <RoleSelector setUserData={setUserData} user={user} />}
|
{hasAdminAccess(currentUser) && <RoleSelector setUserData={setUserData} user={user} />}
|
||||||
</SettingGroupContent>
|
</SettingGroupContent>
|
||||||
);
|
);
|
||||||
@ -153,68 +163,6 @@ const Basic: React.FC<UserDetailProps> = ({errors, validators, user, setUserData
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const DetailsInputs: React.FC<UserDetailProps> = ({errors, validators, user, setUserData}) => {
|
|
||||||
return (
|
|
||||||
<SettingGroupContent>
|
|
||||||
<TextField
|
|
||||||
hint="https://example.com/author"
|
|
||||||
title="Slug"
|
|
||||||
value={user.slug}
|
|
||||||
onChange={(e) => {
|
|
||||||
setUserData?.({...user, slug: e.target.value});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
hint="Where in the world do you live?"
|
|
||||||
title="Location"
|
|
||||||
value={user.location}
|
|
||||||
onChange={(e) => {
|
|
||||||
setUserData?.({...user, location: e.target.value});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
error={!!errors?.url}
|
|
||||||
hint={errors?.url || 'Have a website or blog other than this one? Link it!'}
|
|
||||||
placeholder='https://example.com'
|
|
||||||
title="Website"
|
|
||||||
value={user.website}
|
|
||||||
onBlur={(e) => {
|
|
||||||
validators?.url(e.target.value);
|
|
||||||
}}
|
|
||||||
onChange={(e) => {
|
|
||||||
setUserData?.({...user, website: e.target.value});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
hint='URL of your personal Facebook Profile'
|
|
||||||
placeholder='https://www.facebook.com/ghost'
|
|
||||||
title="Facebook profile"
|
|
||||||
value={user.facebook}
|
|
||||||
onChange={(e) => {
|
|
||||||
setUserData?.({...user, facebook: e.target.value});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
hint='URL of your personal Twitter profile'
|
|
||||||
placeholder='https://twitter.com/ghost'
|
|
||||||
title="Twitter profile"
|
|
||||||
value={user.twitter}
|
|
||||||
onChange={(e) => {
|
|
||||||
setUserData?.({...user, twitter: e.target.value});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextArea
|
|
||||||
hint={<>Recommended: 200 characters. You‘ve used <span className='font-bold'>{user.bio?.length || 0}</span></>}
|
|
||||||
title="Bio"
|
|
||||||
value={user.bio || ''}
|
|
||||||
onChange={(e) => {
|
|
||||||
setUserData?.({...user, bio: e.target.value});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</SettingGroupContent>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const Details: React.FC<UserDetailProps> = ({errors, validators, user, setUserData}) => {
|
const Details: React.FC<UserDetailProps> = ({errors, validators, user, setUserData}) => {
|
||||||
return (
|
return (
|
||||||
<SettingGroup
|
<SettingGroup
|
||||||
@ -445,15 +393,6 @@ const StaffToken: React.FC<UserDetailProps> = () => {
|
|||||||
});
|
});
|
||||||
const [token, setToken] = useState('');
|
const [token, setToken] = useState('');
|
||||||
const {mutateAsync: newApiKey} = genStaffToken();
|
const {mutateAsync: newApiKey} = genStaffToken();
|
||||||
const [copied, setCopied] = useState(false);
|
|
||||||
|
|
||||||
const copyToClipboard = () => {
|
|
||||||
navigator.clipboard.writeText(token);
|
|
||||||
setCopied(true);
|
|
||||||
setTimeout(() => {
|
|
||||||
setCopied(false);
|
|
||||||
}, 2000);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const getApiKey = async () => {
|
const getApiKey = async () => {
|
||||||
@ -479,24 +418,14 @@ const StaffToken: React.FC<UserDetailProps> = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<SettingGroup
|
<div>
|
||||||
border={false}
|
<Heading className='mb-2' level={6} grey>Staff access token</Heading>
|
||||||
customHeader={<CustomHeader>Staff access token</CustomHeader>}
|
<APIKeys hasLabel={false} keys={[
|
||||||
title='Staff access token'
|
{
|
||||||
>
|
text: token || '',
|
||||||
<TextField
|
onRegenerate: genConfirmation
|
||||||
readOnly={true}
|
}]} />
|
||||||
rightPlaceholder={
|
</div>
|
||||||
<div className='flex'>
|
|
||||||
<Button className='mt-2' color='white' label='Regenerate' size='sm' onClick={genConfirmation} />
|
|
||||||
<Button className='mt-2' color={copied ? 'green' : 'white'} label={copied ? 'Copied' : 'Copy'} size='sm' onClick={copyToClipboard} />
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
title="Staff access token"
|
|
||||||
type="text"
|
|
||||||
value={token || ''}
|
|
||||||
/>
|
|
||||||
</SettingGroup>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -731,11 +660,27 @@ const UserDetailModalContent: React.FC<{user: User}> = ({user}) => {
|
|||||||
okLabel = 'Saved';
|
okLabel = 'Saved';
|
||||||
}
|
}
|
||||||
|
|
||||||
const fileUploadButtonClasses = 'absolute left-12 md:left-auto md:right-[104px] bottom-12 bg-[rgba(0,0,0,0.75)] rounded text-sm text-white flex items-center justify-center px-3 h-8 opacity-80 hover:opacity-100 transition cursor-pointer font-medium z-10';
|
const coverButtonContainerClassName = clsx(
|
||||||
|
canAccessSettings(currentUser) ? (
|
||||||
|
userData.cover_image ? 'relative ml-10 mr-[106px] flex translate-y-[-80px] gap-3 md:ml-0 md:justify-end' : 'relative -mb-8 ml-10 mr-[106px] flex translate-y-[358px] md:ml-0 md:translate-y-[268px] md:justify-end'
|
||||||
|
) : (
|
||||||
|
userData.cover_image ? 'relative ml-10 flex max-w-4xl translate-y-[-80px] gap-3 md:mx-auto md:justify-end' : 'relative -mb-8 ml-10 flex max-w-4xl translate-y-[358px] md:mx-auto md:translate-y-[268px] md:justify-end'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
const deleteButtonClasses = 'absolute left-12 md:left-auto md:right-[152px] bottom-12 bg-[rgba(0,0,0,0.75)] rounded text-sm text-white flex items-center justify-center px-3 h-8 opacity-80 hover:opacity-100 transition cursor-pointer font-medium z-10';
|
const coverEditButtonBaseClasses = 'bg-[rgba(0,0,0,0.75)] rounded text-sm text-white flex items-center justify-center px-3 h-8 opacity-80 hover:opacity-100 transition-all cursor-pointer font-medium';
|
||||||
|
|
||||||
const editButtonClasses = 'absolute left-12 md:left-auto md:right-[102px] bottom-12 bg-[rgba(0,0,0,0.75)] rounded text-sm text-white flex items-center justify-center px-3 h-8 opacity-80 hover:opacity-100 transition cursor-pointer font-medium z-10';
|
const fileUploadButtonClasses = clsx(
|
||||||
|
coverEditButtonBaseClasses
|
||||||
|
);
|
||||||
|
|
||||||
|
const deleteButtonClasses = clsx(
|
||||||
|
coverEditButtonBaseClasses
|
||||||
|
);
|
||||||
|
|
||||||
|
const editButtonClasses = clsx(
|
||||||
|
coverEditButtonBaseClasses
|
||||||
|
);
|
||||||
|
|
||||||
const suspendedText = userData.status === 'inactive' ? ' (Suspended)' : '';
|
const suspendedText = userData.status === 'inactive' ? ' (Suspended)' : '';
|
||||||
|
|
||||||
@ -769,7 +714,7 @@ const UserDetailModalContent: React.FC<{user: User}> = ({user}) => {
|
|||||||
backDrop={canAccessSettings(currentUser)}
|
backDrop={canAccessSettings(currentUser)}
|
||||||
dirty={saveState === 'unsaved'}
|
dirty={saveState === 'unsaved'}
|
||||||
okLabel={okLabel}
|
okLabel={okLabel}
|
||||||
size={canAccessSettings(currentUser) ? 'lg' : 'full'}
|
size={canAccessSettings(currentUser) ? 'lg' : 'bleed'}
|
||||||
stickyFooter={true}
|
stickyFooter={true}
|
||||||
testId='user-detail-modal'
|
testId='user-detail-modal'
|
||||||
onOk={async () => {
|
onOk={async () => {
|
||||||
@ -795,8 +740,9 @@ const UserDetailModalContent: React.FC<{user: User}> = ({user}) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<div className={`relative -mx-12 -mt-12 rounded-t bg-gradient-to-tr from-grey-900 to-black`}>
|
<div className={`relative -mx-10 -mt-10 ${canAccessSettings(currentUser) && 'rounded-t'} bg-gradient-to-tr from-grey-900 to-black`}>
|
||||||
<ImageUpload
|
<ImageUpload
|
||||||
|
buttonContainerClassName={coverButtonContainerClassName}
|
||||||
deleteButtonClassName={deleteButtonClasses}
|
deleteButtonClassName={deleteButtonClasses}
|
||||||
deleteButtonContent='Delete cover image'
|
deleteButtonContent='Delete cover image'
|
||||||
editButtonClassName={editButtonClasses}
|
editButtonClassName={editButtonClasses}
|
||||||
@ -804,7 +750,7 @@ const UserDetailModalContent: React.FC<{user: User}> = ({user}) => {
|
|||||||
height={userData.cover_image ? '100%' : '32px'}
|
height={userData.cover_image ? '100%' : '32px'}
|
||||||
id='cover-image'
|
id='cover-image'
|
||||||
imageClassName='w-full h-full object-cover'
|
imageClassName='w-full h-full object-cover'
|
||||||
imageContainerClassName='absolute inset-0 bg-cover group bg-center rounded-t overflow-hidden'
|
imageContainerClassName={`absolute inset-0 bg-cover group bg-center ${canAccessSettings(currentUser) && 'rounded-t'} overflow-hidden`}
|
||||||
imageURL={userData.cover_image || ''}
|
imageURL={userData.cover_image || ''}
|
||||||
pintura={
|
pintura={
|
||||||
{
|
{
|
||||||
@ -825,18 +771,18 @@ const UserDetailModalContent: React.FC<{user: User}> = ({user}) => {
|
|||||||
handleImageUpload('cover_image', file);
|
handleImageUpload('cover_image', file);
|
||||||
}}
|
}}
|
||||||
>Upload cover image</ImageUpload>
|
>Upload cover image</ImageUpload>
|
||||||
<div className="absolute bottom-12 right-12 z-10">
|
{canAccessSettings(currentUser) && <div className="absolute bottom-12 right-12 z-10">
|
||||||
<Menu items={menuItems} position='right' trigger={<UserMenuTrigger />}></Menu>
|
<Menu items={menuItems} position='right' trigger={<UserMenuTrigger />}></Menu>
|
||||||
</div>
|
</div>}
|
||||||
<div className='relative flex flex-col items-start gap-4 px-12 pb-60 pt-10 md:flex-row md:items-center md:pb-7 md:pt-60'>
|
<div className={`${!canAccessSettings(currentUser) ? 'mx-10 pl-0 md:max-w-[50%] min-[920px]:ml-[calc((100vw-920px)/2)] min-[920px]:max-w-[460px]' : 'max-w-[50%] pl-12'} relative flex flex-col items-start gap-4 pb-60 pt-10 md:flex-row md:items-center md:pb-7 md:pt-60`}>
|
||||||
<ImageUpload
|
<ImageUpload
|
||||||
deleteButtonClassName='md:invisible absolute pr-3 -right-2 -top-2 flex h-8 w-16 cursor-pointer items-center justify-end rounded-full bg-[rgba(0,0,0,0.75)] text-white group-hover:!visible'
|
deleteButtonClassName='md:invisible absolute pr-3 -right-2 -top-2 flex h-8 w-16 cursor-pointer items-center justify-end rounded-full bg-[rgba(0,0,0,0.75)] text-white group-hover:!visible'
|
||||||
deleteButtonContent={<Icon colorClass='text-white' name='trash' size='sm' />}
|
deleteButtonContent={<Icon colorClass='text-white' name='trash' size='sm' />}
|
||||||
editButtonClassName='md:invisible absolute right-[22px] -top-2 flex h-8 w-8 cursor-pointer items-center justify-center text-white group-hover:!visible z-20'
|
editButtonClassName='md:invisible absolute right-[22px] -top-2 flex h-8 w-8 cursor-pointer items-center justify-center text-white group-hover:!visible z-20'
|
||||||
fileUploadClassName='rounded-full bg-black flex items-center justify-center opacity-80 transition hover:opacity-100 -ml-2 cursor-pointer h-[80px] w-[80px]'
|
fileUploadClassName='rounded-full bg-black flex items-center justify-center opacity-80 transition hover:opacity-100 -ml-2 cursor-pointer h-[80px] w-[80px]'
|
||||||
id='avatar'
|
id='avatar'
|
||||||
imageClassName='w-full h-full object-cover rounded-full'
|
imageClassName='w-full h-full object-cover rounded-full shrink-0'
|
||||||
imageContainerClassName='relative group bg-cover bg-center -ml-2 h-[80px] w-[80px]'
|
imageContainerClassName='relative group bg-cover bg-center -ml-2 h-[80px] w-[80px] shrink-0'
|
||||||
imageURL={userData.profile_image}
|
imageURL={userData.profile_image}
|
||||||
pintura={
|
pintura={
|
||||||
{
|
{
|
||||||
@ -866,12 +812,14 @@ const UserDetailModalContent: React.FC<{user: User}> = ({user}) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='mt-10 grid grid-cols-1 gap-x-12 gap-y-20 md:grid-cols-2'>
|
<div className={`${!canAccessSettings(currentUser) && 'mx-auto max-w-4xl'} mt-10 grid grid-cols-1 gap-x-12 gap-y-20 md:grid-cols-2`}>
|
||||||
<Basic errors={errors} setUserData={setUserData} user={userData} validators={validators} />
|
<Basic errors={errors} setUserData={setUserData} user={userData} validators={validators} />
|
||||||
<Details errors={errors} setUserData={setUserData} user={userData} validators={validators} />
|
<div className='flex flex-col justify-between gap-10'>
|
||||||
|
<Details errors={errors} setUserData={setUserData} user={userData} validators={validators} />
|
||||||
|
<StaffToken user={userData} />
|
||||||
|
</div>
|
||||||
<EmailNotifications setUserData={setUserData} user={userData} />
|
<EmailNotifications setUserData={setUserData} user={userData} />
|
||||||
<Password user={userData} />
|
<Password user={userData} />
|
||||||
<StaffToken user={userData} />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
@ -192,7 +192,7 @@ const InvitesUserList: React.FC<InviteListProps> = ({users}) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const Users: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
const Users: React.FC<{ keywords: string[], highlight?: boolean }> = ({keywords, highlight = true}) => {
|
||||||
const {
|
const {
|
||||||
ownerUser,
|
ownerUser,
|
||||||
adminUsers,
|
adminUsers,
|
||||||
@ -246,6 +246,7 @@ const Users: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
|||||||
return (
|
return (
|
||||||
<SettingGroup
|
<SettingGroup
|
||||||
customButtons={buttons}
|
customButtons={buttons}
|
||||||
|
highlightOnModalClose={highlight}
|
||||||
keywords={keywords}
|
keywords={keywords}
|
||||||
navid='users'
|
navid='users'
|
||||||
testId='users'
|
testId='users'
|
||||||
|
Loading…
Reference in New Issue
Block a user