diff --git a/apps/admin-x-settings/src/admin-x-ds/global/form/Select.tsx b/apps/admin-x-settings/src/admin-x-ds/global/form/Select.tsx index 7a4432bc55..ec5462d23a 100644 --- a/apps/admin-x-settings/src/admin-x-ds/global/form/Select.tsx +++ b/apps/admin-x-settings/src/admin-x-ds/global/form/Select.tsx @@ -9,11 +9,16 @@ export interface SelectOption { label: string; } +export interface SelectOptionGroup { + label: string; + options: SelectOption[]; +} + export interface SelectProps { title?: string; size?: 'xs' | 'md'; prompt?: string; - options: SelectOption[]; + options: SelectOption[] | SelectOptionGroup[]; selectedOption?: string onSelect: (value: string) => void; error?:boolean; @@ -87,13 +92,25 @@ const Select: React.FC = ({ diff --git a/apps/admin-x-settings/src/components/settings/membership/Tiers.tsx b/apps/admin-x-settings/src/components/settings/membership/Tiers.tsx index d3a1db421d..01403d4684 100644 --- a/apps/admin-x-settings/src/components/settings/membership/Tiers.tsx +++ b/apps/admin-x-settings/src/components/settings/membership/Tiers.tsx @@ -2,13 +2,13 @@ import React, {useState} from 'react'; import SettingGroup from '../../../admin-x-ds/settings/SettingGroup'; import TabView from '../../../admin-x-ds/global/TabView'; import TiersList from './tiers/TiersList'; -import {getArchivedTiers, getPaidActiveTiers} from '../../../utils/helpers'; +import {getActiveTiers, getArchivedTiers} from '../../../utils/helpers'; import {useTiers} from '../../providers/ServiceProvider'; const Tiers: React.FC<{ keywords: string[] }> = ({keywords}) => { const [selectedTab, setSelectedTab] = useState('active-tiers'); const {data: tiers, update: updateTier} = useTiers(); - const activeTiers = getPaidActiveTiers(tiers); + const activeTiers = getActiveTiers(tiers); const archivedTiers = getArchivedTiers(tiers); const tabs = [ diff --git a/apps/admin-x-settings/src/components/settings/membership/tiers/TierDetailModal.tsx b/apps/admin-x-settings/src/components/settings/membership/tiers/TierDetailModal.tsx index 2d32d66ac2..e77feaab91 100644 --- a/apps/admin-x-settings/src/components/settings/membership/tiers/TierDetailModal.tsx +++ b/apps/admin-x-settings/src/components/settings/membership/tiers/TierDetailModal.tsx @@ -4,7 +4,7 @@ import Heading from '../../../../admin-x-ds/global/Heading'; import Icon from '../../../../admin-x-ds/global/Icon'; import Modal from '../../../../admin-x-ds/global/modal/Modal'; import NiceModal, {useModal} from '@ebay/nice-modal-react'; -import React from 'react'; +import React, {useState} from 'react'; import Select from '../../../../admin-x-ds/global/form/Select'; import SortableList from '../../../../admin-x-ds/global/SortableList'; import TextField from '../../../../admin-x-ds/global/form/TextField'; @@ -14,7 +14,8 @@ import useForm from '../../../../hooks/useForm'; import useRouting from '../../../../hooks/useRouting'; import useSortableIndexedList from '../../../../hooks/useSortableIndexedList'; import {Tier} from '../../../../types/api'; -import {currencyFromDecimal, currencyToDecimal} from '../../../../utils/currency'; +import {currencies, currencyFromDecimal, currencyGroups, currencyToDecimal, getSymbol} from '../../../../utils/currency'; +import {showToast} from '../../../../admin-x-ds/global/Toast'; import {useTiers} from '../../../providers/ServiceProvider'; interface TierDetailModalProps { @@ -28,32 +29,58 @@ type TierFormState = Partial = ({tier}) => { + const isFreeTier = tier?.type === 'free'; + const modal = useModal(); const {updateRoute} = useRouting(); const {update: updateTier, create: createTier} = useTiers(); + const [hasFreeTrial, setHasFreeTrial] = React.useState(!!tier?.trial_days); + + const [errors, setErrors] = useState<{ [key in keyof Tier]?: string }>({}); // eslint-disable-line no-unused-vars + + const setError = (field: keyof Tier, error: string | undefined) => { + setErrors({...errors, [field]: error}); + }; const {formState, updateForm, handleSave} = useForm({ initialState: { ...(tier || {}), monthly_price: tier?.monthly_price ? currencyToDecimal(tier.monthly_price).toString() : '', yearly_price: tier?.yearly_price ? currencyToDecimal(tier.yearly_price).toString() : '', - trial_days: tier?.trial_days?.toString() || '' + trial_days: tier?.trial_days?.toString() || '', + currency: tier?.currency || currencies[0].isoCode }, onSave: async () => { - const values = { - ...formState, - monthly_price: currencyFromDecimal(parseFloat(formState.monthly_price)), - yearly_price: currencyFromDecimal(parseFloat(formState.yearly_price)), - trial_days: currencyFromDecimal(parseFloat(formState.trial_days)) - }; + if (Object.values(errors).some(error => error)) { + showToast({ + type: 'pageError', + message: 'One or more fields have errors' + }); + return; + } + + const {monthly_price: monthlyPrice, yearly_price: yearlyPrice, trial_days: trialDays, currency, ...rest} = formState; + const values: Partial = rest; + + values.benefits = values.benefits?.filter(benefit => benefit); + + if (!isFreeTier) { + values.currency = currency; + values.monthly_price = currencyFromDecimal(parseFloat(monthlyPrice)); + values.yearly_price = currencyFromDecimal(parseFloat(yearlyPrice)); + values.trial_days = parseInt(formState.trial_days); + } if (tier?.id) { await updateTier({...tier, ...values}); } else { await createTier(values); } + + modal.remove(); } }); + const benefits = useSortableIndexedList({ items: formState.benefits || [], setItems: newBenefits => updateForm(state => ({...state, benefits: newBenefits})), @@ -61,6 +88,12 @@ const TierDetailModal: React.FC = ({tier}) => { canAddNewItem: item => !!item }); + const forceCurrencyValue = (value: string) => { + return value.match(/[\d]+\.?[\d]{0,2}/)?.[0] || ''; + }; + + const currencySymbol = formState.currency ? getSymbol(formState.currency) : '$'; + return { updateRoute('tiers'); @@ -69,75 +102,84 @@ const TierDetailModal: React.FC = ({tier}) => { size='lg' title='Tier' stickyFooter - onOk={async () => { - await handleSave(); - modal.remove(); - }} + onOk={handleSave} >
- setError('name', e.target.value ? undefined : 'You must specify a name')} onChange={e => updateForm(state => ({...state, name: e.target.value}))} - /> + />} updateForm(state => ({...state, description: e.target.value}))} /> -
+ {!isFreeTier &&
Prices