Refactor form into two separate files

[MAILPOET-3231]
This commit is contained in:
Pavel Dohnal
2021-05-26 13:45:47 +02:00
committed by Veljko V
parent ecf149d4f3
commit bc8c43b0f8
4 changed files with 281 additions and 235 deletions

View File

@@ -0,0 +1,182 @@
import React, { useEffect, useState } from 'react';
import {
assign,
compose,
has,
prop,
} from 'lodash/fp';
import { useRouteMatch, Link, useHistory } from 'react-router-dom';
import MailPoet from 'mailpoet';
import Background from 'common/background/background';
import Heading from 'common/typography/heading/heading';
import HideScreenOptions from 'common/hide_screen_options/hide_screen_options';
import { EmailSegmentOptions } from './dynamic_segments_filters/email';
import { WooCommerceOptions } from './dynamic_segments_filters/woocommerce';
import { SubscriberSegmentOptions } from './dynamic_segments_filters/subscriber';
import { WooCommerceSubscriptionOptions } from './dynamic_segments_filters/woocommerce_subscription';
import { SegmentFormData } from './segment_form_data';
import { Form } from './form';
import {
AnyFormItem,
FilterValue,
GroupFilterValue,
SubscriberActionTypes,
} from './types';
import APIErrorsNotice from '../../notices/api_errors_notice';
const messages = {
onUpdate: (): void => {
MailPoet.Notice.success(MailPoet.I18n.t('dynamicSegmentUpdated'));
},
onCreate: (data): void => {
MailPoet.Notice.success(MailPoet.I18n.t('dynamicSegmentAdded'));
MailPoet.trackEvent('Segments > Add new', {
'MailPoet Free version': MailPoet.version,
type: data.segmentType || 'unknown type',
subtype: data.action || data.wordpressRole || 'unknown subtype',
});
},
};
function getAvailableFilters(): GroupFilterValue[] {
const filters: GroupFilterValue[] = [
{
label: MailPoet.I18n.t('email'),
options: EmailSegmentOptions,
},
{
label: MailPoet.I18n.t('wpUserRole'),
options: SubscriberSegmentOptions,
},
];
if (MailPoet.isWoocommerceActive) {
filters.push({
label: MailPoet.I18n.t('woocommerce'),
options: WooCommerceOptions,
});
}
if (MailPoet.isWoocommerceActive && SegmentFormData.canUseWooSubscriptions) {
filters.push({
label: MailPoet.I18n.t('woocommerceSubscriptions'),
options: WooCommerceSubscriptionOptions,
});
}
return filters;
}
const DynamicSegmentForm: React.FunctionComponent = () => {
const [segmentFilters] = useState(getAvailableFilters());
const [errors, setErrors] = useState([]);
const [segmentType, setSegmentType] = useState<FilterValue | undefined>(undefined);
const [item, setItem] = useState<AnyFormItem>({});
const match = useRouteMatch<{id: string}>();
const history = useHistory();
useEffect(() => {
function findSegmentType(itemSearch): FilterValue | undefined {
let found: FilterValue | undefined;
if (itemSearch.action === undefined) {
// bc compatibility, the wordpress user role segment doesn't have action
return SubscriberSegmentOptions.find(
(value) => value.value === SubscriberActionTypes.WORDPRESS_ROLE
);
}
segmentFilters.forEach((filter: GroupFilterValue) => {
filter.options.forEach((option: FilterValue) => {
if (option.group === itemSearch.segmentType) {
if (itemSearch.action === option.value) {
found = option;
}
}
});
});
return found;
}
function convertSavedData(data: {
[key: string]: string | number;
}): AnyFormItem {
let converted: AnyFormItem = JSON.parse(JSON.stringify(data));
// for compatibility with older data
if (has('link_id', data)) converted = assign(converted, { link_id: data.link_id.toString() });
if (has('newsletter_id', data)) converted = assign(converted, { newsletter_id: data.newsletter_id.toString() });
if (has('product_id', data)) converted = assign(converted, { product_id: data.product_id.toString() });
if (has('category_id', data)) converted = assign(converted, { category_id: data.category_id.toString() });
return converted;
}
function loadSegment(segmentId): void {
MailPoet.Ajax.post({
api_version: MailPoet.apiVersion,
endpoint: 'dynamic_segments',
action: 'get',
data: {
id: segmentId,
},
})
.done((response) => {
if (response.data.is_plugin_missing) {
history.push('/segments');
} else {
setItem(convertSavedData(response.data));
setSegmentType(findSegmentType(response.data));
}
})
.fail(() => {
history.push('/segments');
});
}
if (match.params.id !== undefined) {
loadSegment(match.params.id);
}
}, [segmentFilters, match.params.id, history]);
function handleSave(e: Event): void {
e.preventDefault();
setErrors([]);
MailPoet.Ajax.post({
api_version: MailPoet.apiVersion,
endpoint: 'dynamic_segments',
action: 'save',
data: item,
}).done(() => {
history.push('/segments');
if (match.params.id !== undefined) {
messages.onUpdate();
} else {
messages.onCreate(item);
}
}).fail(compose([setErrors, prop('errors')]));
}
return (
<>
<Background color="#fff" />
<HideScreenOptions />
{(errors.length > 0 && (
<APIErrorsNotice errors={errors} />
))}
<Heading level={1} className="mailpoet-title">
<span>{MailPoet.I18n.t('formPageTitle')}</span>
<Link className="mailpoet-button mailpoet-button-small" to="/segments">{MailPoet.I18n.t('backToList')}</Link>
</Heading>
<Form
onSave={handleSave}
segmentType={segmentType}
item={item}
onItemChange={setItem}
onSegmentTypeChange={setSegmentType}
segmentFilters={segmentFilters}
/>
</>
);
};
export default DynamicSegmentForm;

View File

@@ -1,258 +1,117 @@
import React, { useEffect, useState } from 'react';
import React from 'react';
import {
assign,
compose,
has,
prop,
} from 'lodash/fp';
import { useRouteMatch, Link, useHistory } from 'react-router-dom';
import MailPoet from 'mailpoet';
import Background from 'common/background/background';
import Button from 'common/button/button';
import Heading from 'common/typography/heading/heading';
import Input from 'common/form/input/input';
import HideScreenOptions from 'common/hide_screen_options/hide_screen_options';
import Select from 'common/form/react_select/react_select';
import Textarea from 'common/form/textarea/textarea';
import { EmailSegmentOptions } from './dynamic_segments_filters/email';
import { WooCommerceOptions } from './dynamic_segments_filters/woocommerce';
import { SubscriberSegmentOptions } from './dynamic_segments_filters/subscriber';
import { WooCommerceSubscriptionOptions } from './dynamic_segments_filters/woocommerce_subscription';
import { SubscribersCounter } from './subscribers_counter';
import { FormFilterFields } from './form_filter_fields';
import { SegmentFormData } from './segment_form_data';
import { isFormValid } from './validator';
import {
AnyFormItem,
FilterValue,
SubscriberActionTypes,
GroupFilterValue,
} from './types';
import APIErrorsNotice from '../../notices/api_errors_notice';
const messages = {
onUpdate: (): void => MailPoet.Notice.success(MailPoet.I18n.t('dynamicSegmentUpdated')),
onCreate: (data): void => {
MailPoet.Notice.success(MailPoet.I18n.t('dynamicSegmentAdded'));
MailPoet.trackEvent('Segments > Add new', {
'MailPoet Free version': MailPoet.version,
type: data.segmentType || 'unknown type',
subtype: data.action || data.wordpressRole || 'unknown subtype',
});
},
};
type GroupFilterValue = {
label: string;
options: FilterValue[];
interface Props {
onSave: (Event) => void;
item: AnyFormItem;
segmentType: FilterValue | undefined;
onItemChange: (AnyFormItem) => void;
onSegmentTypeChange: (FilterValue) => void;
segmentFilters: GroupFilterValue[];
}
function getAvailableFilters(): GroupFilterValue[] {
const filters: GroupFilterValue[] = [
{
label: MailPoet.I18n.t('email'),
options: EmailSegmentOptions,
},
{
label: MailPoet.I18n.t('wpUserRole'),
options: SubscriberSegmentOptions,
},
];
if (MailPoet.isWoocommerceActive) {
filters.push({
label: MailPoet.I18n.t('woocommerce'),
options: WooCommerceOptions,
});
}
if (MailPoet.isWoocommerceActive && SegmentFormData.canUseWooSubscriptions) {
filters.push({
label: MailPoet.I18n.t('woocommerceSubscriptions'),
options: WooCommerceSubscriptionOptions,
});
}
return filters;
}
const DynamicSegmentForm: React.FunctionComponent = () => {
const [segmentFilters] = useState(getAvailableFilters());
const [errors, setErrors] = useState([]);
const [segmentType, setSegmentType] = useState<FilterValue | undefined>(undefined);
const [item, setItem] = useState<AnyFormItem>({});
const match = useRouteMatch<{id: string}>();
const history = useHistory();
useEffect(() => {
function findSegmentType(itemSearch): FilterValue | undefined {
let found;
if (itemSearch.action === undefined) {
// bc compatibility, the wordpress user role segment doesn't have action
return SubscriberSegmentOptions.find(
(value) => value.value === SubscriberActionTypes.WORDPRESS_ROLE
);
}
segmentFilters.forEach((filter) => {
filter.options.forEach((option) => {
if (option.group === itemSearch.segmentType) {
if (itemSearch.action === option.value) {
found = option;
}
}
});
});
return found;
}
function convertSavedData(data: {
[key: string]: string | number;
}): AnyFormItem {
const converted = JSON.parse(JSON.stringify(data));
// for compatibility with older data
if (has('link_id', data)) converted.link_id = data.link_id.toString();
if (has('newsletter_id', data)) converted.newsletter_id = data.newsletter_id.toString();
if (has('product_id', data)) converted.product_id = data.product_id.toString();
if (has('category_id', data)) converted.category_id = data.category_id.toString();
return converted;
}
function loadSegment(segmentId): void {
MailPoet.Ajax.post({
api_version: MailPoet.apiVersion,
endpoint: 'dynamic_segments',
action: 'get',
data: {
id: segmentId,
},
})
.done((response) => {
if (response.data.is_plugin_missing) {
history.push('/segments');
} else {
setItem(convertSavedData(response.data));
setSegmentType(findSegmentType(response.data));
}
})
.fail(() => {
history.push('/segments');
});
}
if (match.params.id !== undefined) {
loadSegment(match.params.id);
}
}, [segmentFilters, match.params.id, history]);
function handleSave(e): void {
e.preventDefault();
setErrors([]);
MailPoet.Ajax.post({
api_version: MailPoet.apiVersion,
endpoint: 'dynamic_segments',
action: 'save',
data: item,
}).done(() => {
history.push('/segments');
if (match.params.id !== undefined) {
messages.onUpdate();
} else {
messages.onCreate(item);
}
}).fail(compose([setErrors, prop('errors')]));
}
return (
<>
<Background color="#fff" />
<HideScreenOptions />
{(errors.length > 0 && (
<APIErrorsNotice errors={errors} />
))}
<Heading level={1} className="mailpoet-title">
<span>{MailPoet.I18n.t('formPageTitle')}</span>
<Link className="mailpoet-button mailpoet-button-small" to="/segments">{MailPoet.I18n.t('backToList')}</Link>
</Heading>
<form className="mailpoet_form">
<div className="mailpoet-form-grid">
<div className="mailpoet-form-field-name form-field-row-name">
<Heading level={4}>
<label htmlFor="field_name">
{MailPoet.I18n.t('name')}
</label>
</Heading>
<div className="mailpoet-form-field">
<Input
type="text"
name="name"
id="field_name"
defaultValue={item.name}
onChange={
(e): void => setItem(assign(item, { name: e.target.value }))
}
/>
</div>
</div>
<div className="mailpoet-form-field-description form-field-row-description">
<Heading level={4}>
<label htmlFor="field_description">
{MailPoet.I18n.t('description')}
</label>
</Heading>
<p className="mailpoet-form-description">
{MailPoet.I18n.t('segmentDescriptionTip')}
</p>
<div className="mailpoet-form-field">
<Textarea
name="description"
id="field_description"
defaultValue={item.description}
onChange={
(e): void => setItem(assign(item, { description: e.target.value }))
}
/>
</div>
</div>
<div>
<Heading level={4}>
<label htmlFor="field_filters">
{MailPoet.I18n.t('formPageTitle')}
</label>
</Heading>
<Select
placeholder={MailPoet.I18n.t('selectActionPlaceholder')}
options={segmentFilters}
value={segmentType}
onChange={(newValue: FilterValue): void => {
setItem(assign(item, {
segmentType: newValue.group,
action: newValue.value,
}));
setSegmentType(newValue);
}}
automationId="select-segment-action"
isFullWidth
export const Form: React.FunctionComponent<Props> = ({
onSave,
item,
segmentType,
onItemChange,
onSegmentTypeChange,
segmentFilters,
}) => (
<>
<form className="mailpoet_form">
<div className="mailpoet-form-grid">
<div className="mailpoet-form-field-name form-field-row-name">
<Heading level={4}>
<label htmlFor="field_name">
{MailPoet.I18n.t('name')}
</label>
</Heading>
<div className="mailpoet-form-field">
<Input
type="text"
name="name"
id="field_name"
defaultValue={item.name}
onChange={
(e): void => onItemChange(assign(item, { name: e.target.value }))
}
/>
{segmentType !== undefined && (
<FormFilterFields
segmentType={segmentType}
updateItem={setItem}
item={item}
/>
)}
</div>
<SubscribersCounter item={item} />
<div className="mailpoet-form-actions">
<Button type="submit" onClick={handleSave} isDisabled={!isFormValid(item)}>
{MailPoet.I18n.t('save')}
</Button>
</div>
</div>
</form>
</>
);
};
export default DynamicSegmentForm;
<div className="mailpoet-form-field-description form-field-row-description">
<Heading level={4}>
<label htmlFor="field_description">
{MailPoet.I18n.t('description')}
</label>
</Heading>
<p className="mailpoet-form-description">
{MailPoet.I18n.t('segmentDescriptionTip')}
</p>
<div className="mailpoet-form-field">
<Textarea
name="description"
id="field_description"
defaultValue={item.description}
onChange={
(e): void => onItemChange(assign(item, { description: e.target.value }))
}
/>
</div>
</div>
<div>
<Heading level={4}>
<label htmlFor="field_filters">
{MailPoet.I18n.t('formPageTitle')}
</label>
</Heading>
<Select
placeholder={MailPoet.I18n.t('selectActionPlaceholder')}
options={segmentFilters}
value={segmentType}
onChange={(newValue: FilterValue): void => {
onItemChange(assign(item, {
segmentType: newValue.group,
action: newValue.value,
}));
onSegmentTypeChange(newValue);
}}
automationId="select-segment-action"
isFullWidth
/>
{segmentType !== undefined && (
<FormFilterFields
segmentType={segmentType}
updateItem={onItemChange}
item={item}
/>
)}
</div>
<SubscribersCounter item={item} />
<div className="mailpoet-form-actions">
<Button type="submit" onClick={onSave} isDisabled={!isFormValid(item)}>
{MailPoet.I18n.t('save')}
</Button>
</div>
</div>
</form>
</>
);

View File

@@ -21,6 +21,11 @@ export enum SubscriberActionTypes {
SUBSCRIBED_DATE = 'subscribedDate',
}
export type GroupFilterValue = {
label: string;
options: FilterValue[];
}
export interface SelectOption {
value: string;
label: string;

View File

@@ -11,7 +11,7 @@ import SegmentList from 'segments/list.jsx';
import SegmentForm from 'segments/form.jsx';
import { GlobalContext, useGlobalContextValue } from 'context/index.jsx';
import Notices from 'notices/notices.jsx';
import DynamicSegmentForm from './dynamic/form';
import DynamicSegmentForm from './dynamic/dynamic_segments_form';
import DynamicSegmentList from './dynamic/list.jsx';
import ListHeading from './heading';