Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Code for Cart Discount #1316

Open
osseonews opened this issue Mar 31, 2024 · 0 comments
Open

Code for Cart Discount #1316

osseonews opened this issue Mar 31, 2024 · 0 comments

Comments

@osseonews
Copy link

We developed the code to apply a discount in the cart. I would normally put in a pull request for this type of thing, but our code now has so many conflicts, it would be too complicated. I figured I'd just post the basic code here and hopefully it will help people who are looking to get this functionality. Adapt to your base code, obviously.

  1. Add a Mutation in mutations/cart.ts
export const CART_DISCOUNT_CODE_UPDATE_MUTATION = /* GraphQL */ `
  mutation cartDiscountCodesUpdate ($cartId: ID!
    $discountCodes: [String!]) {
    cartDiscountCodesUpdate(cartId: $cartId, discountCodes: $discountCodes) {
      cart {
        ...cart
      }
    }
  }
  ${cartFragment}
`;
  1. Add a Function in lib/shopify/index where all the functions are:
export async function updateDiscounts(cartId: string, discounts: string[]): Promise<Cart> {
  const res = await shopifyFetch<ShopifyDiscountCartOperation>({
    query: CART_DISCOUNT_CODE_UPDATE_MUTATION,
    variables: {
      cartId,
      discountCodes: discounts
    },
    cache: 'no-store'
  });
  return reshapeCart(res.body.data.cartDiscountCodesUpdate.cart);
}
  1. Add an Action in cart/actions.ts
export async function applyDiscount(
  prevState: any,
  formData: FormData
) {
  //console.log ("Form Data", formData)
  const cartId = cookies().get('cartId')?.value;

  if (!cartId) {
    return 'Missing cart ID';
  }
    const schema = z.object({
    discountCode: z.string().min(1),
  });
  const parse = schema.safeParse({
    discountCode: formData.get("discountCode"),
  });

  if (!parse.success) {
    return "Error applying discount. Discount code required.";
  }

  const data = parse.data;
  let discountCodes = []; // Create a new empty array - actually this array should be the current array of discount codes, but as we only allow one code now, we create an empty array
  discountCodes.push(data.discountCode); // Push the string into the array
  // Ensure the discount codes are unique - this is not really necessary now, because we are only using one code
  const uniqueCodes = discountCodes.filter((value, index, array) => {
    return array.indexOf(value) === index;
  });

  try {
    await updateDiscounts(cartId, uniqueCodes);
    //close cart and have tooltip for c
    revalidateTag(TAGS.cart);
  } catch (e) {
    return 'Error applying discount';
  }
}

export async function removeDiscount(
  prevState: any,
  payload: {
    discount: string;
    discounts: string[];
  }
) {
  //console.log ("payload", payload)
  const cartId = cookies().get('cartId')?.value;

  if (!cartId) {
    return 'Missing cart ID';
  }
  const code = payload?.discount
  const codes = payload?.discounts ?? [] //the entire array of discounts
  if (!code) {
    return "Error removing discount. Discount code required.";
  }
  let discountCodes = codes; 
  //remove the code from the array and return the array
  let newCodes = discountCodes.filter(item => item !== code);

  try {
    await updateDiscounts(cartId, newCodes);
    //close cart and have tooltip for c
    revalidateTag(TAGS.cart);
  } catch (e) {
    return 'Error applying discount';
  }
}
  1. Add a Form Component in your Modal for the cart that is something like this (we use Shadcn, so many our components come from there)
function SubmitButton(props: Props) {
  const { pending } = useFormStatus();
  const buttonClasses =
    'mt-3 inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 bg-primary text-primary-foreground hover:bg-primary/90 h-9 rounded-md px-3';
  const disabledClasses = 'cursor-not-allowed opacity-60 hover:opacity-60';
  const message = props?.message;
  return (
    <>
      {message && (
        <Alert className="my-5" variant="destructive">
          <ExclamationTriangleIcon className="mr-2 h-4 w-4" />
          <AlertTitle>{message}</AlertTitle>
        </Alert>
      )}
      {pending ? "" : <input type="text" name="discountCode" placeholder="Discount code" required />}

      <button
        onClick={(e: React.FormEvent<HTMLButtonElement>) => {
          if (pending) e.preventDefault();
        }}
        aria-label="Add Discount Code"
        aria-disabled={pending}
        className={clsx(buttonClasses, {
          'hover:opacity-90': true,
          [disabledClasses]: pending
        })}
      >
        <div className="w-full">
          {pending ? <LoadingDots className="bg-secondary text-white mb-3" /> : null}
        </div>
        {pending ? '' : 'Apply Discount'}
      </button>
    </>
  );
}

export function ApplyCartDiscount({ cart }: { cart: Cart }) {
  const [message, formAction] = useFormState(applyDiscount, null);
  //console.log('Cart Discount Codes', cart?.discountCodes);
  //filter the discount codes to remove those where applicablce is false and then jsut get the code in array
  //if we have a discount code we don't show the form
  const filteredCodes =
    cart?.discountCodes && cart?.discountCodes.length
      ? cart?.discountCodes.filter((code) => code.applicable).map((code) => code.code)
      : [];

  return (
    <>
      {filteredCodes && filteredCodes.length > 0 ? (
        <div>
          {filteredCodes.map((code, index) => (
            <Badge key={index} variant="secondary">
              <TagIcon className="mr-2 inline-block h-5 w-5" />
              {code}
              <DeleteDiscountButton item={code} items={filteredCodes}/>
            </Badge>
          ))}
        </div>
      ) : (
        <form action={formAction}>
          <SubmitButton message={message} />
          <p aria-live="polite" className="sr-only" role="status">
            {message}
          </p>
        </form>
      )}
    </>
  );
}
function SubmitButton() {
  const { pending } = useFormStatus();

  return (
    <button
      type="submit"
      onClick={(e: React.FormEvent<HTMLButtonElement>) => {
        if (pending) e.preventDefault();
      }}
      aria-label="Remove Discount"
      aria-disabled={pending}
      className={clsx(
        'ease flex',
        {
          'cursor-not-allowed px-0': pending
        }
      )}
    >
      {pending ? (
        <LoadingDots className="bg-primary" />
      ) : (
        <XCircleIcon className="mx-5 text-primary hover:border-neutral-800 hover:opacity-80 h-5 w-5" />
      )}
    </button>
  );
}

export function DeleteDiscountButton({ item,items}: { item: string, items:string[]}) {
  const [message, formAction] = useFormState(removeDiscount, null);
  const payload = {
    discount: item,
    discounts: items,
  };
  const actionWithVariant = formAction.bind(null, payload);

  return (
    <form action={actionWithVariant}>
      <SubmitButton />
      <p aria-live="polite" className="sr-only" role="status">
        {message}
      </p>
    </form>
  );
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant