TryGhost/Ghost

View on GitHub
apps/admin-x-settings/src/components/settings/email/useDefaultRecipientsOptions.tsx

Summary

Maintainability
C
7 hrs
Test Coverage
import {GroupBase, MultiValue} from 'react-select';
import {Label} from '@tryghost/admin-x-framework/api/labels';
import {LoadMultiSelectOptions, MultiSelectOption, debounce} from '@tryghost/admin-x-design-system';
import {Offer} from '@tryghost/admin-x-framework/api/offers';
import {Tier} from '@tryghost/admin-x-framework/api/tiers';
import {isObjectId} from '../../../utils/helpers';
import {useEffect, useState} from 'react';
import {useFilterableApi} from '@tryghost/admin-x-framework/hooks';

const SIMPLE_SEGMENT_OPTIONS: MultiSelectOption[] = [{
    label: 'Free members',
    value: 'status:free',
    color: 'green'
}, {
    label: 'Paid members',
    value: 'status:-free',
    color: 'pink'
}];

const useDefaultRecipientsOptions = (selectedOption: string, defaultEmailRecipientsFilter?: string | null) => {
    const tiers = useFilterableApi<Tier, 'tiers', 'name'>({path: '/tiers/', filterKey: 'name', responseKey: 'tiers'});
    const labels = useFilterableApi<Label, 'labels', 'name'>({path: '/labels/', filterKey: 'name', responseKey: 'labels'});
    const offers = useFilterableApi<Offer, 'offers', 'name'>({path: '/offers/', filterKey: 'name', responseKey: 'offers'});

    const [selectedSegments, setSelectedSegments] = useState<MultiValue<MultiSelectOption> | null>(null);

    const tierOption = (tier: Tier): MultiSelectOption => ({value: tier.id, label: tier.name, color: 'black'});
    const labelOption = (label: Label): MultiSelectOption => ({value: `label:${label.slug}`, label: label.name, color: 'grey'});
    const offerOption = (offer: Offer): MultiSelectOption => ({value: `offer_redemptions:${offer.id}`, label: offer.name, color: 'black'});

    const loadOptions: LoadMultiSelectOptions = async (input, callback) => {
        const [tiersData, labelsData, offersData] = await Promise.all([tiers.loadData(input), labels.loadData(input), offers.loadData(input)]);

        const segmentOptionGroups: GroupBase<MultiSelectOption>[] = [
            {
                options: SIMPLE_SEGMENT_OPTIONS.filter(({label}) => label.toLowerCase().includes(input.toLowerCase()))
            },
            {
                label: 'Active Tiers',
                options: tiersData.filter(({active, type}) => active && type !== 'free').map(tierOption) || []
            },
            {
                label: 'Archived Tiers',
                options: tiersData.filter(({active}) => !active).map(tierOption) || []
            },
            {
                label: 'Labels',
                options: labelsData.map(labelOption) || []
            },
            {
                label: 'Offers',
                options: offersData.map(offerOption) || []
            }
        ];

        if (selectedSegments === null) {
            initSelectedSegments();
        }

        callback(segmentOptionGroups.filter(group => group.options.length > 0));
    };

    const initSelectedSegments = async () => {
        const filters = defaultEmailRecipientsFilter?.split(',') || [];
        const tierIds: string[] = [], labelSlugs: string[] = [], offerIds: string[] = [];

        for (const filter of filters) {
            if (filter.startsWith('label:')) {
                labelSlugs.push(filter.replace('label:', ''));
            } else if (filter.startsWith('offer_redemptions:')) {
                offerIds.push(filter.replace('offer_redemptions:', ''));
            } else if (isObjectId(filter)) {
                tierIds.push(filter);
            }
        }

        const options = await Promise.all([
            tiers.loadInitialValues(tierIds, 'id').then(data => data.map(tierOption)),
            labels.loadInitialValues(labelSlugs, 'slug').then(data => data.map(labelOption)),
            offers.loadInitialValues(offerIds, 'id').then(data => data.map(offerOption))
        ]).then(results => results.flat());

        setSelectedSegments(filters.map(filter => options.find(option => option.value === filter)!));
    };

    useEffect(() => {
        if (selectedOption === 'segment') {
            loadOptions('', () => {});
        }
    }, [selectedOption]); // eslint-disable-line react-hooks/exhaustive-deps

    return {
        loadOptions: debounce(loadOptions, 500),
        selectedSegments,
        setSelectedSegments
    };
};

export default useDefaultRecipientsOptions;