Skip to content

Commit

Permalink
feature: added recurring donation levels to form builder
Browse files Browse the repository at this point in the history
  • Loading branch information
JasonTheAdams committed Dec 12, 2023
1 parent 584621b commit baf925d
Show file tree
Hide file tree
Showing 12 changed files with 346 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public function __invoke(DonationAmountBlockModel $block, string $currency): Don
$amountNode
->label($block->getLabel())
->levels(...$block->getLevels())
->recurringLevels(...$block->getRecurringLevels())
->allowLevels($block->getPriceOption() === 'multi')
->allowCustomAmount($block->isCustomAmountEnabled())
->fixedAmountValue($block->getSetPrice())
Expand Down
1 change: 1 addition & 0 deletions src/DonationForms/resources/propTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ export interface DonationAmountProps extends GroupProps {

export interface AmountProps extends FieldProps {
levels: number[];
recurringLevels: number[] | null;
allowLevels: boolean;
allowCustomAmount: boolean;
fixedAmountValue: number;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,7 @@ type DonationAmountLevelsProps = {
/**
* @since 3.0.0
*/
export default function DonationAmountLevels({
name,
currency,
levels,
onLevelClick,
}: DonationAmountLevelsProps) {
export default function DonationAmountLevels({name, currency, levels, recurringLevels, onLevelClick}: DonationAmountLevelsProps) {
const {useWatch, useCurrencyFormatter} = window.givewp.form.hooks;
const amount = useWatch({name});
const formatter = useCurrencyFormatter(currency);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export default function Amount({

const currency = useWatch({name: 'currency'});

console.log({levels, recurringLevels});
const getAmountLevels = useCallback(() => {
if (currencySwitcherSettings.length <= 1) {
return levels;
Expand Down
39 changes: 36 additions & 3 deletions src/FormBuilder/BlockModels/DonationAmountBlockModel.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ public function getLabel(): string
*/
public function getLevels(): array
{
return array_map(static function($level) {
return (float)filter_var($level, FILTER_SANITIZE_NUMBER_FLOAT,FILTER_FLAG_ALLOW_FRACTION);
return array_map(static function ($level) {
return (float)filter_var($level, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION);
}, $this->block->getAttribute('levels'));
}

Expand All @@ -76,7 +76,40 @@ public function getLevels(): array
*/
public function getDefaultLevel(): ?float
{
return (float)filter_var($this->block->getAttribute('defaultLevel'), FILTER_SANITIZE_NUMBER_FLOAT,FILTER_FLAG_ALLOW_FRACTION);
return (float)filter_var(
$this->block->getAttribute('defaultLevel'),
FILTER_SANITIZE_NUMBER_FLOAT,
FILTER_FLAG_ALLOW_FRACTION
);
}

/**
* @unreleased
*/
public function getRecurringLevels(): ?array
{
$levels = $this->block->getAttribute('recurringLevels');
$hasRecurringLevels = $this->block->getAttribute('hasRecurringLevels');

if (!$hasRecurringLevels || !is_array($levels) || empty($levels)) {
return null;
}

return array_map(static function ($level) {
return (float)filter_var($level, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION);
}, $levels);
}

public function getDefaultRecurringLevel(): ?float
{
$defaultLevel = $this->block->getAttribute('recurringDefaultLevel');
$hasRecurringLevels = $this->block->getAttribute('hasRecurringLevels');

if (!$hasRecurringLevels || !is_numeric($defaultLevel)) {
return null;
}

return (float)filter_var($defaultLevel, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,16 @@ import {
} from '@wordpress/components';
import {__, sprintf} from '@wordpress/i18n';
import {InspectorControls} from '@wordpress/block-editor';
import {CurrencyControl, formatCurrencyAmount} from '@givewp/form-builder/components/CurrencyControl';
import {CurrencyControl} from '@givewp/form-builder/components/CurrencyControl';
import periodLookup from '../period-lookup';
import RecurringDonationsPromo from '@givewp/form-builder/promos/recurring-donations';
import {getFormBuilderWindowData} from '@givewp/form-builder/common/getWindowData';
import {useCallback, useState} from '@wordpress/element';
import {useCallback} from '@wordpress/element';
import Options from '@givewp/form-builder/components/OptionsPanel';
import {OptionProps} from '@givewp/form-builder/components/OptionsPanel/types';
import {useEffect} from 'react';
import {DonationAmountAttributes} from '@givewp/form-builder/blocks/fields/amount/types';
import {subscriptionPeriod} from '@givewp/forms/registrars/templates/groups/DonationAmount/subscriptionPeriod';
import useDonationLevels from '@givewp/form-builder/blocks/fields/amount/inspector/useDonationLevels';

const compareBillingPeriods = (val1: string, val2: string): number => {
const index1 = Object.keys(periodLookup).indexOf(val1);
Expand Down Expand Up @@ -61,6 +61,9 @@ const Inspector = ({attributes, setAttributes}) => {
label = __('Donation Amount', 'give'),
levels,
defaultLevel,
hasRecurringLevels,
recurringLevels,
defaultRecurringLevel,
priceOption,
setPrice,
customAmount,
Expand Down Expand Up @@ -113,61 +116,36 @@ const Inspector = ({attributes, setAttributes}) => {
[recurringBillingPeriodOptions]
);

const {
levelOptions: donationLevels,
handleLevelAdded,
handleLevelRemoved,
handleLevelsChange,
} = useDonationLevels(
levels,
defaultLevel,
useCallback((defaultLevel) => setAttributes({defaultLevel}), [setAttributes]),
useCallback((levels) => setAttributes({levels}), [setAttributes])
);

const {
levelOptions: recurringDonationLevels,
handleLevelAdded: handleRecurringLevelAdded,
handleLevelRemoved: handleRecurringLevelRemoved,
handleLevelsChange: handleRecurringLevelsChange,
} = useDonationLevels(
recurringLevels || levels,
defaultRecurringLevel || defaultLevel,
useCallback((defaultRecurringLevel) => setAttributes({defaultRecurringLevel}), [setAttributes]),
useCallback((recurringLevels) => setAttributes({recurringLevels}), [setAttributes])
);

const {gateways, recurringAddonData, gatewaySettingsUrl} = getFormBuilderWindowData();
const enabledGateways = gateways.filter((gateway) => gateway.enabled);
const recurringGateways = gateways.filter((gateway) => gateway.supportsSubscriptions);
const isRecurringSupported = enabledGateways.some((gateway) => gateway.supportsSubscriptions);
const isRecurring = isRecurringSupported && recurringEnabled;

const [donationLevels, setDonationLevels] = useState<OptionProps[]>(
levels.map((level) => ({
id: String(Math.floor(Math.random() * 1000000)),
label: formatCurrencyAmount(level.toString()),
value: level.toString(),
checked: defaultLevel === level,
}))
);

const handleLevelAdded = () => {
const newLevelValue = levels.length ? String(Math.max(...levels) * 2) : '10';
const newLevel = {
id: String(Math.floor(Math.random() * 1000000)),
label: formatCurrencyAmount(newLevelValue),
value: newLevelValue,
checked: false,
};

// If there are no levels, set the new level as the default.
if (!levels.length) {
newLevel.checked = true;
setAttributes({defaultLevel: Number(newLevelValue)});
}

setDonationLevels([...donationLevels, newLevel]);
setAttributes({levels: [...levels, Number(newLevelValue)]});
};

const handleLevelRemoved = (level: OptionProps, index: number) => {
const newLevels = levels.filter((_, i) => i !== index);
const newDonationLevels = donationLevels.filter((_, i) => i !== index);

if (level.checked && newDonationLevels.length > 0) {
newDonationLevels[0].checked = true;
setAttributes({defaultLevel: Number(newDonationLevels[0].value)});
}

setDonationLevels(newDonationLevels);
setAttributes({levels: newLevels});
};

const handleLevelsChange = (options: OptionProps[]) => {
const checkedLevel = options.filter((option) => option.checked);
const newLevels = options.filter((option) => option.value).map((option) => Number(option.value));

setDonationLevels(options);
setAttributes({levels: newLevels, defaultLevel: Number(checkedLevel[0].value)});
};

const getDefaultBillingPeriodOptions = useCallback(
(options) => {
if (recurringEnableOneTimeDonations) {
Expand Down Expand Up @@ -257,6 +235,30 @@ const Inspector = ({attributes, setAttributes}) => {
onRemoveOption={handleLevelRemoved}
defaultControlsTooltip={__('Default Level', 'give')}
/>

{isRecurringSupported && (
<ToggleControl
label={__('Enable recurring donation levels', 'give')}
help={__(
'This will allow you to set different donation levels for recurring donations.',
'give'
)}
checked={hasRecurringLevels}
onChange={() => setAttributes({hasRecurringLevels: !hasRecurringLevels})}
/>
)}

{isRecurringSupported && hasRecurringLevels && (
<Options
currency={true}
multiple={false}
options={recurringDonationLevels}
setOptions={handleRecurringLevelsChange}
onAddOption={handleRecurringLevelAdded}
onRemoveOption={handleRecurringLevelRemoved}
defaultControlsTooltip={__('Default Level', 'give')}
/>
)}
</PanelBody>
)}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import {useState, useCallback} from '@wordpress/element';
import type {OptionProps} from '@givewp/form-builder/components/OptionsPanel/types';
import {formatCurrencyAmount} from '@givewp/form-builder/components/CurrencyControl';

function generateLevelId() {
return String(Math.floor(Math.random() * 1000000));
}

export default function useDonationLevels(
levels: number[],
defaultLevel: number,
setDefaultLevel: (defaultLevel: number) => void,
setLevels: (levels: number[]) => void,
) {
const [levelOptions, setLevelOptions] = useState<OptionProps[]>(
levels.map((level: number) => ({
id: generateLevelId(),
label: formatCurrencyAmount(level.toString()),
value: level.toString(),
checked: defaultLevel === level,
}))
);

const handleLevelAdded = useCallback(() => {
const newLevelValue = levels.length ? String(Math.max(...levels) * 2) : '10';
const newLevel = {
id: generateLevelId(),
label: formatCurrencyAmount(newLevelValue),
value: newLevelValue,
checked: false,
};

// If there are no levels, set the new level as the default.
if (!levels.length) {
newLevel.checked = true;
setDefaultLevel(Number(newLevelValue));
}

setLevelOptions([...levelOptions, newLevel]);
setLevels([...levels, Number(newLevelValue)]);
}, [levels, setLevels, setDefaultLevel, setLevelOptions]);

const handleLevelRemoved = useCallback(
(level: OptionProps, index: number) => {
const newLevels = levels.filter((_, i) => i !== index);
const newLevelOptions = levelOptions.filter((_, i) => i !== index);

if (level.checked && newLevelOptions.length > 0) {
newLevelOptions[0].checked = true;
setDefaultLevel(Number(newLevelOptions[0].value));
}

setLevelOptions(newLevelOptions);
setLevels(newLevels);
},
[levels, setLevels, setDefaultLevel, setLevelOptions]
);

const handleLevelsChange = useCallback((options: OptionProps[]) => {
const checkedLevel = options.filter((option) => option.checked);
const newLevels = options.filter((option) => option.value).map((option) => Number(option.value));

setLevelOptions(options);
setLevels(newLevels);
setDefaultLevel(Number(checkedLevel[0].value));
}, [setLevels, setDefaultLevel, setLevelOptions]);

return {
levelOptions,
handleLevelAdded,
handleLevelRemoved,
handleLevelsChange,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,18 @@ const settings: FieldBlock['settings'] = {
type: 'number',
default: defaultLevel,
},
hasRecurringLevels: {
type: 'boolean',
default: false,
},
recurringLevels: {
type: 'array',
default: levels,
},
recurringDefaultLevel: {
type: 'number',
default: defaultLevel,
},
priceOption: {
type: 'string',
default: priceOption,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ export interface DonationAmountAttributes {
label: string;
levels: number[];
defaultLevel: number;
hasRecurringLevels: boolean;
recurringLevels: number[];
defaultRecurringLevel: number;
priceOption: string;
setPrice: number;
customAmount: boolean;
Expand All @@ -14,5 +17,5 @@ export interface DonationAmountAttributes {
recurringBillingPeriodOptions: subscriptionPeriod[];
recurringLengthOfTime: string;
recurringOptInDefaultBillingPeriod: subscriptionPeriod | 'one-time';
recurringEnableOneTimeDonations: boolean
}
recurringEnableOneTimeDonations: boolean;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {createReduxStore} from '@wordpress/data';
import {getWindowData} from '@givewp/form-builder/common';

const DEFAULT_STATE = getWindowData();

const store = createReduxStore('givewp/core', {
reducer: (state = DEFAULT_STATE) => state,
selectors: {
get: () => () => DEFAULT_STATE,
},
});

export default store;
Loading

0 comments on commit baf925d

Please sign in to comment.