diff --git a/components/cart/actions.ts b/components/cart/actions.ts index fa2c34d37..982193349 100644 --- a/components/cart/actions.ts +++ b/components/cart/actions.ts @@ -81,3 +81,55 @@ export async function updateItemQuantity( return 'Error updating item quantity'; } } + +export async function calculateDiscounts(cart: any) { + const discountGroups = [ + { + name: 'Tier 2', + discount: { + amount: 0.1, + minimumSpent: 150 + } + }, + { + name: 'Tier 1', + discount: { + amount: 0.05, + minimumSpent: 120 + } + } + ]; + + const subTotal = cart?.cost.subtotalAmount.amount; + const currencyCode = cart?.cost.subtotalAmount.currencyCode; + + const discountGroupsSorted = discountGroups.sort( + (a, b) => a.discount.minimumSpent - b.discount.minimumSpent + ); + const minSpent = Math.max(...discountGroupsSorted.map((group) => group.discount.minimumSpent)); + const eligibleDiscount = discountGroupsSorted.filter( + (group) => subTotal >= group.discount.minimumSpent + ); + const finalDiscount = eligibleDiscount.length + ? Math.max(...eligibleDiscount.map((group) => group.discount.amount)) + : 0; + const closestNextTier = discountGroupsSorted + .filter((group) => group.discount.minimumSpent > subTotal) + .shift(); + + const spentToNextDiscount = closestNextTier + ? closestNextTier?.discount.minimumSpent - subTotal + : 0; + const nextDiscount = closestNextTier?.discount.amount; + const discountAmount = finalDiscount ? finalDiscount * 100 : 0; + + return { + discountAmount, + spentToNextDiscount, + nextDiscount, + discountGroups: discountGroupsSorted, + minSpent, + subTotal, + currencyCode + }; +} diff --git a/components/cart/index.tsx b/components/cart/index.tsx index 3e250ba93..82fd10d90 100644 --- a/components/cart/index.tsx +++ b/components/cart/index.tsx @@ -1,14 +1,16 @@ import { getCart } from 'lib/shopify'; import { cookies } from 'next/headers'; +import { calculateDiscounts } from './actions'; import CartModal from './modal'; export default async function Cart() { const cartId = cookies().get('cartId')?.value; let cart; - + let tieredDiscounts; if (cartId) { cart = await getCart(cartId); + tieredDiscounts = await calculateDiscounts(cart); } - return ; + return ; } diff --git a/components/cart/modal.tsx b/components/cart/modal.tsx index f569afc79..83d319a68 100644 --- a/components/cart/modal.tsx +++ b/components/cart/modal.tsx @@ -13,60 +13,19 @@ import CloseCart from './close-cart'; import { DeleteItemButton } from './delete-item-button'; import { EditItemQuantityButton } from './edit-item-quantity-button'; import OpenCart from './open-cart'; +import TierDiscount from './tier-discount'; type MerchandiseSearchParams = { [key: string]: string; }; -function TierDiscount({ subTotal }: { subTotal: number }) { - const discountGroups = [ - { - name: 'Tier 1', - discount: { - amount: 0.05, - minimumSpent: 10 - } - }, - { - name: 'Tier 2', - discount: { - amount: 0.1, - minimumSpent: 20 - } - } - ]; - const highestMinimumSpent = discountGroups.reduce((acc, group) => { - if (group.discount.minimumSpent > acc) { - return group.discount.minimumSpent; - } else { - return acc; - } - }, 0); - const eligibleDiscountGroups = discountGroups.filter( - (group) => subTotal >= group.discount.minimumSpent - ); - const finalDiscount = eligibleDiscountGroups.reduce((prev, current) => { - // Compare and return the highest discount group based on your criteria - return prev.discount.minimumSpent > current.discount.minimumSpent ? prev : current; - }); - - const discountAmount = finalDiscount.discount.amount * 100; - - return ( -
-

Spent more

-
-
-
-

{discountAmount}% off

-
- ); -} - -export default function CartModal({ cart }: { cart: Cart | undefined }) { +export default function CartModal({ + cart, + tieredDiscount +}: { + cart: Cart | undefined; + tieredDiscount: any; +}) { const [isOpen, setIsOpen] = useState(false); const quantityRef = useRef(cart?.totalQuantity); const openCart = () => setIsOpen(true); @@ -127,7 +86,7 @@ export default function CartModal({ cart }: { cart: Cart | undefined }) { ) : (
- +
    {cart.lines.map((item, i) => { const merchandiseSearchParams = {} as MerchandiseSearchParams; diff --git a/components/cart/tier-discount.tsx b/components/cart/tier-discount.tsx new file mode 100644 index 000000000..abcd9abda --- /dev/null +++ b/components/cart/tier-discount.tsx @@ -0,0 +1,66 @@ +import Price from 'components/price'; + +type DiscountGroup = { + discount: { + amount: number; + minimumSpent: number; + }; +}; + +export default function TierDiscount({ tieredDiscount }: { tieredDiscount: any }) { + if (!tieredDiscount) { + return; + } + + const { + discountGroups, + discountAmount, + spentToNextDiscount, + minSpent, + nextDiscount, + subTotal, + currencyCode + } = tieredDiscount; + + return ( + <> +
    +
    + {spentToNextDiscount > 0 && ( + <> + Spent more than + + + )} + {nextDiscount && to get {nextDiscount * 100}%} +
    +
    +
    + {discountGroups.map((group: DiscountGroup, i: number) => ( +
    + ))} +
    +

    + {spentToNextDiscount === 0 ? ( + Congratulations you got {discountAmount}% Discounts! + ) : ( + You got : {discountAmount}% off + )} +

    +
    + + ); +} diff --git a/lib/shopify/fragments/discount-metaobject.ts b/lib/shopify/fragments/discount-metaobject.ts new file mode 100644 index 000000000..613e3b7f6 --- /dev/null +++ b/lib/shopify/fragments/discount-metaobject.ts @@ -0,0 +1,11 @@ +const discountMetaobject = /* GraphQL */ ` + fragment metaobject on Metaobject { + fields { + value + key + } + handle + } +`; + +export default discountMetaobject; diff --git a/lib/shopify/index.ts b/lib/shopify/index.ts index e8b6637c8..db57ed191 100644 --- a/lib/shopify/index.ts +++ b/lib/shopify/index.ts @@ -16,6 +16,7 @@ import { getCollectionQuery, getCollectionsQuery } from './queries/collection'; +import { getDiscountMetaobjectsQuery } from './queries/discount-metaobject'; import { getMenuQuery } from './queries/menu'; import { getPageQuery, getPagesQuery } from './queries/page'; import { @@ -39,6 +40,7 @@ import { ShopifyCollectionProductsOperation, ShopifyCollectionsOperation, ShopifyCreateCartOperation, + ShopifyDiscountMetaobjectsOperation, ShopifyMenuOperation, ShopifyPageOperation, ShopifyPagesOperation, @@ -448,3 +450,12 @@ export async function revalidate(req: NextRequest): Promise { return NextResponse.json({ status: 200, revalidated: true, now: Date.now() }); } + +export async function getDiscountMetaobjects() { + const res = await shopifyFetch({ + query: getDiscountMetaobjectsQuery, + cache: 'no-store' + }); + + return res.body.data.metaobjects; +} diff --git a/lib/shopify/queries/discount-metaobject.ts b/lib/shopify/queries/discount-metaobject.ts new file mode 100644 index 000000000..d6075221e --- /dev/null +++ b/lib/shopify/queries/discount-metaobject.ts @@ -0,0 +1,12 @@ +import discountMetaobject from '../fragments/discount-metaobject'; + +export const getDiscountMetaobjectsQuery = /* GraphQL */ ` + query getDiscountMetaobjects { + metaobjects(type: "dynamic_discount", first: 10) { + nodes { + ...metaobject + } + } + } + ${discountMetaobject} +`; diff --git a/lib/shopify/types.ts b/lib/shopify/types.ts index 23dc02d46..1c132d007 100644 --- a/lib/shopify/types.ts +++ b/lib/shopify/types.ts @@ -61,6 +61,19 @@ export type Page = { updatedAt: string; }; +type Field = { + key: string; + value: string; +}; + +type MetaObject = { + fields: Field[]; +}; + +export type DiscountMetaobject = MetaObject & { + handle: string; +}; + export type Product = Omit & { variants: ProductVariant[]; images: Image[]; @@ -263,3 +276,14 @@ export type ShopifyProductsOperation = { sortKey?: string; }; }; + +export type ShopifyDiscountMetaobjectsOperation = { + data: { + metaobjects: Connection; + }; + variables: { + query?: string; + reverse?: boolean; + sortKey?: string; + }; +};