Hi folks! I am trying to follow this documentation to set up discount using shopify functions
https://shopify.dev/docs/apps/discounts/experience/getting-started
It works well for me untill I react to the last step where I am trying to create a UI
https://shopify.dev/docs/apps/discounts/experience/ui
I am getting and error in console that appears to be in new.jsx file that says “No i18n was provided. Your application must be wrapped in an component”
Any idea what might be the issue? this is my new.jsx file
import { useParams } from "react-router-dom";
import { useForm, useField } from "@shopify/react-form";
import { CurrencyCode } from "@shopify/react-i18n";
import { Redirect } from "@shopify/app-bridge/actions";
import { useAppBridge } from "@shopify/app-bridge-react";
import enTranslations from '@shopify/polaris/locales/en.json';
import {AppProvider} from '@shopify/polaris';
import {
ActiveDatesCard,
CombinationCard,
DiscountClass,
DiscountMethod,
MethodCard,
DiscountStatus,
RequirementType,
SummaryCard,
UsageLimitsCard,
onBreadcrumbAction,
} from "@shopify/discount-app-components";
import {
Banner,
Card,
Layout,
Page,
TextField,
Stack,
PageActions,
} from "@shopify/polaris";
import { data } from "@shopify/app-bridge/actions/Modal";
import { useAuthenticatedFetch } from "../../../hooks";
const todaysDate = new Date();
// Metafield that will be used for storing function configuration
const METAFIELD_NAMESPACE = "$app:s-functions-discount";
const METAFIELD_CONFIGURATION_KEY = "s-function-configuration";
export default function VolumeNew() {
// Read the function ID from the URL
const { functionId } = useParams();
const app = useAppBridge();
const redirect = Redirect.create(app);
const currencyCode = CurrencyCode.Cad;
const authenticatedFetch = useAuthenticatedFetch();
// Define base discount form fields
const {
fields: {
discountTitle,
discountCode,
discountMethod,
combinesWith,
requirementType,
requirementSubtotal,
requirementQuantity,
usageTotalLimit,
usageOncePerCustomer,
startDate,
endDate,
configuration,
},
submit,
submitting,
dirty,
reset,
submitErrors,
makeClean,
} = useForm({
fields: {
discountTitle: useField(""),
discountMethod: useField(DiscountMethod.Code),
discountCode: useField(""),
combinesWith: useField({
orderDiscounts: false,
productDiscounts: false,
shippingDiscounts: false,
}),
requirementType: useField(RequirementType.None),
requirementSubtotal: useField("0"),
requirementQuantity: useField("0"),
usageTotalLimit: useField(null),
usageOncePerCustomer: useField(false),
startDate: useField(todaysDate),
endDate: useField(null),
configuration: { // Add quantity and percentage configuration to form data
quantity: useField('1'),
percentage: useField('0'),
}
},
onSubmit: async (form) => {
// Create the discount using the added express endpoints
const discount = {
functionId,
combinesWith: form.combinesWith,
startsAt: form.startDate,
endsAt: form.endDate,
metafields: [
{
namespace: METAFIELD_NAMESPACE,
key: METAFIELD_CONFIGURATION_KEY,
type: "json",
value: JSON.stringify({ // Populate metafield from form data
quantity: parseInt(form.configuration.quantity),
percentage: parseFloat(form.configuration.percentage),
}),
},
],
};
let response;
if (form.discountMethod === DiscountMethod.Automatic) {
response = await authenticatedFetch("/api/discounts/automatic", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
discount: {
...discount,
title: form.discountTitle,
},
}),
});
} else {
response = await authenticatedFetch("/api/discounts/code", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
discount: {
...discount,
title: form.discountCode,
code: form.discountCode,
},
}),
});
}
const data = (await response.json()).data;
const remoteErrors = data.discountCreate.userErrors;
if (remoteErrors.length > 0) {
return { status: "fail", errors: remoteErrors };
}
redirect.dispatch(Redirect.Action.ADMIN_SECTION, {
name: Redirect.ResourceType.Discount,
});
return { status: "success" };
},
});
const errorBanner =
submitErrors.length > 0 ? (
<Layout.Section>
<Banner status="critical">
<p>There were some issues with your form submission:</p>
<ul>
{submitErrors.map(({ message, field }, index) => {
return (
<li key={`${message}${index}`}>
{field.join(".")} {message}
</li>
);
})}
</ul>
</Banner>
</Layout.Section>
) : null;
return (
// Render a discount form using Polaris components and the discount app components
<Page
title="Create volume discount"
breadcrumbs={[
{
content: "Discounts",
onAction: () => onBreadcrumbAction(redirect, true),
},
]}
primaryAction={{
content: "Save",
onAction: submit,
disabled: !dirty,
loading: submitting,
}}
>
<Layout>
{errorBanner}
<Layout.Section>
<form onSubmit={submit}>
<MethodCard
title="Volume"
discountTitle={discountTitle}
discountClass={DiscountClass.Product}
discountCode={discountCode}
discountMethod={discountMethod}
/>
<Card title="Volume">
<Card.Section>
<Stack>
<TextField label="Minimum quantity" {...configuration.quantity} />
<TextField label="Discount percentage" {...configuration.percentage} suffix="%" />
</Stack>
</Card.Section>
</Card>
{discountMethod.value === DiscountMethod.Code && (
<UsageLimitsCard
totalUsageLimit={usageTotalLimit}
oncePerCustomer={usageOncePerCustomer}
/>
)}
<CombinationCard
combinableDiscountTypes={combinesWith}
discountClass={DiscountClass.Product}
discountDescriptor={
discountMethod.value === DiscountMethod.Automatic
? discountTitle.value
: discountCode.value
}
/>
<ActiveDatesCard
startDate={startDate}
endDate={endDate}
timezoneAbbreviation="EST"
/>
</form>
</Layout.Section>
<Layout.Section secondary>
<SummaryCard
header={{
discountMethod: discountMethod.value,
discountDescriptor:
discountMethod.value === DiscountMethod.Automatic
? discountTitle.value
: discountCode.value,
appDiscountType: "Volume",
isEditing: false,
}}
performance={{
status: DiscountStatus.Scheduled,
usageCount: 0,
}}
minimumRequirements={{
requirementType: requirementType.value,
subtotal: requirementSubtotal.value,
quantity: requirementQuantity.value,
currencyCode: currencyCode,
}}
usageLimits={{
oncePerCustomer: usageOncePerCustomer.value,
totalUsageLimit: usageTotalLimit.value,
}}
activeDates={{
startDate: startDate.value,
endDate: endDate.value,
}}
/>
</Layout.Section>
<Layout.Section>
<PageActions
primaryAction={{
content: "Save discount",
onAction: submit,
disabled: !dirty,
loading: submitting,
}}
secondaryActions={[
{
content: "Discard",
onAction: () => onBreadcrumbAction(redirect, true),
},
]}
/>
</Layout.Section>
</Layout>
</Page>
);
}

