Skip to content

Commit

Permalink
Add support for new combination options
Browse files Browse the repository at this point in the history
Add in the warning banner
  • Loading branch information
davejcameron committed Aug 3, 2023
1 parent 5e51eee commit 31ee8cc
Show file tree
Hide file tree
Showing 8 changed files with 119 additions and 68 deletions.
5 changes: 5 additions & 0 deletions .changeset/wet-ants-fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@shopify/discount-app-components': minor
---

Add support for new combination options
22 changes: 11 additions & 11 deletions locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,6 @@
"discountNameFilled": "can be combined with:",
"discountNameNotFilled": "This {discountClass} discount can be combined with:",
"options": {
"productLabelOther": "Other product discounts",
"productLabel": "Product discounts",
"orderLabel": "Order discounts",
"shippingLabel": "Shipping discounts"
Expand All @@ -114,26 +113,31 @@
"order": "order",
"shipping": "shipping"
},
"warning": {
"title": "Some combinations can result in large discounts",
"description": "Test a few combinations. If the total discount is too large, adjust which discounts can combine.",
"link": "Learn more"
},
"HelpText": {
"emptyState": {
"product": {
"title": "No product discounts are currently set to combine.",
"title": "No product discounts are set to combine.",
"warning": {
"withproduct": "To let customers use more than one discount, set up at least one product discount that combines with product discounts.",
"withorder": "To let customers use more than one discount, set up at least one product discount that combines with order discounts.",
"withshipping": "To let customers use more than one discount, set up at least one product discount that combines with shipping discounts."
}
},
"order": {
"title": "No order discounts are currently set to combine.",
"title": "No order discounts are set to combine.",
"warning": {
"withproduct": "To let customers use more than one discount, set up at least one order discount that combines with product discounts.",
"withorder": "To let customers use more than one discount, set up at least one order discount that combines with order discounts.",
"withshipping": "To let customers use more than one discount, set up at least one order discount that combines with shipping discounts."
}
},
"shipping": {
"title": "No shipping discounts are currently set to combine.",
"title": "No shipping discounts are set to combine.",
"warning": {
"withproduct": "To let customers use more than one discount, set up at least one shipping discount that combines with product discounts.",
"withorder": "To let customers use more than one discount, set up at least one shipping discount that combines with order discounts.",
Expand All @@ -144,19 +148,15 @@
},
"combinations": {
"info": {
"one": "{discountCountLink} is currently set to combine.",
"other": "{discountCountLink} are currently set to combine."
"one": "{discountCountLink} is set to combine.",
"other": "{discountCountLink} are set to combine."
},
"multipleEligibleDiscounts": "If an item is eligible for multiple discounts, only the largest discount will apply.",
"multipleEligibleDiscounts": "If an item is eligible for multiple discounts, only the largest will apply.",
"counts": {
"product": {
"one": "{count} product discount",
"other": "{count} product discounts"
},
"productOther": {
"one": "{count} other product discount",
"other": "{count} other product discounts"
},
"order": {
"one": "{count} order discount",
"other": "{count} order discounts"
Expand Down
36 changes: 30 additions & 6 deletions src/components/CombinationCard/CombinationCard.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import React from 'react';
import {
Banner,
Link,
LegacyCard as Card,
ChoiceList,
LegacyStack as Stack,
Expand Down Expand Up @@ -67,9 +69,34 @@ export function CombinationCard({

const trimmedDescriptor = discountDescriptor.trim();

const selectedChoices = getSelectedChoices(combinableDiscountTypes.value);

const shouldShowBanner =
(discountClass === DiscountClass.Order &&
(selectedChoices.includes(DiscountClass.Product) ||
selectedChoices.includes(DiscountClass.Order))) ||
(discountClass === DiscountClass.Product &&
selectedChoices.includes(DiscountClass.Order));

return (
<Card title={i18n.translate('title', I18N_SCOPE)} sectioned>
<Stack vertical spacing="baseTight">
{shouldShowBanner && (
<Banner
title={i18n.translate('warning.title', I18N_SCOPE)}
status="warning"
>
<p>
{i18n.translate('warning.description', I18N_SCOPE)}{' '}
<Link
url={`https://help.shopify.com/${i18n.locale}/manual/discounts/combining-discounts`}
external
>
{i18n.translate('warning.link', I18N_SCOPE)}
</Link>
</p>
</Banner>
)}
<p>
{trimmedDescriptor ? (
<>
Expand Down Expand Up @@ -122,10 +149,7 @@ function buildChoices({
const hasCounts = typeof combinableDiscountCounts !== 'undefined';

const productOption = {
label:
currentDiscountClass === DiscountClass.Product
? i18n.translate('options.productLabelOther', I18N_SCOPE)
: i18n.translate('options.productLabel', I18N_SCOPE),
label: i18n.translate('options.productLabel', I18N_SCOPE),
value: DiscountClass.Product,
renderChildren: (isSelected: boolean) =>
isSelected && hasCounts ? (
Expand Down Expand Up @@ -181,9 +205,9 @@ function buildChoices({

switch (currentDiscountClass) {
case DiscountClass.Product:
return [productOption, shippingOption];
return [productOption, orderOption, shippingOption];
case DiscountClass.Order:
return [shippingOption];
return [productOption, orderOption, shippingOption];
case DiscountClass.Shipping:
return [productOption, orderOption];
default:
Expand Down
22 changes: 6 additions & 16 deletions src/components/CombinationCard/components/HelpText/HelpText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,6 @@ export function HelpText({
url: DISCOUNT_COMBINATION_MODAL_APP_BRIDGE_URL,
});

const productCombinesWithProduct =
currentDiscountClass === DiscountClass.Product &&
targetDiscountClass === DiscountClass.Product;

const targetDiscountClassLabel = targetDiscountClass.toLocaleLowerCase();
const scope = `DiscountAppComponents.CombinationCard.HelpText`;

Expand Down Expand Up @@ -70,11 +66,7 @@ export function HelpText({
<span ref={buttonWrapperRef}>
<Button onClick={handleModalOpen} plain>
{i18n.translate(
`combinations.counts.${
productCombinesWithProduct
? 'productOther'
: targetDiscountClassLabel
}`,
`combinations.counts.${targetDiscountClassLabel}`,
{scope},
{
count,
Expand All @@ -86,20 +78,18 @@ export function HelpText({
},
)}
</Text>
{productCombinesWithProduct && (
<Text as="span" color="subdued">
{i18n.translate('combinations.multipleEligibleDiscounts', {scope})}
</Text>
)}
<Text as="span" color="subdued">
{i18n.translate('combinations.multipleEligibleDiscounts', {scope})}
</Text>
</Stack>
) : (
<>
<Text as="span" color="subdued">
{i18n.translate('title', {
scope: `${scope}.emptyState.${currentDiscountClass.toLowerCase()}`,
scope: `${scope}.emptyState.${targetDiscountClass.toLowerCase()}`,
})}{' '}
{i18n.translate(`warning.with${currentDiscountClass.toLowerCase()}`, {
scope: `${scope}.emptyState.${currentDiscountClass.toLowerCase()}`,
scope: `${scope}.emptyState.${targetDiscountClass.toLowerCase()}`,
})}{' '}
<Link
url={`https://help.shopify.com/${i18n.locale}/manual/discounts/combining-discounts`}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ describe('<HelpText />', () => {
);

expect(helpText).toContainReactText(
'3 other product discounts are currently set to combine.If an item is eligible for multiple discounts, only the largest discount will apply.',
'3 product discounts are set to combine.If an item is eligible for multiple discounts, only the largest will apply.',
);
});
});
Expand All @@ -69,15 +69,15 @@ describe('<HelpText />', () => {
it.each([
[
DiscountClass.Product,
'No product discounts are currently set to combine. To let customers use more than one discount, set up at least one product discount that combines with product discounts. Learn more',
'No product discounts are set to combine. To let customers use more than one discount, set up at least one product discount that combines with product discounts. Learn more',
],
[
DiscountClass.Order,
'No order discounts are currently set to combine. To let customers use more than one discount, set up at least one order discount that combines with order discounts. Learn more',
'No product discounts are set to combine. To let customers use more than one discount, set up at least one product discount that combines with order discounts. Learn more',
],
[
DiscountClass.Shipping,
'No shipping discounts are currently set to combine. To let customers use more than one discount, set up at least one shipping discount that combines with shipping discounts. Learn more',
'No product discounts are set to combine. To let customers use more than one discount, set up at least one product discount that combines with shipping discounts. Learn more',
],
])(
'renders empty state content when no %s discounts are set to combine with current discount',
Expand Down
65 changes: 41 additions & 24 deletions src/components/CombinationCard/tests/CombinationCard.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import {LegacyCard as Card, ChoiceList} from '@shopify/polaris';
import {Banner, LegacyCard as Card, ChoiceList} from '@shopify/polaris';
import {mockField, mountWithApp} from 'tests/utilities';
import {composeGid} from '@shopify/admin-graphql-api-utilities';

Expand Down Expand Up @@ -41,20 +41,6 @@ describe('<CombinationCard />', () => {
) : null,
};

const mockProductOption = {
label: 'Product discounts',
value: DiscountClass.Product,
renderChildren: (isSelected: boolean) =>
isSelected ? (
<HelpText
currentDiscountClass={mockProps.discountClass}
targetDiscountClass={DiscountClass.Product}
count={mockProps.combinableDiscountCounts!.productDiscountsCount}
currentDiscountName={mockProps.discountDescriptor}
currentDiscountId={mockProps.discountId}
/>
) : null,
};
const mockOrderOption = {
label: 'Order discounts',
value: DiscountClass.Order,
Expand Down Expand Up @@ -153,9 +139,9 @@ describe('<CombinationCard />', () => {

it.each`
discountClass | expectedChoiceOptions
${DiscountClass.Product} | ${[mockProductOtherOption, mockShippingOption]}
${DiscountClass.Order} | ${[mockShippingOption]}
${DiscountClass.Shipping} | ${[mockProductOption, mockOrderOption]}
${DiscountClass.Product} | ${[{label: 'Product discounts', value: 'PRODUCT'}, {label: 'Order discounts', value: 'ORDER'}, {label: 'Shipping discounts', value: 'SHIPPING'}]}
${DiscountClass.Order} | ${[{label: 'Product discounts', value: 'PRODUCT'}, {label: 'Order discounts', value: 'ORDER'}, {label: 'Shipping discounts', value: 'SHIPPING'}]}
${DiscountClass.Shipping} | ${[{label: 'Product discounts', value: 'PRODUCT'}, {label: 'Order discounts', value: 'ORDER'}]}
`(
'renders choices for $discountClass discount',
({discountClass, expectedChoiceOptions}) => {
Expand All @@ -164,12 +150,14 @@ describe('<CombinationCard />', () => {
);

expect(combinationCard).toContainReactComponent(ChoiceList, {
choices: expectedChoiceOptions.map(
(choice: {label: string; value: DiscountClass}) => ({
value: choice.value,
label: choice.label,
renderChildren: expect.any(Function),
}),
choices: expect.arrayContaining(
expectedChoiceOptions.map((choice: {label: string; value: string}) =>
expect.objectContaining({
value: choice.value,
label: choice.label,
renderChildren: expect.any(Function),
}),
),
),
});
},
Expand Down Expand Up @@ -275,4 +263,33 @@ describe('<CombinationCard />', () => {
currentDiscountId: mockDiscountId,
});
});

it.each`
discountClass | selectedChoices | bannerCount
${DiscountClass.Order} | ${['PRODUCT']} | ${1}
${DiscountClass.Order} | ${['ORDER']} | ${1}
${DiscountClass.Order} | ${['SHIPPING']} | ${0}
${DiscountClass.Product} | ${['ORDER']} | ${1}
${DiscountClass.Product} | ${['PRODUCT', 'SHIPPING']} | ${0}
${DiscountClass.Shipping} | ${['PRODUCT', 'ORDER']} | ${0}
`(
'renders warning banner conditionally for $discountClass discount and selected choices $selectedChoices',
({discountClass, selectedChoices, bannerCount}) => {
const mockPropsWithSelectedChoices = {
...mockProps,
discountClass,
combinableDiscountTypes: mockField({
productDiscounts: selectedChoices.includes('PRODUCT'),
orderDiscounts: selectedChoices.includes('ORDER'),
shippingDiscounts: selectedChoices.includes('SHIPPING'),
}),
};

const combinationCard = mountWithApp(
<CombinationCard {...mockPropsWithSelectedChoices} />,
);

expect(combinationCard.findAll(Banner)).toHaveLength(bannerCount);
},
);
});
Original file line number Diff line number Diff line change
@@ -1,19 +1,35 @@
import React from 'react';
import {Provider} from '../../foundation/Provider';
import CombinationCard from './CombinationCardPattern';
import {DiscountClass} from '../../..';

// eslint-disable-next-line import/no-default-export, import/no-anonymous-default-export
export default {
title: 'CombinationCard pattern',
parameters: {
layout: 'fullscreen',
},
component: CombinationCard,
argTypes: {
discountClass: {
control: {
type: 'select',
options: [
DiscountClass.Product,
DiscountClass.Order,
DiscountClass.Shipping,
],
},
},
},
};

const CombinationCardPattern = () => (
const Template = (args) => (
<Provider>
<CombinationCard />
<CombinationCard {...args} />
</Provider>
);

export {CombinationCardPattern};
export const ProductDiscount = (args) => <Template discountDescriptor="My cool product discount" {...args} discountClass={DiscountClass.Product} />
export const OrderDiscount = (args) => <Template discountDescriptor="My cool order discount" {...args} discountClass={DiscountClass.Order} />
export const ShippingDiscount = (args) => <Template discountDescriptor="My cool shipping discount" {...args} discountClass={DiscountClass.Shipping} />
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import React, {useState} from 'react';

import {Page} from '@shopify/polaris';
import {CombinationCard, DiscountClass} from '../../..';
import {CombinableDiscountTypes} from '../../../types';

export default function CombinationCardPattern() {
export default function CombinationCardPattern({discountClass, discountDescriptor}) {
const [combinesWith, setCombinesWith] = useState<CombinableDiscountTypes>({
orderDiscounts: false,
productDiscounts: false,
Expand All @@ -23,8 +22,8 @@ export default function CombinationCardPattern() {
productDiscountsCount: 3,
shippingDiscountsCount: 0,
}}
discountClass={DiscountClass.Product}
discountDescriptor="My cool discount"
discountClass={discountClass}
discountDescriptor={discountDescriptor}
/>
</Page>
);
Expand Down

0 comments on commit 31ee8cc

Please sign in to comment.