mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-24 06:35:49 +03:00
Added tier and label selection in default recipients
refs https://github.com/TryGhost/Team/issues/3151
This commit is contained in:
parent
c0e638fecd
commit
731858e9e0
@ -1,17 +1,18 @@
|
||||
import Heading from './Heading';
|
||||
import Hint from './Hint';
|
||||
import React from 'react';
|
||||
import {MultiValue, default as ReactSelect, components} from 'react-select';
|
||||
import {GroupBase, MultiValue, OptionsOrGroups, default as ReactSelect, components} from 'react-select';
|
||||
|
||||
export type MultiSelectColor = 'grey' | 'black' | string;
|
||||
export type MultiSelectColor = 'grey' | 'black' | 'green' | 'pink';
|
||||
|
||||
export type MultiSelectOption = {
|
||||
value: string;
|
||||
label: string;
|
||||
color?: MultiSelectColor;
|
||||
}
|
||||
|
||||
interface MultiSelectProps {
|
||||
options: MultiSelectOption[];
|
||||
options: OptionsOrGroups<MultiSelectOption, GroupBase<MultiSelectOption>>;
|
||||
defaultValues?: MultiSelectOption[];
|
||||
title?: string;
|
||||
clearBg?: boolean;
|
||||
@ -22,6 +23,21 @@ interface MultiSelectProps {
|
||||
onChange: (selected: MultiValue<MultiSelectOption>) => void
|
||||
}
|
||||
|
||||
const multiValueColor = (color?: MultiSelectColor) => {
|
||||
switch (color) {
|
||||
case 'black':
|
||||
return 'bg-black text-white';
|
||||
case 'grey':
|
||||
return 'bg-grey-300 text-black';
|
||||
case 'green':
|
||||
return 'bg-green-500 text-white';
|
||||
case 'pink':
|
||||
return 'bg-pink-500 text-white';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
const MultiSelect: React.FC<MultiSelectProps> = ({
|
||||
title = '',
|
||||
clearBg = false,
|
||||
@ -34,27 +50,15 @@ const MultiSelect: React.FC<MultiSelectProps> = ({
|
||||
onChange,
|
||||
...props
|
||||
}) => {
|
||||
let multiValueColor;
|
||||
switch (color) {
|
||||
case 'black':
|
||||
multiValueColor = 'bg-black text-white';
|
||||
break;
|
||||
case 'grey':
|
||||
multiValueColor = 'bg-grey-300 text-black';
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
const customClasses = {
|
||||
control: `w-full cursor-pointer appearance-none min-h-[40px] border-b ${!clearBg && 'bg-grey-75 px-[10px]'} py-2 outline-none ${error ? 'border-red' : 'border-grey-500 hover:border-grey-700'} ${(title && !clearBg) && 'mt-2'}`,
|
||||
valueContainer: 'gap-1',
|
||||
placeHolder: 'text-grey-600',
|
||||
menu: 'shadow py-2 rounded-b z-50 bg-white',
|
||||
option: 'hover:cursor-pointer hover:bg-grey-100 px-3 py-[6px]',
|
||||
multiValue: `rounded-sm items-center text-[14px] py-px pl-2 pr-1 gap-1.5 ${multiValueColor}`,
|
||||
noOptionsMessage: 'p-3 text-grey-600'
|
||||
multiValue: (optionColor?: MultiSelectColor) => `rounded-sm items-center text-[14px] py-px pl-2 pr-1 gap-1.5 ${multiValueColor(optionColor || color)}`,
|
||||
noOptionsMessage: 'p-3 text-grey-600',
|
||||
groupHeading: 'py-[6px] px-3 text-2xs font-semibold uppercase tracking-wide text-grey-700'
|
||||
};
|
||||
|
||||
const DropdownIndicator: React.FC<any> = ddiProps => (
|
||||
@ -74,9 +78,11 @@ const MultiSelect: React.FC<MultiSelectProps> = ({
|
||||
placeholder: () => customClasses.placeHolder,
|
||||
menu: () => customClasses.menu,
|
||||
option: () => customClasses.option,
|
||||
multiValue: () => customClasses.multiValue,
|
||||
noOptionsMessage: () => customClasses.noOptionsMessage
|
||||
multiValue: ({data}) => customClasses.multiValue(data.color),
|
||||
noOptionsMessage: () => customClasses.noOptionsMessage,
|
||||
groupHeading: () => customClasses.groupHeading
|
||||
}}
|
||||
closeMenuOnSelect={false}
|
||||
components={{DropdownIndicator}}
|
||||
defaultValue={defaultValues}
|
||||
isClearable={false}
|
||||
@ -92,4 +98,4 @@ const MultiSelect: React.FC<MultiSelectProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default MultiSelect;
|
||||
export default MultiSelect;
|
||||
|
@ -1,10 +1,12 @@
|
||||
import MultiSelect, {MultiSelectOption} from '../../../admin-x-ds/global/MultiSelect';
|
||||
import React from 'react';
|
||||
import React, {useContext, useEffect, useState} from 'react';
|
||||
import Select from '../../../admin-x-ds/global/Select';
|
||||
import SettingGroup from '../../../admin-x-ds/settings/SettingGroup';
|
||||
import SettingGroupContent from '../../../admin-x-ds/settings/SettingGroupContent';
|
||||
import useSettingGroup from '../../../hooks/useSettingGroup';
|
||||
import {MultiValue} from 'react-select';
|
||||
import {GroupBase, MultiValue} from 'react-select';
|
||||
import {Label, Tier} from '../../../types/api';
|
||||
import {ServicesContext} from '../../providers/ServiceProvider';
|
||||
import {getOptionLabel, getSettingValues} from '../../../utils/helpers';
|
||||
|
||||
type RefipientValueArgs = {
|
||||
@ -29,6 +31,16 @@ const RECIPIENT_FILTER_OPTIONS = [{
|
||||
value: 'none'
|
||||
}];
|
||||
|
||||
const SIMPLE_SEGMENT_OPTIONS: MultiSelectOption[] = [{
|
||||
label: 'Free members',
|
||||
value: 'status:free',
|
||||
color: 'green'
|
||||
}, {
|
||||
label: 'Paid members',
|
||||
value: 'status:-free',
|
||||
color: 'pink'
|
||||
}];
|
||||
|
||||
function getDefaultRecipientValue({
|
||||
defaultEmailRecipients,
|
||||
defaultEmailRecipientsFilter
|
||||
@ -63,6 +75,25 @@ const DefaultRecipients: React.FC = () => {
|
||||
'editor_default_email_recipients', 'editor_default_email_recipients_filter'
|
||||
]) as [string, string|null];
|
||||
|
||||
const [selectedOption, setSelectedOption] = useState(getDefaultRecipientValue({
|
||||
defaultEmailRecipients,
|
||||
defaultEmailRecipientsFilter
|
||||
}));
|
||||
|
||||
const {api} = useContext(ServicesContext);
|
||||
const [tiers, setTiers] = useState<Tier[]>([]);
|
||||
const [labels, setLabels] = useState<Label[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
api.tiers.browse().then((response) => {
|
||||
setTiers(response.tiers);
|
||||
});
|
||||
|
||||
api.labels.browse().then((response) => {
|
||||
setLabels(response.labels);
|
||||
});
|
||||
}, [api]);
|
||||
|
||||
const setDefaultRecipientValue = (value: string) => {
|
||||
if (['visibility', 'disabled'].includes(value)) {
|
||||
updateSetting('editor_default_email_recipients', value);
|
||||
@ -82,12 +113,34 @@ const DefaultRecipients: React.FC = () => {
|
||||
if (value === 'none') {
|
||||
updateSetting('editor_default_email_recipients_filter', null);
|
||||
}
|
||||
|
||||
setSelectedOption(value);
|
||||
};
|
||||
|
||||
const emailRecipientValue = getDefaultRecipientValue({
|
||||
defaultEmailRecipients,
|
||||
defaultEmailRecipientsFilter
|
||||
});
|
||||
const segmentOptionGroups: GroupBase<MultiSelectOption>[] = [
|
||||
{
|
||||
options: SIMPLE_SEGMENT_OPTIONS
|
||||
},
|
||||
{
|
||||
label: 'Active Tiers',
|
||||
options: tiers.map(tier => ({value: tier.id, label: tier.name, color: 'black'}))
|
||||
},
|
||||
{
|
||||
label: 'Labels',
|
||||
options: labels.map(label => ({value: `label:${label.slug}`, label: label.name, color: 'grey'}))
|
||||
}
|
||||
];
|
||||
|
||||
const segmentOptions = segmentOptionGroups.flatMap(({options}) => options);
|
||||
|
||||
const defaultSelectedSegments = (defaultEmailRecipientsFilter?.split(',') || [])
|
||||
.map(value => segmentOptions.find(option => option.value === value))
|
||||
.filter((option): option is MultiSelectOption => Boolean(option));
|
||||
|
||||
const setSelectedSegments = (selected: MultiValue<MultiSelectOption>) => {
|
||||
const selectedGroups = selected?.map(({value}) => value).join(',');
|
||||
updateSetting('editor_default_email_recipients_filter', selectedGroups);
|
||||
};
|
||||
|
||||
const values = (
|
||||
<SettingGroupContent
|
||||
@ -95,7 +148,7 @@ const DefaultRecipients: React.FC = () => {
|
||||
{
|
||||
heading: 'Default Newsletter recipients',
|
||||
key: 'default-recipients',
|
||||
value: getOptionLabel(RECIPIENT_FILTER_OPTIONS, emailRecipientValue)
|
||||
value: getOptionLabel(RECIPIENT_FILTER_OPTIONS, selectedOption)
|
||||
}
|
||||
]}
|
||||
/>
|
||||
@ -104,7 +157,7 @@ const DefaultRecipients: React.FC = () => {
|
||||
const form = (
|
||||
<SettingGroupContent columns={1}>
|
||||
<Select
|
||||
defaultSelectedOption={emailRecipientValue}
|
||||
defaultSelectedOption={selectedOption}
|
||||
hint='Who should be able to subscribe to your site?'
|
||||
options={RECIPIENT_FILTER_OPTIONS}
|
||||
title="Default Newsletter recipients"
|
||||
@ -112,23 +165,13 @@ const DefaultRecipients: React.FC = () => {
|
||||
setDefaultRecipientValue(value);
|
||||
}}
|
||||
/>
|
||||
{(emailRecipientValue === 'segment') && (
|
||||
{(selectedOption === 'segment') && (
|
||||
<MultiSelect
|
||||
defaultValues={[
|
||||
{value: 'option2', label: 'Fake tier 2'}
|
||||
]}
|
||||
options={[
|
||||
{value: 'option1', label: 'Fake tier 1'},
|
||||
{value: 'option2', label: 'Fake tier 2'},
|
||||
{value: 'option3', label: 'Fake tier 3'}
|
||||
]}
|
||||
defaultValues={defaultSelectedSegments}
|
||||
options={segmentOptionGroups}
|
||||
title='Select tiers'
|
||||
clearBg
|
||||
onChange={(selected: MultiValue<MultiSelectOption>) => {
|
||||
selected?.map(o => (
|
||||
alert(`${o.label} (${o.value})`)
|
||||
));
|
||||
}}
|
||||
onChange={setSelectedSegments}
|
||||
/>
|
||||
)}
|
||||
</SettingGroupContent>
|
||||
|
@ -61,6 +61,32 @@ export type Post = {
|
||||
url: string;
|
||||
};
|
||||
|
||||
export type Tier = {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string | null;
|
||||
slug: string;
|
||||
active: true,
|
||||
type: string;
|
||||
welcome_page_url: string | null;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
visibility: string;
|
||||
benefits: string[];
|
||||
currency?: string;
|
||||
monthly_price?: number;
|
||||
yearly_price?: number;
|
||||
trial_days: number;
|
||||
}
|
||||
|
||||
export type Label = {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
type CustomThemeSettingData =
|
||||
{ type: 'text', value: string | null, default: string | null } |
|
||||
{ type: 'color', value: string, default: string } |
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {CustomThemeSetting, Post, Setting, SiteData, User, UserRole} from '../types/api';
|
||||
import {CustomThemeSetting, Label, Post, Setting, SiteData, Tier, User, UserRole} from '../types/api';
|
||||
import {getGhostPaths} from './helpers';
|
||||
|
||||
interface Meta {
|
||||
@ -54,19 +54,20 @@ export interface CustomThemeSettingsResponseType {
|
||||
}
|
||||
|
||||
export interface PostsResponseType {
|
||||
meta: {
|
||||
pagination: {
|
||||
page: number
|
||||
limit: number
|
||||
pages: number
|
||||
total: number
|
||||
next: number | null
|
||||
prev: number | null
|
||||
}
|
||||
}
|
||||
meta?: Meta
|
||||
posts: Post[];
|
||||
}
|
||||
|
||||
export interface TiersResponseType {
|
||||
meta?: Meta
|
||||
tiers: Tier[]
|
||||
}
|
||||
|
||||
export interface LabelsResponseType {
|
||||
meta?: Meta
|
||||
labels: Label[]
|
||||
}
|
||||
|
||||
export interface SiteResponseType {
|
||||
site: SiteData;
|
||||
}
|
||||
@ -144,6 +145,12 @@ interface API {
|
||||
};
|
||||
latestPost: {
|
||||
browse: () => Promise<PostsResponseType>
|
||||
};
|
||||
tiers: {
|
||||
browse: () => Promise<TiersResponseType>
|
||||
};
|
||||
labels: {
|
||||
browse: () => Promise<LabelsResponseType>
|
||||
}
|
||||
}
|
||||
|
||||
@ -349,6 +356,21 @@ function setupGhostApi({ghostVersion}: GhostApiOptions): API {
|
||||
const data: PostsResponseType = await response.json();
|
||||
return data;
|
||||
}
|
||||
},
|
||||
tiers: {
|
||||
browse: async () => {
|
||||
const filter = encodeURIComponent('type:paid+active:true');
|
||||
const response = await fetcher(`/tiers/?filter=${filter}&limit=all`);
|
||||
const data: TiersResponseType = await response.json();
|
||||
return data;
|
||||
}
|
||||
},
|
||||
labels: {
|
||||
browse: async () => {
|
||||
const response = await fetcher(`/labels/?limit=all`);
|
||||
const data: LabelsResponseType = await response.json();
|
||||
return data;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit ba0b3d08cca796d29dc3c96f25f6420557059591
|
||||
Subproject commit 23f7c303657eb413c0cbe296fffb88647f91a258
|
Loading…
Reference in New Issue
Block a user