From 4ea96cc6e5aaee536e3d628f0903f37a13bb85ed Mon Sep 17 00:00:00 2001 From: Sol Irvine Date: Thu, 24 Aug 2023 09:59:14 -0700 Subject: [PATCH] add multiple items to cart at once --- app/[locale]/product/[handle]/page.tsx | 6 +- components/cart/actions.ts | 31 +++++++ components/cart/add-many-to-cart.tsx | 117 +++++++++++++++++++++++++ messages/en.json | 40 +++++---- messages/ja.json | 2 + 5 files changed, 175 insertions(+), 21 deletions(-) create mode 100644 components/cart/add-many-to-cart.tsx diff --git a/app/[locale]/product/[handle]/page.tsx b/app/[locale]/product/[handle]/page.tsx index 0d19171fa..5da149f4e 100644 --- a/app/[locale]/product/[handle]/page.tsx +++ b/app/[locale]/product/[handle]/page.tsx @@ -3,7 +3,7 @@ import { notFound } from 'next/navigation'; import { Suspense } from 'react'; import clsx from 'clsx'; -import { AddToCart } from 'components/cart/add-to-cart'; +import { AddManyToCart } from 'components/cart/add-many-to-cart'; import { GridTileImage } from 'components/grid/tile'; import Label from 'components/label'; import { SupportedLocale } from 'components/layout/navbar/language-control'; @@ -135,7 +135,9 @@ export default async function ProductPage({
- diff --git a/components/cart/actions.ts b/components/cart/actions.ts index 3fb013194..40a74789d 100644 --- a/components/cart/actions.ts +++ b/components/cart/actions.ts @@ -28,6 +28,37 @@ export const addItem = async (variantId: string | undefined): Promise => { + let cartId = cookies().get('cartId')?.value; + let cart; + + if (cartId) { + cart = await getCart(cartId); + } + + if (!cartId || !cart) { + cart = await createCart(); + cartId = cart.id; + cookies().set('cartId', cartId); + } + + if (!variantId) { + return 'Missing product variant ID'; + } + + try { + await addToCart(cartId, [{ merchandiseId: variantId, quantity }]); + } catch (e) { + return quantity === 1 ? 'Error adding item to cart' : 'Error adding items to cart'; + } +}; + export const removeItem = async (lineId: string): Promise => { const cartId = cookies().get('cartId')?.value; diff --git a/components/cart/add-many-to-cart.tsx b/components/cart/add-many-to-cart.tsx new file mode 100644 index 000000000..9916b9bce --- /dev/null +++ b/components/cart/add-many-to-cart.tsx @@ -0,0 +1,117 @@ +'use client'; + +import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/react/24/outline'; +import clsx from 'clsx'; +import LoadingDots from 'components/loading-dots'; +import { Product, ProductVariant } from 'lib/shopify/types'; +import { useTranslations } from 'next-intl'; +import { useRouter, useSearchParams } from 'next/navigation'; +import { useState, useTransition } from 'react'; +import { addItems } from './actions'; + +export function AddManyToCart({ + product, + quantity = 1, + variants, + availableForSale +}: { + product: Product; + quantity: number; + variants: ProductVariant[]; + availableForSale: boolean; +}) { + const router = useRouter(); + const searchParams = useSearchParams(); + const t = useTranslations('Index'); + + const [currentQuantity, setCurrentQuantity] = useState(quantity); + + const [isPending, startTransition] = useTransition(); + const defaultVariantId = variants.length === 1 ? variants[0]?.id : undefined; + const variant = variants.find((variant: ProductVariant) => + variant.selectedOptions.every( + (option) => option.value === searchParams.get(option.name.toLowerCase()) + ) + ); + console.debug({ product, variant, currentQuantity }); + const selectedVariantId = variant?.id || defaultVariantId; + const title = !availableForSale + ? t('cart.out-of-stock') + : !selectedVariantId + ? t('cart.options') + : undefined; + + return ( +
+
+
{t('cart.quantity-label')}
+ setCurrentQuantity(Number(e.target.value))} + className={clsx( + 'w-auto grow bg-transparent px-2 py-3 text-right text-white', + 'outline-none focus:border-0 focus:outline-none focus:ring-0', + 'focus-visible:ring-none focus-visible:border-none focus-visible:outline-none', + 'focus-visible:ring-0 focus-visible:ring-offset-0', + 'active:border-none active:outline-none active:ring-0' + )} + /> +
+ + +
+
+ +
+ ); +} diff --git a/messages/en.json b/messages/en.json index a190c0bba..930976050 100644 --- a/messages/en.json +++ b/messages/en.json @@ -76,25 +76,27 @@ "cart": { "add": "add to cart", "out-of-stock": "out of stock", - "title": "Shopping Bag", - "subtitle": "Review your Order", - "empty": "Your shopping bag is empty", - "declinedCard": "We couldn't process the purchase. Please check your card information and try again.", - "thankYou": "Thank you for your order.", - "subtotal": "Subtotal", - "taxes": "Taxes", - "taxCalculation": "Calculated at checkout", - "shipping": "Shipping", - "shippingCalculation": "Calculated at checkout", - "total": "Total", - "proceed": "Proceed to Checkout", - "continue": "Continue Shopping", - "note": "Notes", - "notePlaceholder": "Enter any notes you would like to include with your order", - "addNote": "Add a note to your order", - "editNote": "Edit note", - "hideNote": "Hide", - "saving": "Saving...", + "options": "please select options", + "quantity-label": "quantity", + "title": "shopping bag", + "subtitle": "review your order", + "empty": "your shopping bag is empty", + "declinedCard": "we couldn't process the purchase. Please check your card information and try again.", + "thankYou": "thank you for your order.", + "subtotal": "subtotal", + "taxes": "taxes", + "taxCalculation": "calculated at checkout", + "shipping": "shipping", + "shippingCalculation": "calculated at checkout", + "total": "total", + "proceed": "proceed to Checkout", + "continue": "continue Shopping", + "note": "notes", + "notePlaceholder": "enter any notes you would like to include with your order", + "addNote": "add a note to your order", + "editNote": "edit note", + "hideNote": "hide", + "saving": "saving...", "addFeaturedProduct": "+ add" }, "age-gate": { diff --git a/messages/ja.json b/messages/ja.json index dd4947b54..59c83253a 100644 --- a/messages/ja.json +++ b/messages/ja.json @@ -76,6 +76,8 @@ "cart": { "add": "カートに入れる", "out-of-stock": "品切れ中", + "options": "オプション", + "quantity-label": "購入数", "title": "ショッピングカード", "subtitle": "ご注文内容の確認", "empty": "買い物袋が空っぽ",