Skip to content

Commit

Permalink
Replaces Route Handlers with Server Actions (vercel#1050)
Browse files Browse the repository at this point in the history
  • Loading branch information
manovotny authored and bc-alexsaiannyi committed Jul 21, 2023
1 parent c744bad commit 5e4c39e
Show file tree
Hide file tree
Showing 9 changed files with 170 additions and 244 deletions.
109 changes: 0 additions & 109 deletions app/api/cart/route.ts

This file was deleted.

2 changes: 1 addition & 1 deletion app/product/[handle]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Suspense } from 'react';
import Grid from 'components/grid';
import Footer from 'components/layout/footer';
import ProductGridItems from 'components/layout/product-grid-items';
import { AddToCart } from 'components/product/add-to-cart';
import { AddToCart } from 'components/cart/add-to-cart';
import { Gallery } from 'components/product/gallery';
import { VariantSelector } from 'components/product/variant-selector';
import Prose from 'components/prose';
Expand Down
57 changes: 57 additions & 0 deletions components/cart/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
'use server';

import { addToCart, removeFromCart, updateCart } from 'lib/shopify';
import { cookies } from 'next/headers';

export const addItem = async (variantId: string | undefined): Promise<Error | undefined> => {
const cartId = cookies().get('cartId')?.value;

if (!cartId || !variantId) {
return new Error('Missing cartId or variantId');
}
try {
await addToCart(cartId, [{ merchandiseId: variantId, quantity: 1 }]);
} catch (e) {
return new Error('Error adding item', { cause: e });
}
};

export const removeItem = async (lineId: string): Promise<Error | undefined> => {
const cartId = cookies().get('cartId')?.value;

if (!cartId) {
return new Error('Missing cartId');
}
try {
await removeFromCart(cartId, [lineId]);
} catch (e) {
return new Error('Error removing item', { cause: e });
}
};

export const updateItemQuantity = async ({
lineId,
variantId,
quantity
}: {
lineId: string;
variantId: string;
quantity: number;
}): Promise<Error | undefined> => {
const cartId = cookies().get('cartId')?.value;

if (!cartId) {
return new Error('Missing cartId');
}
try {
await updateCart(cartId, [
{
id: lineId,
merchandiseId: variantId,
quantity
}
]);
} catch (e) {
return new Error('Error updating item quantity', { cause: e });
}
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use client';

import clsx from 'clsx';
import { addItem } from 'components/cart/actions';
import { useRouter, useSearchParams } from 'next/navigation';
import { useEffect, useState, useTransition } from 'react';

Expand All @@ -21,7 +22,6 @@ export function AddToCart({
const router = useRouter();
const searchParams = useSearchParams();
const [isPending, startTransition] = useTransition();
const [adding, setAdding] = useState(false);

useEffect(() => {
const variant = variants.find((variant: ProductVariant) =>
Expand All @@ -36,51 +36,33 @@ export function AddToCart({
}
}, [searchParams, variants, setSelectedVariantId]);

const isMutating = adding || isPending;

async function handleAdd() {
if (!availableForSale) return;

setAdding(true);

const response = await fetch(`/api/cart`, {
method: 'POST',
body: JSON.stringify({
merchandiseId: selectedVariantId,
productId: selectedProductId,
isBigCommerceAPI: true
})
});

const data = await response.json();

if (data.error) {
alert(data.error);
return;
}

setAdding(false);

startTransition(() => {
router.refresh();
});
}

return (
<button
aria-label="Add item to cart"
disabled={isMutating}
onClick={handleAdd}
disabled={isPending}
onClick={() => {
if (!availableForSale) return;
startTransition(async () => {
const error = await addItem(selectedVariantId);

if (error) {
alert(error);
return;
}

router.refresh();
});
}}
className={clsx(
'flex w-full items-center justify-center bg-black p-4 text-sm uppercase tracking-wide text-white opacity-90 hover:opacity-100 dark:bg-white dark:text-black',
{
'cursor-not-allowed opacity-60': !availableForSale,
'cursor-not-allowed': isMutating
'cursor-not-allowed': isPending
}
)}
>
<span>{availableForSale ? 'Add To Cart' : 'Out Of Stock'}</span>
{isMutating ? <LoadingDots className="bg-white dark:bg-black" /> : null}
{isPending ? <LoadingDots className="bg-white dark:bg-black" /> : null}
</button>
);
}
47 changes: 18 additions & 29 deletions components/cart/delete-item-button.tsx
Original file line number Diff line number Diff line change
@@ -1,51 +1,40 @@
import CloseIcon from 'components/icons/close';
import LoadingDots from 'components/loading-dots';
import { useRouter } from 'next/navigation';
import { startTransition, useState } from 'react';

import clsx from 'clsx';
import { removeItem } from 'components/cart/actions';
import type { VercelCartItem as CartItem } from 'lib/bigcommerce/types';
import { useTransition } from 'react';

export default function DeleteItemButton({ item }: { item: CartItem }) {
const router = useRouter();
const [removing, setRemoving] = useState(false);
const [isPending, startTransition] = useTransition();

async function handleRemove() {
setRemoving(true);

const response = await fetch(`/api/cart`, {
method: 'POST',
body: JSON.stringify({
lineId: item.id,
isBigCommerceAPI: true,
})
});
const data = await response.json();

if (data.error) {
alert(data.error);
return;
}

setRemoving(false);

startTransition(() => {
router.refresh();
});
}
return (
<button
aria-label="Remove cart item"
onClick={handleRemove}
disabled={removing}
onClick={() => {
startTransition(async () => {
const error = await removeItem(item.id);

if (error) {
alert(error);
return;
}

router.refresh();
});
}}
disabled={isPending}
className={clsx(
'ease flex min-w-[36px] max-w-[36px] items-center justify-center border px-2 transition-all duration-200 hover:border-gray-800 hover:bg-gray-100 dark:border-gray-700 dark:hover:border-gray-600 dark:hover:bg-gray-900',
{
'cursor-not-allowed px-0': removing
'cursor-not-allowed px-0': isPending
}
)}
>
{removing ? (
{isPending ? (
<LoadingDots className="bg-black dark:bg-white" />
) : (
<CloseIcon className="hover:text-accent-3 mx-[1px] h-4 w-4" />
Expand Down
Loading

0 comments on commit 5e4c39e

Please sign in to comment.