mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-03-11 02:12:21 -05:00
AdminX responsive design updates (#17979)
refs. https://github.com/TryGhost/Product/issues/3349 - A lot of pieces in AdminX missed proper handling of non-desktop devices
This commit is contained in:
parent
3d9f22c13a
commit
78e2cb0c28
37 changed files with 220 additions and 150 deletions
|
@ -42,23 +42,23 @@ function App({ghostVersion, officialThemes, zapierTemplates, externalNavigate}:
|
|||
>
|
||||
<Toaster />
|
||||
<NiceModal.Provider>
|
||||
<div className='fixed left-6 top-4 z-20'>
|
||||
<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] md:flex-row md:items-start md:gap-x-10 md:py-[8vmin]" id="admin-x-settings-content">
|
||||
<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="relative z-20 min-w-[260px] grow-0 md:fixed md:top-[8vmin] md:basis-[260px]">
|
||||
<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='h-[84px]'>
|
||||
<Heading>Settings</Heading>
|
||||
</div>
|
||||
<div className="relative mt-[-32px] w-[260px] overflow-x-hidden after:absolute after:inset-x-0 after:top-0 after:block after:h-[40px] after:bg-gradient-to-b after:from-white after:to-transparent after:content-['']">
|
||||
<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-[''] tablet:w-[260px] tablet:after:!visible tablet:after:!block">
|
||||
<Sidebar />
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative flex-auto pt-[3vmin] md:ml-[300px] md:pt-[85px]">
|
||||
<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] h-[80px] bg-gradient-to-t from-transparent to-white to-60%'></div>
|
||||
<Settings />
|
||||
</div>
|
||||
|
|
|
@ -42,7 +42,7 @@ const Avatar: React.FC<AvatarProps> = ({image, label, labelColor, bgColor, size,
|
|||
|
||||
if (image) {
|
||||
return (
|
||||
<img alt="" className={`inline-flex items-center justify-center rounded-full object-cover font-semibold ${avatarSize} ${className && className}`} src={image}/>
|
||||
<img alt="" className={`inline-flex shrink-0 items-center justify-center rounded-full object-cover font-semibold ${avatarSize} ${className && className}`} src={image}/>
|
||||
);
|
||||
} else if (label) {
|
||||
return (
|
||||
|
|
|
@ -78,7 +78,7 @@ const Button: React.FC<ButtonProps> = ({
|
|||
|
||||
styles += ` ${className}`;
|
||||
|
||||
const iconClasses = label && icon ? 'mr-1.5' : '';
|
||||
const iconClasses = label && icon && !hideLabel ? 'mr-1.5' : '';
|
||||
|
||||
const buttonChildren = <>
|
||||
{icon && <Icon className={iconClasses} colorClass={iconColorClass} name={icon} size={size === 'sm' ? 'sm' : 'md'} />}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import React from 'react';
|
||||
import Separator from './Separator';
|
||||
import clsx from 'clsx';
|
||||
|
||||
export type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
|
||||
|
||||
|
@ -41,7 +42,7 @@ export const Heading6Styles = 'text-2xs font-semibold uppercase tracking-wider';
|
|||
export const Heading6StylesGrey = 'text-2xs font-semibold uppercase tracking-wider text-grey-800';
|
||||
|
||||
const Heading: React.FC<Heading1to5Props | Heading6Props | HeadingLabelProps> = ({
|
||||
level,
|
||||
level = 1,
|
||||
children,
|
||||
styles = '',
|
||||
grey = true,
|
||||
|
@ -50,14 +51,37 @@ const Heading: React.FC<Heading1to5Props | Heading6Props | HeadingLabelProps> =
|
|||
className = '',
|
||||
...props
|
||||
}) => {
|
||||
if (!level) {
|
||||
level = 1;
|
||||
}
|
||||
|
||||
const newElement = `${useLabelTag ? 'label' : `h${level}`}`;
|
||||
styles += (level === 6 || useLabelTag) ? (` block ${grey ? Heading6StylesGrey : Heading6Styles}`) : ' ';
|
||||
|
||||
const Element = React.createElement(newElement, {className: styles + ' ' + className, key: 'heading-elem', ...props}, children);
|
||||
if (!useLabelTag) {
|
||||
switch (level) {
|
||||
case 1:
|
||||
styles += ' md:text-5xl';
|
||||
break;
|
||||
case 2:
|
||||
styles += ' md:text-3xl';
|
||||
break;
|
||||
case 3:
|
||||
styles += ' md:text-2xl';
|
||||
break;
|
||||
case 4:
|
||||
styles += ' md:text-xl';
|
||||
break;
|
||||
case 5:
|
||||
styles += ' md:text-lg';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
className = clsx(
|
||||
styles,
|
||||
className
|
||||
);
|
||||
|
||||
const Element = React.createElement(newElement, {className: className, key: 'heading-elem', ...props}, children);
|
||||
|
||||
if (separator) {
|
||||
let gap = (!level || level === 1) ? 2 : 1;
|
||||
|
|
|
@ -42,7 +42,7 @@ const ListItem: React.FC<ListItemProps> = ({
|
|||
};
|
||||
|
||||
const listItemClasses = clsx(
|
||||
'group flex items-center justify-between',
|
||||
'group/list-item flex items-center justify-between',
|
||||
bgOnHover && 'hover:bg-gradient-to-r hover:from-white hover:to-grey-50',
|
||||
separator ? 'border-b border-grey-100 last-of-type:border-b-transparent hover:border-grey-200' : 'border-y border-transparent hover:border-grey-200 first-of-type:hover:border-t-transparent',
|
||||
className
|
||||
|
@ -60,7 +60,7 @@ const ListItem: React.FC<ListItemProps> = ({
|
|||
</div>
|
||||
}
|
||||
{action &&
|
||||
<div className={`py-3 pl-6 ${paddingRight && 'pr-6'} ${hideActions ? 'invisible group-hover:visible' : ''}`}>
|
||||
<div className={`visible py-3 md:pl-6 ${paddingRight && 'md:pr-6'} ${hideActions ? 'group-hover/list-item:visible md:invisible' : ''}`}>
|
||||
{action}
|
||||
</div>
|
||||
}
|
||||
|
|
|
@ -23,7 +23,10 @@ type Story = StoryObj<typeof TabView>;
|
|||
const tabs = [
|
||||
{id: 'tab-1', title: 'Tab one', contents: <div className='py-5'>Contents one</div>},
|
||||
{id: 'tab-2', title: 'Tab two', contents: <div className='py-5'>Contents two</div>},
|
||||
{id: 'tab-3', title: 'Tab three', contents: <div className='py-5'>Contents three</div>}
|
||||
{id: 'tab-3', title: 'Tab three', contents: <div className='py-5'>Contents three</div>},
|
||||
{id: 'tab-4', title: 'Tab four', contents: <div className='py-5'>Contents one</div>},
|
||||
{id: 'tab-5', title: 'Tab five', contents: <div className='py-5'>Contents two</div>},
|
||||
{id: 'tab-6', title: 'Backstreet boys', contents: <div className='py-5'>Contents three</div>}
|
||||
];
|
||||
|
||||
export const Default: Story = {
|
||||
|
|
|
@ -40,7 +40,7 @@ function TabView<ID extends string = string>({
|
|||
};
|
||||
|
||||
const containerClasses = clsx(
|
||||
'flex',
|
||||
'flex w-full overflow-x-scroll',
|
||||
width === 'narrow' && 'gap-3',
|
||||
width === 'normal' && 'gap-5',
|
||||
width === 'wide' && 'gap-7',
|
||||
|
@ -55,7 +55,7 @@ function TabView<ID extends string = string>({
|
|||
key={tab.id}
|
||||
aria-selected={selectedTab === tab.id}
|
||||
className={clsx(
|
||||
'-m-b-px cursor-pointer appearance-none py-1 text-sm transition-all after:invisible after:block after:h-px after:overflow-hidden after:font-bold after:text-transparent after:content-[attr(title)]',
|
||||
'-m-b-px cursor-pointer appearance-none whitespace-nowrap py-1 text-sm transition-all after:invisible after:block after:h-px after:overflow-hidden after:font-bold after:text-transparent after:content-[attr(title)]',
|
||||
border && 'border-b-[3px]',
|
||||
selectedTab === tab.id && border ? 'border-black' : 'border-transparent hover:border-grey-500',
|
||||
selectedTab === tab.id && 'font-bold'
|
||||
|
|
|
@ -25,7 +25,7 @@ const TableRow: React.FC<TableRowProps> = ({id, action, hideActions, className,
|
|||
|
||||
separator = (separator === undefined) ? true : separator;
|
||||
const tableRowClasses = clsx(
|
||||
'group',
|
||||
'group/table-row',
|
||||
bgOnHover && 'hover:bg-gradient-to-r hover:from-white hover:to-grey-50',
|
||||
onClick && 'cursor-pointer',
|
||||
separator ? 'border-b border-grey-100 last-of-type:border-b-transparent hover:border-grey-200' : 'border-y border-transparent first-of-type:hover:border-t-transparent',
|
||||
|
@ -36,7 +36,7 @@ const TableRow: React.FC<TableRowProps> = ({id, action, hideActions, className,
|
|||
<tr className={tableRowClasses} data-testid={testId} id={id} onClick={handleClick}>
|
||||
{children}
|
||||
{action &&
|
||||
<td className={`px-6 py-3 text-center ${hideActions ? 'invisible group-hover:visible' : ''}`}>
|
||||
<td className={`visible block px-6 py-3 text-center ${hideActions ? 'group-hover/table-row:visible md:invisible' : ''}`}>
|
||||
{action}
|
||||
</td>
|
||||
}
|
||||
|
|
|
@ -58,7 +58,7 @@ const Form: React.FC<FormProps> = ({
|
|||
if (grouped) {
|
||||
classes = clsx(
|
||||
classes,
|
||||
'rounded-sm border border-grey-200 p-7'
|
||||
'rounded-sm border border-grey-200 p-4 md:p-7'
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -71,7 +71,7 @@ const ImageUpload: React.FC<ImageUploadProps> = ({
|
|||
|
||||
if (!deleteButtonUnstyled) {
|
||||
deleteButtonClassName = clsx(
|
||||
'invisible absolute right-4 top-4 flex h-8 w-8 cursor-pointer items-center justify-center rounded bg-[rgba(0,0,0,0.75)] text-white hover:bg-black group-hover:!visible',
|
||||
'absolute right-4 top-4 flex h-8 w-8 cursor-pointer items-center justify-center rounded bg-[rgba(0,0,0,0.75)] text-white hover:bg-black group-hover:!visible md:invisible',
|
||||
deleteButtonClassName
|
||||
);
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ export interface ModalProps {
|
|||
onOk?: () => void;
|
||||
onCancel?: () => void;
|
||||
topRightContent?: 'close' | React.ReactNode;
|
||||
hideXOnMobile?: boolean;
|
||||
afterClose?: () => void;
|
||||
children?: React.ReactNode;
|
||||
backDrop?: boolean;
|
||||
|
@ -54,6 +55,7 @@ const Modal: React.FC<ModalProps> = ({
|
|||
okColor = 'black',
|
||||
onCancel,
|
||||
topRightContent,
|
||||
hideXOnMobile = false,
|
||||
afterClose,
|
||||
children,
|
||||
backDrop = true,
|
||||
|
@ -144,31 +146,31 @@ const Modal: React.FC<ModalProps> = ({
|
|||
switch (size) {
|
||||
case 'sm':
|
||||
modalClasses += ' max-w-[480px] ';
|
||||
backdropClasses += ' p-[8vmin]';
|
||||
backdropClasses += ' p-4 md:p-[8vmin]';
|
||||
paddingClasses = 'p-8';
|
||||
break;
|
||||
|
||||
case 'md':
|
||||
modalClasses += ' max-w-[720px] ';
|
||||
backdropClasses += ' p-[8vmin]';
|
||||
backdropClasses += ' p-4 md:p-[8vmin]';
|
||||
paddingClasses = 'p-8';
|
||||
break;
|
||||
|
||||
case 'lg':
|
||||
modalClasses += ' max-w-[1020px] ';
|
||||
backdropClasses += ' p-[4vmin]';
|
||||
backdropClasses += ' p-4 md:p-[4vmin]';
|
||||
paddingClasses = 'p-8';
|
||||
break;
|
||||
|
||||
case 'xl':
|
||||
modalClasses += ' max-w-[1240px] ';
|
||||
backdropClasses += ' p-[3vmin]';
|
||||
backdropClasses += ' p-4 md:p-[3vmin]';
|
||||
paddingClasses = 'p-10';
|
||||
break;
|
||||
|
||||
case 'full':
|
||||
modalClasses += ' h-full ';
|
||||
backdropClasses += ' p-[3vmin]';
|
||||
backdropClasses += ' p-4 md:p-[3vmin]';
|
||||
paddingClasses = 'p-10';
|
||||
break;
|
||||
|
||||
|
@ -178,7 +180,7 @@ const Modal: React.FC<ModalProps> = ({
|
|||
break;
|
||||
|
||||
default:
|
||||
backdropClasses += ' p-[8vmin]';
|
||||
backdropClasses += ' p-4 md:p-[8vmin]';
|
||||
paddingClasses = 'p-8';
|
||||
break;
|
||||
}
|
||||
|
@ -187,6 +189,9 @@ const Modal: React.FC<ModalProps> = ({
|
|||
paddingClasses = 'p-0';
|
||||
}
|
||||
|
||||
// Set bottom padding for backdrop when the menu is on
|
||||
backdropClasses += ' max-[800px]:!pb-20';
|
||||
|
||||
let footerClasses = clsx(
|
||||
`${paddingClasses} ${stickyFooter ? 'py-6' : 'pt-0'}`,
|
||||
'flex w-full items-center justify-between'
|
||||
|
@ -204,7 +209,7 @@ const Modal: React.FC<ModalProps> = ({
|
|||
};
|
||||
|
||||
const modalStyles = (typeof size === 'number') ? {
|
||||
width: size + 'px'
|
||||
maxWidth: size + 'px'
|
||||
} : {};
|
||||
|
||||
let footerContent;
|
||||
|
@ -247,10 +252,10 @@ const Modal: React.FC<ModalProps> = ({
|
|||
<section className={modalClasses} data-testid={testId} style={modalStyles}>
|
||||
<div className={contentClasses}>
|
||||
<div className='h-full'>
|
||||
{topRightContent === 'close' ?
|
||||
{!topRightContent || topRightContent === 'close' ?
|
||||
(<>
|
||||
{title && <Heading level={3}>{title}</Heading>}
|
||||
<div className='absolute right-6 top-6'>
|
||||
<div className={`${topRightContent !== 'close' && 'md:!invisible md:!hidden'} ${hideXOnMobile && 'hidden'} absolute right-6 top-6`}>
|
||||
<Button className='-m-2 cursor-pointer p-2 opacity-50 hover:opacity-100' icon='close' size='sm' unstyled onClick={removeModal} />
|
||||
</div>
|
||||
</>)
|
||||
|
|
|
@ -209,16 +209,17 @@ export const PreviewModalContent: React.FC<PreviewModalProps> = ({
|
|||
size={size}
|
||||
testId={testId}
|
||||
title=''
|
||||
hideXOnMobile
|
||||
>
|
||||
<div className='flex h-full grow'>
|
||||
<div className={`flex grow flex-col ${previewBgColor === 'grey' ? 'bg-grey-50' : 'bg-white'}`}>
|
||||
<div className={`hidden grow flex-col md:!visible md:!flex ${previewBgColor === 'grey' ? 'bg-grey-50' : 'bg-white'}`}>
|
||||
{preview}
|
||||
</div>
|
||||
{sidebar &&
|
||||
<div className='relative flex h-full basis-[400px] flex-col border-l border-grey-100'>
|
||||
<div className='relative flex h-full w-full flex-col border-l border-grey-100 md:w-auto md:basis-[400px]'>
|
||||
{sidebarHeader ? sidebarHeader : (
|
||||
<div className='flex max-h-[74px] items-start justify-between gap-3 px-7 py-5'>
|
||||
<Heading className='mt-1' level={titleHeadingLevel}>{title}</Heading>
|
||||
<div className='flex max-h-[74px] items-center justify-between gap-3 px-7 py-5'>
|
||||
<Heading level={titleHeadingLevel}>{title}</Heading>
|
||||
{sidebarButtons ? sidebarButtons : <ButtonGroup buttons={buttons} /> }
|
||||
</div>
|
||||
)}
|
||||
|
|
|
@ -167,6 +167,7 @@ const SettingGroup: React.FC<SettingGroupProps> = ({
|
|||
border && 'border p-5 md:p-7',
|
||||
!checkVisible(keywords) ? 'hidden' : 'flex',
|
||||
highlight && 'before:pointer-events-none before:absolute before:inset-[1px] before:z-20 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',
|
||||
styles
|
||||
);
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ interface ISettingGroupContent {
|
|||
const SettingGroupContent: React.FC<ISettingGroupContent> = ({columns, values, children, className}) => {
|
||||
let styles = 'flex flex-col gap-x-6 gap-y-7';
|
||||
if (columns === 2) {
|
||||
styles = 'grid grid-cols-2 gap-x-8 gap-y-6';
|
||||
styles = 'grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-6';
|
||||
}
|
||||
|
||||
styles += ` ${className}`;
|
||||
|
|
|
@ -10,13 +10,15 @@ interface Props {
|
|||
const SettingGroupHeader: React.FC<Props> = ({title, description, children}) => {
|
||||
return (
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
{(title || description) &&
|
||||
{(title || description) &&
|
||||
<div>
|
||||
<Heading level={5}>{title}</Heading>
|
||||
{description && <p className="mt-0.5 max-w-lg text-sm">{description}</p>}
|
||||
{description && <p className="mt-0.5 hidden max-w-lg text-sm group-[.is-not-editing]:!visible group-[.is-not-editing]:!block md:!visible md:!block">{description}</p>}
|
||||
</div>
|
||||
}
|
||||
{children}
|
||||
<div className='-mt-0.5'>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -25,58 +25,59 @@ const Sidebar: React.FC = () => {
|
|||
const hasRecommendations = useFeatureFlag('recommendations');
|
||||
|
||||
return (
|
||||
<div className="hidden md:!visible md:!block md:h-[calc(100vh-5vmin-84px)] md:w-[240px] md:overflow-y-scroll md:pt-[32px]">
|
||||
<div className='relative mb-10'>
|
||||
<Icon className='absolute top-2' colorClass='text-grey-500' name='magnifying-glass' size='sm' />
|
||||
<div className='tablet:h-[calc(100vh-5vmin-84px)] tablet:w-[240px] tablet:overflow-y-scroll'>
|
||||
<div className='relative mb-10 md:pt-4 tablet:pt-[32px]'>
|
||||
<Icon className='absolute top-2 md:top-6 tablet:top-10' colorClass='text-grey-500' name='magnifying-glass' size='sm' />
|
||||
<TextField autoComplete="off" className='border-b border-grey-500 px-3 py-1.5 pl-[24px] text-sm' placeholder="Search" title="Search" value={filter} hideTitle unstyled onChange={e => setFilter(e.target.value)} />
|
||||
</div>
|
||||
<div className="hidden tablet:!visible tablet:!block">
|
||||
<SettingNavSection title="General">
|
||||
<SettingNavItem navid='title-and-description' title="Title and description" onClick={handleSectionClick} />
|
||||
<SettingNavItem navid='timezone' title="Timezone" onClick={handleSectionClick} />
|
||||
<SettingNavItem navid='publication-language' title="Publication language" onClick={handleSectionClick} />
|
||||
<SettingNavItem navid='metadata' title="Meta data" onClick={handleSectionClick} />
|
||||
<SettingNavItem navid='twitter' title="Twitter card" onClick={handleSectionClick} />
|
||||
<SettingNavItem navid='facebook' title="Facebook card" onClick={handleSectionClick} />
|
||||
<SettingNavItem navid='social-accounts' title="Social accounts" onClick={handleSectionClick} />
|
||||
<SettingNavItem navid='locksite' title="Make this site private" onClick={handleSectionClick} />
|
||||
<SettingNavItem navid='users' title="Users and permissions" onClick={handleSectionClick} />
|
||||
</SettingNavSection>
|
||||
|
||||
<SettingNavSection title="General">
|
||||
<SettingNavItem navid='title-and-description' title="Title and description" onClick={handleSectionClick} />
|
||||
<SettingNavItem navid='timezone' title="Timezone" onClick={handleSectionClick} />
|
||||
<SettingNavItem navid='publication-language' title="Publication language" onClick={handleSectionClick} />
|
||||
<SettingNavItem navid='metadata' title="Meta data" onClick={handleSectionClick} />
|
||||
<SettingNavItem navid='twitter' title="Twitter card" onClick={handleSectionClick} />
|
||||
<SettingNavItem navid='facebook' title="Facebook card" onClick={handleSectionClick} />
|
||||
<SettingNavItem navid='social-accounts' title="Social accounts" onClick={handleSectionClick} />
|
||||
<SettingNavItem navid='locksite' title="Make this site private" onClick={handleSectionClick} />
|
||||
<SettingNavItem navid='users' title="Users and permissions" onClick={handleSectionClick} />
|
||||
</SettingNavSection>
|
||||
<SettingNavSection title="Site">
|
||||
{/* <SettingNavItem navid='theme' title="Theme" onClick={handleSectionClick} /> */}
|
||||
<SettingNavItem navid='design' title="Branding and design" onClick={handleSectionClick} />
|
||||
<SettingNavItem navid='navigation' title="Navigation" onClick={handleSectionClick} />
|
||||
<SettingNavItem navid='announcement-bar' title="Announcement bar" onClick={handleSectionClick} />
|
||||
</SettingNavSection>
|
||||
|
||||
<SettingNavSection title="Site">
|
||||
{/* <SettingNavItem navid='theme' title="Theme" onClick={handleSectionClick} /> */}
|
||||
<SettingNavItem navid='design' title="Branding and design" onClick={handleSectionClick} />
|
||||
<SettingNavItem navid='navigation' title="Navigation" onClick={handleSectionClick} />
|
||||
<SettingNavItem navid='announcement-bar' title="Announcement bar" onClick={handleSectionClick} />
|
||||
</SettingNavSection>
|
||||
<SettingNavSection title="Membership">
|
||||
<SettingNavItem navid='portal' title="Portal" onClick={handleSectionClick} />
|
||||
<SettingNavItem navid='access' title="Access" onClick={handleSectionClick} />
|
||||
<SettingNavItem navid='tiers' title="Tiers" onClick={handleSectionClick} />
|
||||
{hasTipsAndDonations && <SettingNavItem navid='tips-or-donations' title="Tips or donations" onClick={handleSectionClick} />}
|
||||
<SettingNavItem navid='embed-signup-form' title="Embeddable signup form" onClick={handleSectionClick} />
|
||||
{hasRecommendations && <SettingNavItem navid='recommendations' title="Recommendations" onClick={handleSectionClick} />}
|
||||
<SettingNavItem navid='analytics' title="Analytics" onClick={handleSectionClick} />
|
||||
</SettingNavSection>
|
||||
|
||||
<SettingNavSection title="Membership">
|
||||
<SettingNavItem navid='portal' title="Portal" onClick={handleSectionClick} />
|
||||
<SettingNavItem navid='access' title="Access" onClick={handleSectionClick} />
|
||||
<SettingNavItem navid='tiers' title="Tiers" onClick={handleSectionClick} />
|
||||
{hasTipsAndDonations && <SettingNavItem navid='tips-or-donations' title="Tips or donations" onClick={handleSectionClick} />}
|
||||
<SettingNavItem navid='embed-signup-form' title="Embeddable signup form" onClick={handleSectionClick} />
|
||||
{hasRecommendations && <SettingNavItem navid='recommendations' title="Recommendations" onClick={handleSectionClick} />}
|
||||
<SettingNavItem navid='analytics' title="Analytics" onClick={handleSectionClick} />
|
||||
</SettingNavSection>
|
||||
<SettingNavSection title="Email newsletters">
|
||||
<SettingNavItem navid='enable-newsletters' title="Newsletter sending" onClick={handleSectionClick} />
|
||||
{newslettersEnabled !== 'disabled' && (
|
||||
<>
|
||||
<SettingNavItem navid='newsletters' title="Newsletters" onClick={handleSectionClick} />
|
||||
<SettingNavItem navid='default-recipients' title="Default recipients" onClick={handleSectionClick} />
|
||||
<SettingNavItem navid='mailgun' title="Mailgun settings" onClick={handleSectionClick} />
|
||||
</>
|
||||
)}
|
||||
</SettingNavSection>
|
||||
|
||||
<SettingNavSection title="Email newsletters">
|
||||
<SettingNavItem navid='enable-newsletters' title="Newsletter sending" onClick={handleSectionClick} />
|
||||
{newslettersEnabled !== 'disabled' && (
|
||||
<>
|
||||
<SettingNavItem navid='newsletters' title="Newsletters" onClick={handleSectionClick} />
|
||||
<SettingNavItem navid='default-recipients' title="Default recipients" onClick={handleSectionClick} />
|
||||
<SettingNavItem navid='mailgun' title="Mailgun settings" onClick={handleSectionClick} />
|
||||
</>
|
||||
)}
|
||||
</SettingNavSection>
|
||||
|
||||
<SettingNavSection title="Advanced">
|
||||
<SettingNavItem navid='integrations' title="Integrations" onClick={handleSectionClick} />
|
||||
<SettingNavItem navid='code-injection' title="Code injection" onClick={handleSectionClick} />
|
||||
<SettingNavItem navid='labs' title="Labs" onClick={handleSectionClick} />
|
||||
<SettingNavItem navid='history' title="History" onClick={handleSectionClick} />
|
||||
</SettingNavSection>
|
||||
<SettingNavSection title="Advanced">
|
||||
<SettingNavItem navid='integrations' title="Integrations" onClick={handleSectionClick} />
|
||||
<SettingNavItem navid='code-injection' title="Code injection" onClick={handleSectionClick} />
|
||||
<SettingNavItem navid='labs' title="Labs" onClick={handleSectionClick} />
|
||||
<SettingNavItem navid='history' title="History" onClick={handleSectionClick} />
|
||||
</SettingNavSection>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -31,7 +31,7 @@ const HistoryIcon: React.FC<{action: Action}> = ({action}) => {
|
|||
|
||||
const HistoryAvatar: React.FC<{action: Action}> = ({action}) => {
|
||||
return (
|
||||
<div className='relative'>
|
||||
<div className='relative shrink-0'>
|
||||
<Avatar
|
||||
bgColor={generateAvatarColor(action.actor?.name || action.actor?.slug || '')}
|
||||
image={action.actor?.image}
|
||||
|
@ -68,7 +68,7 @@ const HistoryFilter: React.FC<{
|
|||
toggleResourceType: (resource: string, included: boolean) => void;
|
||||
}> = ({excludedEvents, excludedResources, toggleEventType, toggleResourceType}) => {
|
||||
return (
|
||||
<Popover trigger={<Button label='Filter' link />}>
|
||||
<Popover position='right' trigger={<Button label='Filter' link />}>
|
||||
<div className='flex w-[220px] flex-col gap-8 p-5'>
|
||||
<ToggleGroup>
|
||||
<HistoryFilterToggle excludedItems={excludedEvents} item='added' label='Added' toggleItem={toggleEventType} />
|
||||
|
|
|
@ -192,7 +192,7 @@ const Integrations: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
|||
] as const;
|
||||
|
||||
const buttons = (
|
||||
<Button color='green' label='Add custom integration' link={true} onClick={() => {
|
||||
<Button className='hidden md:!visible md:!block' color='green' label='Add custom integration' link={true} onClick={() => {
|
||||
updateRoute('integrations/add');
|
||||
setSelectedTab('custom');
|
||||
}} />
|
||||
|
@ -207,6 +207,12 @@ const Integrations: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
|||
testId='integrations'
|
||||
title="Integrations"
|
||||
>
|
||||
<div className='flex justify-center rounded border border-green px-4 py-2 md:hidden'>
|
||||
<Button color='green' label='Add custom integration' link onClick={() => {
|
||||
updateRoute('integrations/add');
|
||||
setSelectedTab('custom');
|
||||
}} />
|
||||
</div>
|
||||
<TabView<'built-in' | 'custom'> selectedTab={selectedTab} tabs={tabs} onTabChange={setSelectedTab} />
|
||||
</SettingGroup>
|
||||
);
|
||||
|
|
|
@ -18,11 +18,11 @@ const APIKeyField: React.FC<APIKeyFieldProps> = ({label, text = '', hint, onRege
|
|||
};
|
||||
|
||||
return <>
|
||||
<div className='p-0 py-1 pr-4 text-sm text-grey-600'>{label}</div>
|
||||
<div className='group relative overflow-hidden rounded p-1 text-sm hover:bg-grey-50'>
|
||||
<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 md:mb-0 md:p-1'>
|
||||
{text}
|
||||
{hint}
|
||||
<div className='invisible absolute right-0 top-[50%] flex translate-y-[-50%] gap-1 bg-white pl-1 text-sm group-hover:visible'>
|
||||
<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 md:invisible'>
|
||||
{onRegenerate && <Button color='outline' label='Regenerate' size='sm' onClick={onRegenerate} />}
|
||||
<Button color='outline' label={copied ? 'Copied' : 'Copy'} size='sm' onClick={copyText} />
|
||||
</div>
|
||||
|
@ -32,7 +32,7 @@ const APIKeyField: React.FC<APIKeyFieldProps> = ({label, text = '', hint, onRege
|
|||
|
||||
const APIKeys: React.FC<{keys: APIKeyFieldProps[]}> = ({keys}) => {
|
||||
return (
|
||||
<div className='grid grid-cols-[max-content_1fr]'>
|
||||
<div className='grid grid-cols-1 md:grid-cols-[max-content_1fr]'>
|
||||
{keys.map(key => <APIKeyField key={key.label} {...key} />)}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -14,7 +14,7 @@ const IntegrationHeader: React.FC<IntegrationHeaderProps> = ({
|
|||
extra
|
||||
}) => {
|
||||
return (
|
||||
<div className='flex w-full gap-4'>
|
||||
<div className='flex w-full flex-col gap-4 md:flex-row'>
|
||||
<div className='h-14 w-14'>{icon}</div>
|
||||
<div className='flex min-w-0 flex-1 flex-col'>
|
||||
<h3>{title}</h3>
|
||||
|
|
|
@ -99,17 +99,18 @@ const PinturaModal = NiceModal.create(() => {
|
|||
title='Pintura'
|
||||
/>
|
||||
<div className='mt-7'>
|
||||
<div className='mb-7 flex items-stretch justify-between gap-4 rounded-sm bg-grey-75 p-7'>
|
||||
<div className='basis-1/2'>
|
||||
<div className='mb-7 flex flex-col items-stretch justify-between gap-4 rounded-sm bg-grey-75 p-4 md:flex-row md:p-7'>
|
||||
<div className='md:basis-1/2'>
|
||||
<p className='mb-4 font-bold'>Add advanced image editing to Ghost, with Pintura</p>
|
||||
<p className='mb-4 text-sm'>Pintura is a powerful JavaScript image editor that allows you to crop, rotate, annotate and modify images directly inside Ghost.</p>
|
||||
<p className='text-sm'>Try a demo, purchase a license, and download the required CSS/JS files from pqina.nl/pintura/ to activate this feature.</p>
|
||||
</div>
|
||||
<div className='flex grow basis-1/2 flex-col items-end justify-between'>
|
||||
<div className='flex grow flex-col items-end justify-between gap-2 md:basis-1/2 md:gap-0'>
|
||||
<img alt='Pintura screenshot' src={pinturaScreenshot} />
|
||||
<a className='-mb-1 text-sm font-bold text-green' href="https://pqina.nl/pintura/?ref=ghost.org" rel="noopener noreferrer" target="_blank">Find out more →</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Form marginBottom={false} title='Pintura configuration' grouped>
|
||||
<Toggle
|
||||
checked={enabled}
|
||||
|
@ -122,7 +123,7 @@ const PinturaModal = NiceModal.create(() => {
|
|||
/>
|
||||
{enabled && (
|
||||
<>
|
||||
<div className='flex items-center justify-between'>
|
||||
<div className='flex flex-col justify-between gap-1 md:flex-row md:items-center'>
|
||||
<div>
|
||||
<div>Upload Pintura script</div>
|
||||
<div className='text-xs text-grey-600'>Upload the <code>pintura-umd.js</code> file from the Pintura package</div>
|
||||
|
@ -134,7 +135,7 @@ const PinturaModal = NiceModal.create(() => {
|
|||
triggerUpload('js');
|
||||
}} />
|
||||
</div>
|
||||
<div className='flex items-center justify-between'>
|
||||
<div className='flex flex-col justify-between gap-1 md:flex-row md:items-center'>
|
||||
<div>
|
||||
<div>Upload Pintura styles</div>
|
||||
<div className='text-xs text-grey-600'>Upload the <code>pintura.css</code> file from the Pintura package</div>
|
||||
|
|
|
@ -90,7 +90,7 @@ const SlackModal = NiceModal.create(() => {
|
|||
onChange={e => updateSetting('slack_url', e.target.value)}
|
||||
onKeyDown={() => clearError('slackUrl')}
|
||||
/>
|
||||
<div className='flex w-full items-center gap-2'>
|
||||
<div className='flex w-full flex-col gap-2 md:flex-row md:items-center'>
|
||||
<TextField
|
||||
containerClassName='flex-grow'
|
||||
hint='The username to display messages from'
|
||||
|
|
|
@ -97,28 +97,31 @@ const ZapierModal = NiceModal.create(() => {
|
|||
{zapierTemplates.map(template => (
|
||||
<ListItem
|
||||
action={<Button className='whitespace-nowrap text-sm font-semibold text-[#FF4A00]' href={template.url} label='Use this Zap' tag='a' target='_blank' link unstyled />}
|
||||
avatar={<>
|
||||
<img className='h-8 w-8 object-contain' role='presentation' src={`${adminRoot}${template.ghostImage}`} />
|
||||
<ArrowRightIcon className='h-3 w-3' />
|
||||
<img className='h-8 w-8 object-contain' role='presentation' src={`${adminRoot}${template.appImage}`} />
|
||||
</>}
|
||||
bgOnHover={false}
|
||||
className='flex items-center gap-3 py-2'
|
||||
title={<span className='text-sm'>{template.title}</span>}
|
||||
title={
|
||||
<div className='flex flex-col gap-4 md:flex-row md:items-center'>
|
||||
<div className='flex shrink-0 flex-nowrap items-center gap-2'>
|
||||
<img className='h-8 w-8 object-contain' role='presentation' src={`${adminRoot}${template.ghostImage}`} />
|
||||
<ArrowRightIcon className='h-3 w-3' />
|
||||
<img className='h-8 w-8 object-contain' role='presentation' src={`${adminRoot}${template.appImage}`} />
|
||||
</div>
|
||||
<span className='text-sm'>{template.title}</span>
|
||||
</div>
|
||||
}
|
||||
hideActions
|
||||
/>
|
||||
))}
|
||||
</List>
|
||||
|
||||
<div className='mt-6 flex'>
|
||||
<Button
|
||||
<div className='mt-6'>
|
||||
<a
|
||||
className='mt-6 self-baseline text-sm font-bold'
|
||||
href='https://zapier.com/apps/ghost/integrations?utm_medium=partner_api&utm_source=widget&utm_campaign=Widget'
|
||||
label={<>View more Ghost integrations powered by <Logo className='relative top-[-1px] ml-1 h-6' /></>}
|
||||
rel='noopener noreferrer'
|
||||
tag='a'
|
||||
target='_blank'
|
||||
link
|
||||
/>
|
||||
target='_blank'>
|
||||
View more Ghost integrations powered by <span><Logo className='relative top-[-2px] inline-block h-6' /></span>
|
||||
</a>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
|
|
|
@ -76,13 +76,13 @@ const NewsletterItem: React.FC<{newsletter: Newsletter, onlyOne: boolean}> = ({n
|
|||
<span className='whitespace-nowrap text-xs text-grey-700'>{newsletter.description || 'No description'}</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell onClick={showDetails}>
|
||||
<TableCell className='hidden md:!visible md:!table-cell' onClick={showDetails}>
|
||||
<div className={`flex grow flex-col`}>
|
||||
<span>{newsletter.count?.active_members}</span>
|
||||
<span className='whitespace-nowrap text-xs text-grey-700'>Subscribers</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell onClick={showDetails}>
|
||||
<TableCell className='hidden md:!visible md:!table-cell' onClick={showDetails}>
|
||||
<div className={`flex grow flex-col`}>
|
||||
<span>{newsletter.count?.posts}</span>
|
||||
<span className='whitespace-nowrap text-xs text-grey-700'>Posts sent</span>
|
||||
|
|
|
@ -48,7 +48,7 @@ const Facebook: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
|||
);
|
||||
|
||||
const inputFields = (
|
||||
<div className="mx-[52px]">
|
||||
<div className="md:mx-[52px]">
|
||||
<div className="mb-4 flex items-center gap-2">
|
||||
<div>
|
||||
<FacebookLogo className='h-10 w-10' />
|
||||
|
|
|
@ -51,7 +51,7 @@ const LockSite: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
|||
);
|
||||
|
||||
const hint = (
|
||||
<>A private RSS feed is available at <Link href="http://localhost:2368/51aa059ba6eb50c24c14047d4255ac/rss">http://localhost:2368/51aa059ba6eb50c24c14047d4255ac/rss</Link></>
|
||||
<>A private RSS feed is available at <Link className='break-all' href="http://localhost:2368/51aa059ba6eb50c24c14047d4255ac/rss">http://localhost:2368/51aa059ba6eb50c24c14047d4255ac/rss</Link></>
|
||||
);
|
||||
|
||||
const inputs = (
|
||||
|
|
|
@ -52,11 +52,11 @@ const Twitter: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
|||
);
|
||||
|
||||
const inputFields = (
|
||||
<div className="flex gap-3">
|
||||
<div className="flex flex-col gap-3 md:flex-row">
|
||||
<div className="pt-1">
|
||||
<TwitterLogo className='-mb-1 h-10 w-10' />
|
||||
</div>
|
||||
<div className="mr-[52px] w-full">
|
||||
<div className="w-full md:mr-[52px]">
|
||||
<div className="mb-2">
|
||||
<span className="mr-1 font-semibold text-grey-900">{siteTitle}</span>
|
||||
<span className="text-grey-700">· 2h</span>
|
||||
|
|
|
@ -629,7 +629,7 @@ const UserDetailModalContent: React.FC<{user: User}> = ({user}) => {
|
|||
okLabel = 'Saved';
|
||||
}
|
||||
|
||||
const fileUploadButtonClasses = 'absolute 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 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 suspendedText = userData.status === 'inactive' ? ' (Suspended)' : '';
|
||||
|
||||
|
@ -706,11 +706,11 @@ const UserDetailModalContent: React.FC<{user: User}> = ({user}) => {
|
|||
}}
|
||||
>Upload cover image</ImageUpload>
|
||||
<div className="absolute bottom-12 right-12 z-10">
|
||||
<Menu items={menuItems} position='left' trigger={<UserMenuTrigger />}></Menu>
|
||||
<Menu items={menuItems} position='right' trigger={<UserMenuTrigger />}></Menu>
|
||||
</div>
|
||||
<div className='relative flex items-center gap-4 px-12 pb-7 pt-60'>
|
||||
<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'>
|
||||
<ImageUpload
|
||||
deleteButtonClassName='invisible absolute -right-2 -top-2 flex h-8 w-8 cursor-pointer items-center justify-center rounded-full bg-[rgba(0,0,0,0.75)] text-white hover:bg-black group-hover:!visible'
|
||||
deleteButtonClassName='md:invisible absolute -right-2 -top-2 flex h-8 w-8 cursor-pointer items-center justify-center rounded-full bg-[rgba(0,0,0,0.75)] text-white hover:bg-black group-hover:!visible'
|
||||
deleteButtonContent={<Icon colorClass='text-white' name='trash' size='sm' />}
|
||||
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'
|
||||
|
@ -734,7 +734,7 @@ const UserDetailModalContent: React.FC<{user: User}> = ({user}) => {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='mt-10 grid grid-cols-2 gap-x-12 gap-y-20'>
|
||||
<div className='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} />
|
||||
<Details errors={errors} setUserData={setUserData} user={userData} validators={validators} />
|
||||
<EmailNotifications setUserData={setUserData} user={userData} />
|
||||
|
|
|
@ -43,7 +43,7 @@ const Owner: React.FC<OwnerProps> = ({user}) => {
|
|||
<div className='group flex gap-3 hover: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='invisible ml-2 inline-block text-sm font-bold text-green group-hover:visible' type='button'>Edit</button></span>
|
||||
<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'>Edit</button></span>
|
||||
<span className='text-xs text-grey-700'>{user.email}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -3,11 +3,25 @@ import SettingGroup from '../../../admin-x-ds/settings/SettingGroup';
|
|||
import StripeButton from '../../../admin-x-ds/settings/StripeButton';
|
||||
import TabView from '../../../admin-x-ds/global/TabView';
|
||||
import TiersList from './tiers/TiersList';
|
||||
import clsx from 'clsx';
|
||||
import useRouting from '../../../hooks/useRouting';
|
||||
import {Tier, getActiveTiers, getArchivedTiers, useBrowseTiers} from '../../../api/tiers';
|
||||
import {checkStripeEnabled} from '../../../api/settings';
|
||||
import {useGlobalData} from '../../providers/GlobalDataProvider';
|
||||
|
||||
const StripeConnectedButton: React.FC<{className?: string; onClick: () => void;}> = ({className, onClick}) => {
|
||||
className = clsx(
|
||||
'group flex shrink-0 items-center justify-center whitespace-nowrap rounded border border-grey-300 px-3 py-1.5 text-sm font-semibold text-grey-900 transition-all hover:border-grey-500',
|
||||
className
|
||||
);
|
||||
return (
|
||||
<button className={className} type='button' onClick={onClick}>
|
||||
<span className="inline-flex h-2 w-2 rounded-full bg-green transition-all group-hover:bg-[#625BF6]"></span>
|
||||
<span className='ml-2'>Connected to Stripe</span>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
const Tiers: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
||||
const [selectedTab, setSelectedTab] = useState('active-tiers');
|
||||
const {settings, config} = useGlobalData();
|
||||
|
@ -54,18 +68,23 @@ const Tiers: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
|||
return (
|
||||
<SettingGroup
|
||||
customButtons={checkStripeEnabled(settings, config) ?
|
||||
<button className='group flex items-center gap-2 rounded border border-grey-300 px-3 py-1.5 text-sm font-semibold text-grey-900 transition-all hover:border-grey-500' type='button' onClick={openConnectModal}>
|
||||
<span className="inline-flex h-2 w-2 rounded-full bg-green transition-all group-hover:bg-[#625BF6]"></span>
|
||||
Connected to Stripe
|
||||
</button>
|
||||
<StripeConnectedButton className='hidden tablet:!visible tablet:!block' onClick={openConnectModal} />
|
||||
:
|
||||
<StripeButton onClick={openConnectModal}/>}
|
||||
<StripeButton className='hidden tablet:!visible tablet:!block' onClick={openConnectModal}/>}
|
||||
description='Set prices and paid member sign up settings'
|
||||
keywords={keywords}
|
||||
navid='tiers'
|
||||
testId='tiers'
|
||||
title='Tiers'
|
||||
>
|
||||
<div className='w-full tablet:hidden'>
|
||||
{checkStripeEnabled(settings, config) ?
|
||||
<StripeConnectedButton className='w-full' onClick={openConnectModal} />
|
||||
:
|
||||
<StripeButton onClick={openConnectModal}/>
|
||||
}
|
||||
</div>
|
||||
|
||||
{content}
|
||||
</SettingGroup>
|
||||
);
|
||||
|
|
|
@ -193,13 +193,13 @@ const Connected: React.FC<{onClose?: () => void}> = ({onClose}) => {
|
|||
<img alt='Ghost Logo' className='absolute left-10 h-16 w-16' src={GhostLogo} />
|
||||
<img alt='Stripe Logo' className='absolute right-10 h-16 w-16 rounded-2xl shadow-[-1.5px_0_0_1.5px_#fff]' src={StripeLogo} />
|
||||
</div>
|
||||
<Heading level={3}>You are connected with Stripe!{stripeConnectLivemode ? null : ' (Test mode)'}</Heading>
|
||||
<Heading className='text-center' level={3}>You are connected with Stripe!{stripeConnectLivemode ? null : ' (Test mode)'}</Heading>
|
||||
<div className='mt-1'>Connected to <strong>Dummy</strong></div>
|
||||
</div>
|
||||
<div className='flex flex-col items-center'>
|
||||
<Heading level={6}>Read next</Heading>
|
||||
<a className='w-100 mt-5 flex items-stretch justify-between border border-grey-200 transition-all hover:border-grey-400' href="https://ghost.org/resources/managing-your-stripe-account/?ref=admin" rel="noopener noreferrer" target="_blank">
|
||||
<div className='p-4'>
|
||||
<a className='w-100 mt-5 flex flex-col items-stretch justify-between border border-grey-200 transition-all hover:border-grey-400 md:flex-row' href="https://ghost.org/resources/managing-your-stripe-account/?ref=admin" rel="noopener noreferrer" target="_blank">
|
||||
<div className='order-2 p-4 md:order-1'>
|
||||
<div className='font-bold'>How to setup and manage your Stripe account</div>
|
||||
<div className='mt-1 text-sm text-grey-800'>Learn how to configure your Stripe account to work with Ghost, from custom branding to payment receipt emails.</div>
|
||||
<div className='mt-3 flex items-center gap-1 text-sm text-grey-800'>
|
||||
|
@ -209,7 +209,7 @@ const Connected: React.FC<{onClose?: () => void}> = ({onClose}) => {
|
|||
<span>by Kym Ellis</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex w-[200px] shrink-0 items-center justify-center overflow-hidden'>
|
||||
<div className='order-1 hidden w-[200px] shrink-0 items-center justify-center overflow-hidden md:!visible md:order-2 md:!flex'>
|
||||
<img alt="Bookmark Thumb" className='min-h-full min-w-full shrink-0' src={BookmarkThumb} />
|
||||
</div>
|
||||
</a>
|
||||
|
@ -292,6 +292,7 @@ const StripeConnectModal: React.FC = () => {
|
|||
size={stripeConnectAccountId ? 740 : 520}
|
||||
testId='stripe-modal'
|
||||
title=''
|
||||
hideXOnMobile
|
||||
>
|
||||
{contents}
|
||||
</Modal>;
|
||||
|
|
|
@ -151,7 +151,7 @@ const TierDetailModalContent: React.FC<{tier?: Tier}> = ({tier}) => {
|
|||
value={formState.description || ''}
|
||||
onChange={e => updateForm(state => ({...state, description: e.target.value}))}
|
||||
/>
|
||||
{!isFreeTier && <div className='flex gap-10'>
|
||||
{!isFreeTier && <div className='flex flex-col gap-10 md:flex-row'>
|
||||
<div className='basis-1/2'>
|
||||
<div className='mb-1 flex h-6 items-center justify-between'>
|
||||
<Heading level={6}>Prices</Heading>
|
||||
|
@ -253,7 +253,7 @@ const TierDetailModalContent: React.FC<{tier?: Tier}> = ({tier}) => {
|
|||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
<div className='sticky top-[94px] shrink-0 basis-[380px]'>
|
||||
<div className='sticky top-[94px] hidden shrink-0 basis-[380px] min-[920px]:!visible min-[920px]:!block'>
|
||||
<TierDetailPreview isFreeTier={isFreeTier} tier={formState} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -16,7 +16,7 @@ interface TierCardProps {
|
|||
tier: Tier;
|
||||
}
|
||||
|
||||
const cardContainerClasses = 'group flex min-h-[200px] flex-col items-start justify-between gap-4 self-stretch rounded-sm border border-grey-300 p-4 transition-all hover:border-grey-400';
|
||||
const cardContainerClasses = 'tablet:group flex min-[900px]:min-h-[200px] flex-col items-start justify-between gap-4 self-stretch rounded-sm border border-grey-300 p-4 transition-all hover:border-grey-400';
|
||||
|
||||
const TierCard: React.FC<TierCardProps> = ({tier}) => {
|
||||
const {updateRoute} = useRouting();
|
||||
|
@ -41,11 +41,11 @@ const TierCard: React.FC<TierCardProps> = ({tier}) => {
|
|||
</div>
|
||||
{tier.monthly_price && (
|
||||
tier.active ?
|
||||
<Button className='group opacity-0 group-hover:opacity-100' color='red' label='Archive' link onClick={() => {
|
||||
<Button className='group group-hover:opacity-100 tablet:opacity-0' color='red' label='Archive' link onClick={() => {
|
||||
updateTier({...tier, active: false});
|
||||
}}/>
|
||||
:
|
||||
<Button className='group opacity-0 group-hover:opacity-100' color='green' label='Activate' link onClick={() => {
|
||||
<Button className='group group-hover:opacity-100 tablet:opacity-0' color='green' label='Activate' link onClick={() => {
|
||||
updateTier({...tier, active: true});
|
||||
}}/>
|
||||
)}
|
||||
|
@ -71,7 +71,7 @@ const TiersList: React.FC<TiersListProps> = ({
|
|||
}
|
||||
|
||||
return (
|
||||
<div className='mt-4 grid grid-cols-3 gap-4'>
|
||||
<div className='mt-4 grid grid-cols-1 gap-4 min-[900px]:grid-cols-3'>
|
||||
{tiers.map((tier) => {
|
||||
return <TierCard tier={tier} />;
|
||||
})}
|
||||
|
|
|
@ -28,7 +28,7 @@ type SidebarProps = {
|
|||
};
|
||||
|
||||
const Sidebar: React.FC<SidebarProps> = ({
|
||||
announcementContent,
|
||||
announcementContent,
|
||||
announcementTextHandler,
|
||||
accentColor,
|
||||
announcementBackgroundColor,
|
||||
|
|
|
@ -21,23 +21,23 @@
|
|||
}
|
||||
|
||||
h1 {
|
||||
@apply text-5xl leading-supertight;
|
||||
@apply text-4xl leading-supertight;
|
||||
}
|
||||
|
||||
h2 {
|
||||
@apply text-3xl;
|
||||
}
|
||||
|
||||
h3 {
|
||||
@apply text-2xl;
|
||||
}
|
||||
|
||||
h4 {
|
||||
h3 {
|
||||
@apply text-xl;
|
||||
}
|
||||
|
||||
h4 {
|
||||
@apply text-lg;
|
||||
}
|
||||
|
||||
h5 {
|
||||
@apply text-lg leading-tight;
|
||||
@apply text-md leading-supertight;
|
||||
}
|
||||
|
||||
h6 {
|
||||
|
|
|
@ -10,7 +10,8 @@ module.exports = {
|
|||
sm: '480px',
|
||||
md: '640px',
|
||||
lg: '1024px',
|
||||
xl: '1280px'
|
||||
xl: '1280px',
|
||||
tablet: '800px'
|
||||
},
|
||||
colors: {
|
||||
transparent: 'transparent',
|
||||
|
|
|
@ -35,7 +35,8 @@ test.describe('Stripe settings', async () => {
|
|||
|
||||
await expect(modal).toHaveCount(0);
|
||||
|
||||
await expect(section.getByText('Connected to Stripe')).toHaveCount(1);
|
||||
// There's a mobile version of the same button in the DOM
|
||||
await expect(section.getByText('Connected to Stripe')).toHaveCount(2);
|
||||
|
||||
// We actually do two settings update requests here, this just checks the last one
|
||||
expect(lastApiRequests.editSettings?.body).toEqual({
|
||||
|
@ -74,7 +75,8 @@ test.describe('Stripe settings', async () => {
|
|||
|
||||
await expect(modal).toHaveCount(0);
|
||||
|
||||
await expect(section.getByText('Connected to Stripe')).toHaveCount(1);
|
||||
// There's a mobile version of the same button in the DOM
|
||||
await expect(section.getByText('Connected to Stripe')).toHaveCount(2);
|
||||
|
||||
expect(lastApiRequests.editSettings?.body).toEqual({
|
||||
settings: [{
|
||||
|
|
Loading…
Add table
Reference in a new issue