mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-06 22:40:14 -05:00
🎨 Improved Access card layout in Settings (#21913)
As part of our efforts to add more personality to Settings, we've identified areas where we can improve the UX. This change makes it easier to manipulate Access settings by removing a step from the flow. fixes https://linear.app/ghost/issue/DES-484/improve-access-card-layout-in-settings **Before** <img width="1718" alt="access-card-before" src="https://github.com/user-attachments/assets/8c0f8f14-31b9-4712-93d2-97eb0f05c965" /> **After** <img width="1718" alt="access-card-after" src="https://github.com/user-attachments/assets/5831a7cc-fbad-4d4b-bccc-6e86be6a8c65" />
This commit is contained in:
parent
69474bb7db
commit
040b290fbd
3 changed files with 93 additions and 79 deletions
|
@ -14,12 +14,12 @@ export interface SettingGroupContentProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
const SettingGroupContent: React.FC<SettingGroupContentProps> = ({columns, values, children, className}) => {
|
const SettingGroupContent: React.FC<SettingGroupContentProps> = ({columns, values, children, className}) => {
|
||||||
let styles = 'flex flex-col gap-x-5 gap-y-7';
|
let styles = 'flex flex-col gap-x-5';
|
||||||
if (columns === 2) {
|
if (columns === 2) {
|
||||||
styles = 'grid grid-cols-1 md: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}`;
|
styles += className ? ` ${className}` : ' gap-y-7';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles}>
|
<div className={styles}>
|
||||||
|
|
|
@ -2,8 +2,8 @@ import React from 'react';
|
||||||
import TopLevelGroup from '../../TopLevelGroup';
|
import TopLevelGroup from '../../TopLevelGroup';
|
||||||
import useSettingGroup from '../../../hooks/useSettingGroup';
|
import useSettingGroup from '../../../hooks/useSettingGroup';
|
||||||
import {GroupBase, MultiValue} from 'react-select';
|
import {GroupBase, MultiValue} from 'react-select';
|
||||||
import {MultiSelect, MultiSelectOption, Select, SettingGroupContent, withErrorBoundary} from '@tryghost/admin-x-design-system';
|
import {MultiSelect, MultiSelectOption, Select, Separator, SettingGroupContent, withErrorBoundary} from '@tryghost/admin-x-design-system';
|
||||||
import {getOptionLabel} from '../../../utils/helpers';
|
// import {getOptionLabel} from '../../../utils/helpers';
|
||||||
import {getSettingValues} from '@tryghost/admin-x-framework/api/settings';
|
import {getSettingValues} from '@tryghost/admin-x-framework/api/settings';
|
||||||
import {useBrowseTiers} from '@tryghost/admin-x-framework/api/tiers';
|
import {useBrowseTiers} from '@tryghost/admin-x-framework/api/tiers';
|
||||||
|
|
||||||
|
@ -81,9 +81,9 @@ const Access: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
||||||
'members_signup_access', 'default_content_visibility', 'default_content_visibility_tiers', 'comments_enabled'
|
'members_signup_access', 'default_content_visibility', 'default_content_visibility_tiers', 'comments_enabled'
|
||||||
]) as string[];
|
]) as string[];
|
||||||
|
|
||||||
const membersSignupAccessLabel = getOptionLabel(MEMBERS_SIGNUP_ACCESS_OPTIONS, membersSignupAccess);
|
// const membersSignupAccessLabel = getOptionLabel(MEMBERS_SIGNUP_ACCESS_OPTIONS, membersSignupAccess);
|
||||||
const defaultContentVisibilityLabel = getOptionLabel(DEFAULT_CONTENT_VISIBILITY_OPTIONS, defaultContentVisibility);
|
// const defaultContentVisibilityLabel = getOptionLabel(DEFAULT_CONTENT_VISIBILITY_OPTIONS, defaultContentVisibility);
|
||||||
const commentsEnabledLabel = getOptionLabel(COMMENTS_ENABLED_OPTIONS, commentsEnabled);
|
// const commentsEnabledLabel = getOptionLabel(COMMENTS_ENABLED_OPTIONS, commentsEnabled);
|
||||||
|
|
||||||
const {data: {tiers} = {}} = useBrowseTiers();
|
const {data: {tiers} = {}} = useBrowseTiers();
|
||||||
|
|
||||||
|
@ -106,71 +106,92 @@ const Access: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
||||||
updateSetting('default_content_visibility_tiers', JSON.stringify(selectedTiers));
|
updateSetting('default_content_visibility_tiers', JSON.stringify(selectedTiers));
|
||||||
};
|
};
|
||||||
|
|
||||||
const values = (
|
// const values = (
|
||||||
<SettingGroupContent
|
// <SettingGroupContent
|
||||||
values={[
|
// values={[
|
||||||
{
|
// {
|
||||||
heading: 'Subscription access',
|
// heading: 'Subscription access',
|
||||||
key: 'subscription-access',
|
// key: 'subscription-access',
|
||||||
value: membersSignupAccessLabel
|
// value: membersSignupAccessLabel
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
heading: 'Default post access',
|
// heading: 'Default post access',
|
||||||
key: 'default-post-access',
|
// key: 'default-post-access',
|
||||||
value: defaultContentVisibilityLabel
|
// value: defaultContentVisibilityLabel
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
heading: 'Commenting',
|
// heading: 'Commenting',
|
||||||
key: 'commenting',
|
// key: 'commenting',
|
||||||
value: commentsEnabledLabel
|
// value: commentsEnabledLabel
|
||||||
}
|
// }
|
||||||
]}
|
// ]}
|
||||||
/>
|
// />
|
||||||
);
|
// );
|
||||||
|
|
||||||
const form = (
|
const form = (
|
||||||
<SettingGroupContent columns={1}>
|
<SettingGroupContent className='gap-y-4' columns={1}>
|
||||||
<Select
|
<div className="flex flex-col content-center items-center gap-4 md:flex-row">
|
||||||
hint='Who should be able to subscribe to your site?'
|
<div className="w-full min-w-[160px] max-w-none md:w-2/3 md:max-w-[320px]">Who should be able to subscribe to your site?</div>
|
||||||
options={MEMBERS_SIGNUP_ACCESS_OPTIONS}
|
<div className="w-full md:flex-1">
|
||||||
selectedOption={MEMBERS_SIGNUP_ACCESS_OPTIONS.find(option => option.value === membersSignupAccess)}
|
<Select
|
||||||
testId='subscription-access-select'
|
options={MEMBERS_SIGNUP_ACCESS_OPTIONS}
|
||||||
title="Subscription access"
|
selectedOption={MEMBERS_SIGNUP_ACCESS_OPTIONS.find(option => option.value === membersSignupAccess)}
|
||||||
onSelect={(option) => {
|
testId='subscription-access-select'
|
||||||
updateSetting('members_signup_access', option?.value || null);
|
onSelect={(option) => {
|
||||||
}}
|
updateSetting('members_signup_access', option?.value || null);
|
||||||
/>
|
handleEditingChange(true);
|
||||||
<Select
|
}}
|
||||||
hint='When a new post is created, who should have access?'
|
/>
|
||||||
options={DEFAULT_CONTENT_VISIBILITY_OPTIONS}
|
</div>
|
||||||
selectedOption={DEFAULT_CONTENT_VISIBILITY_OPTIONS.find(option => option.value === defaultContentVisibility)}
|
</div>
|
||||||
testId='default-post-access-select'
|
<Separator />
|
||||||
title="Default post access"
|
<div className="flex flex-col content-center items-center gap-4 md:flex-row">
|
||||||
onSelect={(option) => {
|
<div className="w-full min-w-[160px] max-w-none md:w-2/3 md:max-w-[320px]">Who should have access to new posts?</div>
|
||||||
updateSetting('default_content_visibility', option?.value || null);
|
<div className="w-full md:flex-1">
|
||||||
}}
|
<Select
|
||||||
/>
|
options={DEFAULT_CONTENT_VISIBILITY_OPTIONS}
|
||||||
|
selectedOption={DEFAULT_CONTENT_VISIBILITY_OPTIONS.find(option => option.value === defaultContentVisibility)}
|
||||||
|
testId='default-post-access-select'
|
||||||
|
onSelect={(option) => {
|
||||||
|
updateSetting('default_content_visibility', option?.value || null);
|
||||||
|
handleEditingChange(true);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{defaultContentVisibility === 'tiers' && (
|
{defaultContentVisibility === 'tiers' && (
|
||||||
<MultiSelect
|
<div className="flex flex-col content-center items-center gap-4 md:flex-row">
|
||||||
color='black'
|
<div className="w-full min-w-[160px] max-w-none md:w-2/3 md:max-w-[320px]">Select specific tiers</div>
|
||||||
options={tierOptionGroups.filter(group => group.options.length > 0)}
|
<div className="w-full md:flex-1">
|
||||||
testId='tiers-select'
|
<MultiSelect
|
||||||
title='Select tiers'
|
color='black'
|
||||||
values={selectedTierOptions}
|
options={tierOptionGroups.filter(group => group.options.length > 0)}
|
||||||
clearBg
|
testId='tiers-select'
|
||||||
onChange={setSelectedTiers}
|
values={selectedTierOptions}
|
||||||
/>
|
onChange={(selectedOptions) => {
|
||||||
|
setSelectedTiers(selectedOptions);
|
||||||
|
handleEditingChange(true);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
<Select
|
<Separator />
|
||||||
hint='Who can comment on posts?'
|
<div className="flex flex-col content-center items-center gap-4 md:flex-row">
|
||||||
options={COMMENTS_ENABLED_OPTIONS}
|
<div className="w-full min-w-[160px] max-w-none md:w-2/3 md:max-w-[320px]">Who can comment on posts?</div>
|
||||||
selectedOption={COMMENTS_ENABLED_OPTIONS.find(option => option.value === commentsEnabled)}
|
<div className="w-full md:flex-1">
|
||||||
testId='commenting-select'
|
<Select
|
||||||
title="Commenting"
|
options={COMMENTS_ENABLED_OPTIONS}
|
||||||
onSelect={(option) => {
|
selectedOption={COMMENTS_ENABLED_OPTIONS.find(option => option.value === commentsEnabled)}
|
||||||
updateSetting('comments_enabled', option?.value || null);
|
testId='commenting-select'
|
||||||
}}
|
title=""
|
||||||
/>
|
onSelect={(option) => {
|
||||||
|
updateSetting('comments_enabled', option?.value || null);
|
||||||
|
handleEditingChange(true);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</SettingGroupContent>
|
</SettingGroupContent>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -183,11 +204,12 @@ const Access: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
||||||
saveState={saveState}
|
saveState={saveState}
|
||||||
testId='access'
|
testId='access'
|
||||||
title='Access'
|
title='Access'
|
||||||
|
hideEditButton
|
||||||
onCancel={handleCancel}
|
onCancel={handleCancel}
|
||||||
onEditingChange={handleEditingChange}
|
onEditingChange={handleEditingChange}
|
||||||
onSave={handleSave}
|
onSave={handleSave}
|
||||||
>
|
>
|
||||||
{isEditing ? form : values}
|
{form}
|
||||||
</TopLevelGroup>
|
</TopLevelGroup>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -21,16 +21,12 @@ test.describe('Access settings', async () => {
|
||||||
await expect(section.getByText('Public')).toHaveCount(1);
|
await expect(section.getByText('Public')).toHaveCount(1);
|
||||||
await expect(section.getByText('Nobody')).toHaveCount(1);
|
await expect(section.getByText('Nobody')).toHaveCount(1);
|
||||||
|
|
||||||
await section.getByRole('button', {name: 'Edit'}).click();
|
|
||||||
|
|
||||||
await chooseOptionInSelect(section.getByTestId('subscription-access-select'), 'Only people I invite');
|
await chooseOptionInSelect(section.getByTestId('subscription-access-select'), 'Only people I invite');
|
||||||
await chooseOptionInSelect(section.getByTestId('default-post-access-select'), /^Members only$/);
|
await chooseOptionInSelect(section.getByTestId('default-post-access-select'), /^Members only$/);
|
||||||
await chooseOptionInSelect(section.getByTestId('commenting-select'), 'All members');
|
await chooseOptionInSelect(section.getByTestId('commenting-select'), 'All members');
|
||||||
|
|
||||||
await section.getByRole('button', {name: 'Save'}).click();
|
await section.getByRole('button', {name: 'Save'}).click();
|
||||||
|
|
||||||
await expect(section.getByTestId('subscription-access-select')).toHaveCount(0);
|
|
||||||
|
|
||||||
await expect(section.getByText('Only people I invite')).toHaveCount(1);
|
await expect(section.getByText('Only people I invite')).toHaveCount(1);
|
||||||
await expect(section.getByText('Members only')).toHaveCount(1);
|
await expect(section.getByText('Members only')).toHaveCount(1);
|
||||||
await expect(section.getByText('All members')).toHaveCount(1);
|
await expect(section.getByText('All members')).toHaveCount(1);
|
||||||
|
@ -56,8 +52,6 @@ test.describe('Access settings', async () => {
|
||||||
|
|
||||||
const section = page.getByTestId('access');
|
const section = page.getByTestId('access');
|
||||||
|
|
||||||
await section.getByRole('button', {name: 'Edit'}).click();
|
|
||||||
|
|
||||||
await chooseOptionInSelect(section.getByTestId('subscription-access-select'), 'Nobody');
|
await chooseOptionInSelect(section.getByTestId('subscription-access-select'), 'Nobody');
|
||||||
|
|
||||||
await section.getByRole('button', {name: 'Save'}).click();
|
await section.getByRole('button', {name: 'Save'}).click();
|
||||||
|
@ -68,7 +62,7 @@ test.describe('Access settings', async () => {
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
await expect(section.getByTestId('subscription-access-select')).toHaveCount(0);
|
await expect(section.getByTestId('subscription-access-select')).toContainText('Nobody');
|
||||||
|
|
||||||
await expect(page.getByTestId('portal').getByRole('button', {name: 'Customize'})).toBeDisabled();
|
await expect(page.getByTestId('portal').getByRole('button', {name: 'Customize'})).toBeDisabled();
|
||||||
await expect(page.getByTestId('enable-newsletters')).toContainText('only existing members will receive newsletters');
|
await expect(page.getByTestId('enable-newsletters')).toContainText('only existing members will receive newsletters');
|
||||||
|
@ -88,8 +82,6 @@ test.describe('Access settings', async () => {
|
||||||
|
|
||||||
const section = page.getByTestId('access');
|
const section = page.getByTestId('access');
|
||||||
|
|
||||||
await section.getByRole('button', {name: 'Edit'}).click();
|
|
||||||
|
|
||||||
await chooseOptionInSelect(section.getByTestId('default-post-access-select'), 'Specific tiers');
|
await chooseOptionInSelect(section.getByTestId('default-post-access-select'), 'Specific tiers');
|
||||||
await section.getByTestId('tiers-select').click();
|
await section.getByTestId('tiers-select').click();
|
||||||
|
|
||||||
|
@ -98,7 +90,7 @@ test.describe('Access settings', async () => {
|
||||||
|
|
||||||
await section.getByRole('button', {name: 'Save'}).click();
|
await section.getByRole('button', {name: 'Save'}).click();
|
||||||
|
|
||||||
await expect(section.getByText('Specific tiers')).toHaveCount(1);
|
await expect(section.getByTestId('default-post-access-select')).toContainText('Specific tiers');
|
||||||
|
|
||||||
expect(lastApiRequests.editSettings?.body).toEqual({
|
expect(lastApiRequests.editSettings?.body).toEqual({
|
||||||
settings: [
|
settings: [
|
||||||
|
|
Loading…
Reference in a new issue