App reviews, troubleshooting, and recommendations
We're moving the community! Starting July 7, the current community will be read-only for approx. 2 weeks. You can browse content, but posting will be temporarily unavailable. Learn more
I am creating option for user to enter the tag they want to apply the discount on app UI.Adding discount option works.This is my code if there is anyone who can guide on what am doing wrong.
ReferenceError: tags are not defined
query RunInput ($inputTags: [String!]!) { cart { buyerIdentity { customer { metafield(namespace: "custom", key: "pmore") { value } } } lines { quantity merchandise { ...on ProductVariant { id product{ hasAnyTag(tags: $inputTags) } __typename } } } } discountNode { metafield(namespace: "$app:2FORDEALC", key: "function-configuration") { value } } }
// @ts-nocheck import { DiscountApplicationStrategy } from "../generated/api"; /** * @typedef {import("../generated/api").RunInput} RunInput * @typedef {import("../generated/api").FunctionRunResult} FunctionRunResult * @typedef {import("../generated/api").Target} Target * @typedef {import("../generated/api").ProductVariant} ProductVariant */ /** * @type {FunctionRunResult} */ const EMPTY_DISCOUNT = { discountApplicationStrategy: DiscountApplicationStrategy.First, discounts: [], }; /** * @param {RunInput} input * @returns {FunctionRunResult} */ export function run(input) { /** * @type {{ * quantity: number * }} */ const configuration = JSON.parse( input?.discountNode?.metafield?.value ?? "{}", ); if (!configuration.fixedAmountOff) { return EMPTY_DISCOUNT; } /** * @type {Array<Target>} */ const targets = []; /** * @type {number} */ let Totalquan = 0; /** * @type {number} */ const DisAmount = configuration.fixedAmountOff; /** * @type {number} */ let TotalDiscount = 0; // Type check for optional chaining // Get total tag matches input.cart.lines.forEach((line) => { if (line.merchandise.__typename === "ProductVariant") { // Type check for optional chaining if (line.merchandise.product?.hasAnyTag) { Totalquan += line.quantity; // Type inference for addition } } }); if (Totalquan % 2 === 0) { TotalDiscount = DisAmount * Totalquan; } else { TotalDiscount = DisAmount * (Totalquan - 1); } // Uneven counter /** * @type {number} */ let uc = 0; if (Totalquan % 2 === 0) { // Even line item discount input.cart.lines.forEach((line) => { if (line.merchandise.__typename === "ProductVariant") { if (line.merchandise.product?.hasAnyTag) { const product = /** @type {ProductVariant} */ (line.merchandise); const productid = product.id; let productquantity = line.quantity; targets.push({ productVariant: { id: productid, quantity: productquantity, }, }); } } }); } else { // Uneven line item discount input.cart.lines.forEach((line) => { if (line.merchandise.__typename === "ProductVariant") { if (line.merchandise.product?.hasAnyTag) { const product = /** @type {ProductVariant} */ (line.merchandise); const productid = product.id; let productquantity = line.quantity; if (productquantity % 2 === 1 && uc === 0) { productquantity -= 1; } uc++; if (productquantity !== 0) { targets.push({ productVariant: { id: productid, quantity: productquantity, }, }); } } } }); } if (!targets.length) { console.error("No cart lines qualify for volume discount."); return EMPTY_DISCOUNT; } return { discounts: [ { targets, value: { fixedAmount: { amount : TotalDiscount } } }, ], discountApplicationStrategy: DiscountApplicationStrategy.First, }; }
import { useEffect, useMemo } from "react"; import { json } from "@remix-run/node"; import { useForm, useField } from "@shopify/react-form"; import { CurrencyCode } from "@shopify/react-i18n"; import { Form, useActionData, useNavigation, useSubmit, } from "@remix-run/react"; import { ActiveDatesCard, CombinationCard, DiscountClass, DiscountMethod, MethodCard, DiscountStatus, RequirementType, SummaryCard, UsageLimitsCard, } from "@shopify/discount-app-components"; import { Banner, Card, Text, Layout, Page, PageActions, TextField, BlockStack, Box, } from "@shopify/polaris"; import shopify from "../shopify.server"; export const action = async ({ params, request }) => { const { functionId } = params; const { admin } = await shopify.authenticate.admin(request); const formData = await request.formData(); const { title, method, code, combinesWith, usageLimit, appliesOncePerCustomer, startsAt, endsAt, configuration, } = JSON.parse(formData.get("discount")); const baseDiscount = { functionId, title, combinesWith, startsAt: new Date(startsAt), endsAt: endsAt && new Date(endsAt), }; const metafields = [ { namespace: "$app:2FORDEALC", key: "function-configuration", type: "json", value: JSON.stringify({ fixedAmountOff: configuration.fixedAmountOff, tags: configuration.tags.split(",").map(tag => tag.trim()), }), }, ]; if (method === DiscountMethod.Code) { const baseCodeDiscount = { ...baseDiscount, title: code, code, usageLimit, appliesOncePerCustomer, }; const response = await admin.graphql( `#graphql mutation CreateCodeDiscount($discount: DiscountCodeAppInput!) { discountCreate: discountCodeAppCreate(codeAppDiscount: $discount) { codeAppDiscount{ discountId } userErrors { code message field } } }`, { variables: { discount: { ...baseCodeDiscount, metafields, }, }, }, ); const responseJson = await response.json(); const errors = responseJson.data.discountCreate?.userErrors; const discount = responseJson.data.discountCreate?.codeAppDiscount; return json({ errors, discount: { ...discount, functionId } }); } else { const response = await admin.graphql( `#graphql mutation CreateAutomaticDiscount($discount: DiscountAutomaticAppInput!) { discountCreate: discountAutomaticAppCreate(automaticAppDiscount: $discount) { automaticAppDiscount { discountId } userErrors { code message field } } }`, { variables: { discount: { ...baseDiscount, metafields, }, }, }, ); const responseJson = await response.json(); const errors = responseJson.data.discountCreate?.userErrors; return json({ errors }); } }; export default function VolumeNew() { const submitForm = useSubmit(); const actionData = useActionData(); const navigation = useNavigation(); const todaysDate = useMemo(() => new Date(), []); const isLoading = navigation.state === "submitting"; const currencyCode = CurrencyCode.Cad; const submitErrors = actionData?.errors || []; const returnToDiscounts = () => open("shopify://admin/discounts", "_top"); useEffect(() => { if (actionData?.errors.length === 0 && actionData?.discount) { returnToDiscounts(); } }, [actionData]); const { fields: { discountTitle, discountCode, discountMethod, combinesWith, requirementType, requirementSubtotal, requirementfixedAmountOff, usageLimit, appliesOncePerCustomer, startDate, endDate, configuration, }, submit, } = 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"), requirementfixedAmountOff: useField("0"), usageLimit: useField(null), appliesOncePerCustomer: useField(false), startDate: useField(todaysDate), endDate: useField(null), configuration: { fixedAmountOff: useField("1"), tags: useField(""), }, }, onSubmit: async (form) => { const discount = { title: form.discountTitle, method: form.discountMethod, code: form.discountCode, combinesWith: form.combinesWith, usageLimit: form.usageLimit == null ? null : parseInt(form.usageLimit), appliesOncePerCustomer: form.appliesOncePerCustomer, startsAt: form.startDate, endsAt: form.endDate, configuration: { fixedAmountOff: parseInt(form.configuration.fixedAmountOff), }, }; submitForm({ discount: JSON.stringify(discount) }, { method: "post" }); return { status: "success" }; }, }); const errorBanner = submitErrors.length > 0 ? ( <Layout.Section> <Banner tone="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 ( <Page> <ui-title-bar title="2 For Discount"> <button variant="breadcrumb" onClick={returnToDiscounts}> Discounts </button> <button variant="primary" onClick={submit}> Save discount </button> </ui-title-bar> <Layout> {errorBanner} <Layout.Section> <Form method="post"> <BlockStack align="space-around" gap="200"> <MethodCard title="2 For Deal C" discountTitle={discountTitle} discountClass={DiscountClass.Product} discountCode={discountCode} discountMethod={discountMethod} /> <Box paddingBlockEnd="300"> <Card> <BlockStack> <Text variant="headingMd" as="h2"> Amount Off SKU (Rands) </Text> <TextField label="Discount per item (1)" autoComplete="on" {...configuration.fixedAmountOff} /> </BlockStack> </Card> </Box> <Box paddingBlockEnd="300"> <Card> <BlockStack> <Text variant="headingMd" as="h2"> Tags for Discount (e.g., "2FORDEALA") </Text> <TextField label="Tags" placeholder="Enter tags separated by commas" value={tags} autoComplete="on" {...configuration.tags} /> </BlockStack> </Card> </Box> {discountMethod.value === DiscountMethod.Code && ( <UsageLimitsCard totalUsageLimit={usageLimit} oncePerCustomer={appliesOncePerCustomer} /> )} <CombinationCard combinableDiscountTypes={combinesWith} discountClass={DiscountClass.Product} discountDescriptor={"Discount"} /> <ActiveDatesCard startDate={startDate} endDate={endDate} timezoneAbbreviation="EST" /> </BlockStack> </Form> </Layout.Section> <Layout.Section variant="oneThird"> <SummaryCard header={{ discountMethod: discountMethod.value, discountDescriptor: discountMethod.value === DiscountMethod.Automatic ? discountTitle.value : discountCode.value, appDiscountType: "Volume", isEditing: false, }} performance={{ status: DiscountStatus.Scheduled, usageCount: 0, isEditing: false, }} minimumRequirements={{ requirementType: requirementType.value, subtotal: requirementSubtotal.value, fixedAmountOff: requirementfixedAmountOff.value, currencyCode: currencyCode, }} usageLimits={{ oncePerCustomer: appliesOncePerCustomer.value, totalUsageLimit: usageLimit.value, }} activeDates={{ startDate: startDate.value, endDate: endDate.value, }} /> </Layout.Section> <Layout.Section> <PageActions primaryAction={{ content: "Save discount", onAction: submit, loading: isLoading, }} secondaryActions={[ { content: "Discard", onAction: returnToDiscounts, }, ]} /> </Layout.Section> </Layout> </Page> ); }
#functions #shopify