Skip to content

Commit

Permalink
Seperate out limits and recurring
Browse files Browse the repository at this point in the history
  • Loading branch information
bkspace committed Oct 1, 2024
1 parent 0befa03 commit 466f711
Show file tree
Hide file tree
Showing 9 changed files with 489 additions and 470 deletions.
176 changes: 43 additions & 133 deletions src/components/UsageLimitsCard/UsageLimitsCard.tsx
Original file line number Diff line number Diff line change
@@ -1,42 +1,21 @@
import React, {useEffect, useState} from 'react';
import {
Card,
ChoiceList,
TextField,
Text,
InlineError,
BlockStack,
Box,
} from '@shopify/polaris';
import {useI18n} from '@shopify/react-i18n';
import React from 'react';
import {Card, BlockStack, Box} from '@shopify/polaris';

import type {Field, PositiveNumericString} from '../../types';
import type {RecurringPaymentType} from '../../constants';
import {forcePositiveInteger} from '../../utilities/numbers';

import {RecurringPayment} from './components';
import styles from './UsageLimitsCard.scss';

export enum UsageLimitType {
TotalUsageLimit = 'TOTAL_USAGE_LIMIT',
OncePerCustomerLimit = 'ONCE_PER_CUSTOMER_LIMIT',
}
import {RecurringPayment, UsageLimits} from './components';

interface UsageLimitProps {
/**
* The total number of times the discount can be used.
*/
totalUsageLimit: Field<PositiveNumericString | null>;

/**
* When selected, the discount may be used at most once per customer
* (optional) When true, displays the "Recurring payments" section. (see {@interface UsageLimitsCardMultiplePaymentsProps})
*/
oncePerCustomer: Field<boolean>;
isRecurring?: false;

/**
* (optional) When true, displays the "Recurring payments" section. (see {@interface UsageLimitsCardMultiplePaymentsProps})
* (optional) When true, displays the "Limits" section. (see {@interface UsageLimitsCardMultiplePaymentsProps})
*/
isRecurring?: false;
isLimited?: false;
}

interface UsageLimitsCardMultiplePaymentsProps
Expand All @@ -57,118 +36,43 @@ interface UsageLimitsCardMultiplePaymentsProps
recurringPaymentLimit: Field<PositiveNumericString>;
}

interface UsageLimitsCardLimitsProps
extends Omit<UsageLimitProps, 'isLimited'> {
/**
* Displays the "Limits" section.
*/
isLimited: true;

/**
* The total number of times the discount can be used.
*/
totalUsageLimit: Field<PositiveNumericString | null>;

/**
* When selected, the discount may be used at most once per customer
*/
oncePerCustomer: Field<boolean>;
}

export type UsageLimitsCardProps =
| UsageLimitProps
| UsageLimitsCardLimitsProps
| UsageLimitsCardMultiplePaymentsProps;

export const DISCOUNT_TOTAL_USAGE_LIMIT_FIELD = 'totalUsageLimit';

export function UsageLimitsCard(props: UsageLimitsCardProps) {
const {totalUsageLimit, oncePerCustomer, isRecurring} = props;

const [showUsageLimit, setShowUsageLimit] = useState(
totalUsageLimit.value !== null,
);

const [i18n] = useI18n();

useEffect(
() => setShowUsageLimit(totalUsageLimit.value !== null),
[totalUsageLimit.value],
);

const handleUsageLimitsChoicesChange = (
selectedUsageLimitTypes: UsageLimitType[],
) => {
const newOncePerCustomer = selectedUsageLimitTypes.includes(
UsageLimitType.OncePerCustomerLimit,
);

// When the checkbox is toggled, either set the totalUsageLimit value to null (null === checkbox off) or an empty string (non-null === checkbox on)
if (!selectedUsageLimitTypes.includes(UsageLimitType.TotalUsageLimit)) {
totalUsageLimit.onChange(null);
} else if (totalUsageLimit.value === null) {
totalUsageLimit.onChange('');
}

newOncePerCustomer !== oncePerCustomer.value &&
oncePerCustomer.onChange(newOncePerCustomer);
};

if (!props.isLimited && !props.isRecurring) return null;
return (
<Box paddingBlockEnd="400">
<Card padding="400">
<BlockStack gap="400">
<Text variant="headingMd" as="h2">
{i18n.translate('DiscountAppComponents.UsageLimitsCard.title')}
</Text>
<ChoiceList
title={i18n.translate(
'DiscountAppComponents.UsageLimitsCard.options',
)}
titleHidden
allowMultiple
selected={[
...(showUsageLimit ? [UsageLimitType.TotalUsageLimit] : []),
...(oncePerCustomer.value
? [UsageLimitType.OncePerCustomerLimit]
: []),
]}
choices={[
{
label: i18n.translate(
'DiscountAppComponents.UsageLimitsCard.totalUsageLimitLabel',
),
value: UsageLimitType.TotalUsageLimit,
renderChildren: (isSelected: boolean) => (
<BlockStack>
{isSelected && (
<div className={styles.TotalUsageLimitTextField}>
<TextField
id={DISCOUNT_TOTAL_USAGE_LIMIT_FIELD}
label={i18n.translate(
'DiscountAppComponents.UsageLimitsCard.totalUsageLimitLabel',
)}
autoComplete="off"
labelHidden
value={totalUsageLimit.value || ''}
onChange={(nextValue) => {
totalUsageLimit.onChange(
forcePositiveInteger(nextValue),
);
}}
onBlur={totalUsageLimit.onBlur}
error={Boolean(totalUsageLimit.error)}
/>
</div>
)}
{isRecurring && (
<Text as="span" tone="subdued">
{i18n.translate(
'DiscountAppComponents.UsageLimitsCard.totalUsageLimitHelpTextSubscription',
)}
</Text>
)}
{isSelected && totalUsageLimit.error && (
<InlineError
fieldID={DISCOUNT_TOTAL_USAGE_LIMIT_FIELD}
message={totalUsageLimit.error}
/>
)}
</BlockStack>
),
},
{
label: i18n.translate(
'DiscountAppComponents.UsageLimitsCard.oncePerCustomerLimitLabel',
),
value: UsageLimitType.OncePerCustomerLimit,
},
]}
onChange={handleUsageLimitsChoicesChange}
/>
</BlockStack>
{isShowRecurringPaymentSection(props) && (
{isLimitedSection(props) && (
<BlockStack gap="400">
<UsageLimits
totalUsageLimit={props.totalUsageLimit}
oncePerCustomer={props.oncePerCustomer}
/>
</BlockStack>
)}
{isRecurringPaymentSection(props) && (
<BlockStack gap="400">
<RecurringPayment
recurringPaymentType={props.recurringPaymentType}
Expand All @@ -181,8 +85,14 @@ export function UsageLimitsCard(props: UsageLimitsCardProps) {
);
}

function isShowRecurringPaymentSection(
function isRecurringPaymentSection(
props: UsageLimitsCardProps,
): props is UsageLimitsCardMultiplePaymentsProps {
return Boolean(props.isRecurring);
}

function isLimitedSection(
props: UsageLimitsCardProps,
): props is UsageLimitsCardLimitsProps {
return Boolean(props.isLimited);
}
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export function RecurringPayment({

return (
<BlockStack gap="400">
<Text variant="headingSm" as="h3">
<Text variant="headingMd" as="h2">
{i18n.translate('DiscountAppComponents.RecurringPayment.title')}
</Text>
<ChoiceList
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@import '../../styles/common';
@import '../../../../styles/common';

.TotalUsageLimitTextField {
@include constrained-input;
Expand Down
135 changes: 135 additions & 0 deletions src/components/UsageLimitsCard/components/UsageLimits/UsageLimits.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import React, {useEffect, useState} from 'react';
import {
ChoiceList,
TextField,
Text,
InlineError,
BlockStack,
} from '@shopify/polaris';
import {useI18n} from '@shopify/react-i18n';

import type {Field, PositiveNumericString} from '../../../../types';
import {forcePositiveInteger} from '../../../../utilities/numbers';

import styles from './UsageLimits.scss';

export enum UsageLimitType {
TotalUsageLimit = 'TOTAL_USAGE_LIMIT',
OncePerCustomerLimit = 'ONCE_PER_CUSTOMER_LIMIT',
}

export const DISCOUNT_TOTAL_USAGE_LIMIT_FIELD = 'totalUsageLimit';

interface Props {
/**
* The total number of times the discount can be used.
*/
totalUsageLimit: Field<PositiveNumericString | null>;

/**
* When selected, the discount may be used at most once per customer
*/
oncePerCustomer: Field<boolean>;
}

export function UsageLimits({totalUsageLimit, oncePerCustomer}: Props) {
const [showUsageLimit, setShowUsageLimit] = useState(
totalUsageLimit.value !== null,
);

const [i18n] = useI18n();

useEffect(
() => setShowUsageLimit(totalUsageLimit.value !== null),
[totalUsageLimit.value],
);

const handleUsageLimitsChoicesChange = (
selectedUsageLimitTypes: UsageLimitType[],
) => {
const newOncePerCustomer = selectedUsageLimitTypes.includes(
UsageLimitType.OncePerCustomerLimit,
);

// When the checkbox is toggled, either set the totalUsageLimit value to null (null === checkbox off) or an empty string (non-null === checkbox on)
if (!selectedUsageLimitTypes.includes(UsageLimitType.TotalUsageLimit)) {
totalUsageLimit.onChange(null);
} else if (totalUsageLimit.value === null) {
totalUsageLimit.onChange('');
}

newOncePerCustomer !== oncePerCustomer.value &&
oncePerCustomer.onChange(newOncePerCustomer);
};

return (
<BlockStack gap="400">
<Text variant="headingMd" as="h2">
{i18n.translate('DiscountAppComponents.UsageLimitsCard.title')}
</Text>
<ChoiceList
title={i18n.translate('DiscountAppComponents.UsageLimitsCard.options')}
titleHidden
allowMultiple
selected={[
...(showUsageLimit ? [UsageLimitType.TotalUsageLimit] : []),
...(oncePerCustomer.value
? [UsageLimitType.OncePerCustomerLimit]
: []),
]}
choices={[
{
label: i18n.translate(
'DiscountAppComponents.UsageLimitsCard.totalUsageLimitLabel',
),
value: UsageLimitType.TotalUsageLimit,
renderChildren: (isSelected: boolean) => (
<BlockStack>
{isSelected && (
<div className={styles.TotalUsageLimitTextField}>
<TextField
id={DISCOUNT_TOTAL_USAGE_LIMIT_FIELD}
label={i18n.translate(
'DiscountAppComponents.UsageLimitsCard.totalUsageLimitLabel',
)}
autoComplete="off"
labelHidden
value={totalUsageLimit.value || ''}
onChange={(nextValue) => {
totalUsageLimit.onChange(
forcePositiveInteger(nextValue),
);
}}
onBlur={totalUsageLimit.onBlur}
error={Boolean(totalUsageLimit.error)}
/>
</div>
)}
{/* {isRecurring && (
<Text as="span" tone="subdued">
{i18n.translate(
'DiscountAppComponents.UsageLimitsCard.totalUsageLimitHelpTextSubscription',
)}
</Text>
)} */}
{isSelected && totalUsageLimit.error && (
<InlineError
fieldID={DISCOUNT_TOTAL_USAGE_LIMIT_FIELD}
message={totalUsageLimit.error}
/>
)}
</BlockStack>
),
},
{
label: i18n.translate(
'DiscountAppComponents.UsageLimitsCard.oncePerCustomerLimitLabel',
),
value: UsageLimitType.OncePerCustomerLimit,
},
]}
onChange={handleUsageLimitsChoicesChange}
/>
</BlockStack>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {UsageLimits} from './UsageLimits';
Loading

0 comments on commit 466f711

Please sign in to comment.