Refactor used coupon filter to load coupons via API
[MAILPOET-5123]
This commit is contained in:
@ -1,10 +1,12 @@
|
|||||||
import { useDispatch, useSelect } from '@wordpress/data';
|
import { useDispatch, useSelect } from '@wordpress/data';
|
||||||
import { useEffect } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
import { __ } from '@wordpress/i18n';
|
||||||
import { Grid } from 'common/grid';
|
import { Grid } from 'common/grid';
|
||||||
import { Select } from 'common';
|
import { Select } from 'common';
|
||||||
import { ReactSelect } from 'common/form/react_select/react_select';
|
import { ReactSelect } from 'common/form/react_select/react_select';
|
||||||
import { MailPoet } from 'mailpoet';
|
import { MailPoet } from 'mailpoet';
|
||||||
import { filter } from 'lodash/fp';
|
import { filter, uniqBy } from 'lodash/fp';
|
||||||
|
import { APIErrorsNotice } from '../../../../../notices/api_errors_notice';
|
||||||
import {
|
import {
|
||||||
WooCommerceFormItem,
|
WooCommerceFormItem,
|
||||||
FilterProps,
|
FilterProps,
|
||||||
@ -36,6 +38,106 @@ export function UsedCouponCodeFields({
|
|||||||
[filterIndex],
|
[filterIndex],
|
||||||
);
|
);
|
||||||
const { updateSegmentFilter } = useDispatch(storeName);
|
const { updateSegmentFilter } = useDispatch(storeName);
|
||||||
|
// isInitialized is used for displaying loading message before loading coupons
|
||||||
|
const [isInitialized, setIsInitialized] = useState<boolean>(false);
|
||||||
|
const [coupons, setCoupons] = useState<SelectOption[]>([]);
|
||||||
|
// additionalLoading state is used for displaying loading message when loading more coupons
|
||||||
|
const [additionalLoading, setAdditionalLoading] = useState<boolean>(false);
|
||||||
|
const [page, setPage] = useState<number>(1);
|
||||||
|
// hasMore state is used to prevent loading more coupons when there are no more coupons to load
|
||||||
|
const [hasMore, setHasMore] = useState<boolean>(true);
|
||||||
|
const [searchQuery, setSearchQuery] = useState<string>('');
|
||||||
|
const [errors, setErrors] = useState([]);
|
||||||
|
|
||||||
|
const loadCoupons = useCallback(
|
||||||
|
(
|
||||||
|
isInitialLoading: boolean,
|
||||||
|
newPage: number,
|
||||||
|
newSearchQuery: string,
|
||||||
|
newHasMore: boolean,
|
||||||
|
) => {
|
||||||
|
if (!newHasMore) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isInitialLoading) {
|
||||||
|
setAdditionalLoading(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MailPoet.Ajax.post({
|
||||||
|
api_version: MailPoet.apiVersion,
|
||||||
|
endpoint: 'coupons',
|
||||||
|
action: 'getCoupons',
|
||||||
|
data: {
|
||||||
|
page_number: newPage,
|
||||||
|
page_size: 1000,
|
||||||
|
include_coupon_ids: segment.coupon_code_ids,
|
||||||
|
search: newSearchQuery,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
const { data } = response;
|
||||||
|
const loadedCoupons: SelectOption[] = data.map((coupon: Coupon) => ({
|
||||||
|
value: coupon.id.toString(),
|
||||||
|
label: coupon.text,
|
||||||
|
}));
|
||||||
|
const nextPage = newPage + 1;
|
||||||
|
if (loadedCoupons.length === 0) {
|
||||||
|
setHasMore(false);
|
||||||
|
} else {
|
||||||
|
setCoupons((prevOptions: SelectOption[]) =>
|
||||||
|
uniqBy(
|
||||||
|
(coupon) => coupon.value,
|
||||||
|
[...prevOptions, ...loadedCoupons],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
setPage(nextPage);
|
||||||
|
}
|
||||||
|
if (!isInitialLoading) {
|
||||||
|
setAdditionalLoading(false);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.fail((response: ErrorResponse) => {
|
||||||
|
setErrors(response.errors as { message: string }[]);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[segment.coupon_code_ids],
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is called when user scrolls to the bottom of the select and should load more coupons.
|
||||||
|
* Loading coupons should not be called when there are no more coupons to load.
|
||||||
|
*/
|
||||||
|
const handleMenuScrollToBottom = () => {
|
||||||
|
if (!additionalLoading && hasMore) {
|
||||||
|
loadCoupons(false, page, searchQuery, hasMore);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function for handling search input change that can filter coupons by search query when
|
||||||
|
* there is more coupons than one page.
|
||||||
|
* @param inputValue
|
||||||
|
*/
|
||||||
|
const handleInputChange = (inputValue: string): void => {
|
||||||
|
const oldSearchQuery = searchQuery; // OldSearchQuery is used to prevent loading coupons when search query is deleted
|
||||||
|
setSearchQuery(inputValue);
|
||||||
|
if (
|
||||||
|
!additionalLoading &&
|
||||||
|
((hasMore && inputValue) || (oldSearchQuery && !inputValue))
|
||||||
|
) {
|
||||||
|
setPage(1);
|
||||||
|
// Passing new values to loadCoupons to avoid using old values
|
||||||
|
loadCoupons(false, 1, inputValue, hasMore);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isInitialized) {
|
||||||
|
loadCoupons(true, page, searchQuery, hasMore);
|
||||||
|
setIsInitialized(true);
|
||||||
|
}
|
||||||
|
}, [isInitialized, page, searchQuery, loadCoupons, hasMore]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!Array.isArray(segment.coupon_code_ids)) {
|
if (!Array.isArray(segment.coupon_code_ids)) {
|
||||||
@ -46,59 +148,69 @@ export function UsedCouponCodeFields({
|
|||||||
}
|
}
|
||||||
}, [updateSegmentFilter, segment, filterIndex]);
|
}, [updateSegmentFilter, segment, filterIndex]);
|
||||||
|
|
||||||
const coupons: Coupon[] = useSelect(
|
|
||||||
(select) => select(storeName).getCoupons(),
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
const couponOptions = coupons.map((coupon) => ({
|
|
||||||
value: coupon.id,
|
|
||||||
label: coupon.name,
|
|
||||||
}));
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Grid.CenteredRow>
|
{errors.length > 0 && <APIErrorsNotice errors={errors} />}
|
||||||
<Select
|
{isInitialized ? (
|
||||||
isMaxContentWidth
|
<>
|
||||||
key="select-operator-used-coupon-codes"
|
<Grid.CenteredRow>
|
||||||
value={segment.operator}
|
<Select
|
||||||
onChange={(e): void => {
|
isMaxContentWidth
|
||||||
void updateSegmentFilter({ operator: e.target.value }, filterIndex);
|
key="select-operator-used-coupon-codes"
|
||||||
}}
|
value={segment.operator}
|
||||||
automationId="select-operator-used-coupon-code"
|
onChange={(e): void => {
|
||||||
>
|
void updateSegmentFilter(
|
||||||
<option value={AnyValueTypes.ANY}>{MailPoet.I18n.t('anyOf')}</option>
|
{ operator: e.target.value },
|
||||||
<option value={AnyValueTypes.ALL}>{MailPoet.I18n.t('allOf')}</option>
|
filterIndex,
|
||||||
<option value={AnyValueTypes.NONE}>
|
);
|
||||||
{MailPoet.I18n.t('noneOf')}
|
}}
|
||||||
</option>
|
automationId="select-operator-used-coupon-code"
|
||||||
</Select>
|
>
|
||||||
<ReactSelect
|
<option value={AnyValueTypes.ANY}>
|
||||||
key="select-coupon-codes"
|
{MailPoet.I18n.t('anyOf')}
|
||||||
isFullWidth
|
</option>
|
||||||
isMulti
|
<option value={AnyValueTypes.ALL}>
|
||||||
placeholder={MailPoet.I18n.t('selectWooCouponCodes')}
|
{MailPoet.I18n.t('allOf')}
|
||||||
options={couponOptions}
|
</option>
|
||||||
value={filter((option) => {
|
<option value={AnyValueTypes.NONE}>
|
||||||
if (!segment.coupon_code_ids) return undefined;
|
{MailPoet.I18n.t('noneOf')}
|
||||||
return segment.coupon_code_ids.indexOf(option.value) !== -1;
|
</option>
|
||||||
}, couponOptions)}
|
</Select>
|
||||||
onChange={(options: SelectOption[]): void => {
|
<ReactSelect
|
||||||
void updateSegmentFilter(
|
key="select-coupon-codes"
|
||||||
{
|
isFullWidth
|
||||||
coupon_code_ids: (options || []).map(
|
isMulti
|
||||||
(x: SelectOption) => x.value,
|
placeholder={MailPoet.I18n.t('selectWooCouponCodes')}
|
||||||
),
|
options={coupons}
|
||||||
},
|
value={filter((option) => {
|
||||||
filterIndex,
|
if (!segment.coupon_code_ids) return undefined;
|
||||||
);
|
return segment.coupon_code_ids.indexOf(option.value) !== -1;
|
||||||
}}
|
}, coupons)}
|
||||||
automationId="select-shipping-methods"
|
onInputChange={handleInputChange}
|
||||||
/>
|
onChange={(options: SelectOption[]): void => {
|
||||||
</Grid.CenteredRow>
|
void updateSegmentFilter(
|
||||||
<Grid.CenteredRow>
|
{
|
||||||
<DaysPeriodField filterIndex={filterIndex} />
|
coupon_code_ids: (options || []).map(
|
||||||
</Grid.CenteredRow>
|
(x: SelectOption) => x.value,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
filterIndex,
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
isLoading={additionalLoading}
|
||||||
|
automationId="select-shipping-methods"
|
||||||
|
onMenuScrollToBottom={handleMenuScrollToBottom}
|
||||||
|
/>
|
||||||
|
</Grid.CenteredRow>
|
||||||
|
<Grid.CenteredRow>
|
||||||
|
<DaysPeriodField filterIndex={filterIndex} />
|
||||||
|
</Grid.CenteredRow>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<Grid.CenteredRow>
|
||||||
|
{__('Loading coupon codes...', 'mailpoet')}
|
||||||
|
</Grid.CenteredRow>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -372,7 +372,7 @@ export type WooShippingMethod = {
|
|||||||
|
|
||||||
export type Coupon = {
|
export type Coupon = {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
text: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export enum SegmentTemplateCategories {
|
export enum SegmentTemplateCategories {
|
||||||
|
Reference in New Issue
Block a user