mirror of
https://github.com/vercel/commerce.git
synced 2025-05-30 21:16:59 +00:00
mas improvements
This commit is contained in:
parent
5071de39ae
commit
bacbe38ffa
@ -6,22 +6,13 @@ import { revalidateTag } from 'next/cache';
|
||||
import { cookies } from 'next/headers';
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
let cartId: string | undefined;
|
||||
|
||||
export async function addItem(prevState: any, selectedVariantId: string | undefined) {
|
||||
let cartId = cookies().get('cartId')?.value;
|
||||
let cart;
|
||||
cartId = cartId || cookies().get('cartId')?.value;
|
||||
|
||||
if (cartId) {
|
||||
cart = await getCart(cartId);
|
||||
}
|
||||
|
||||
if (!cartId || !cart) {
|
||||
cart = await createCart();
|
||||
cartId = cart.id;
|
||||
cookies().set('cartId', cartId);
|
||||
}
|
||||
|
||||
if (!selectedVariantId) {
|
||||
return 'Missing product variant ID';
|
||||
if (!cartId || !selectedVariantId) {
|
||||
return 'Error adding item to cart';
|
||||
}
|
||||
|
||||
try {
|
||||
@ -32,16 +23,28 @@ export async function addItem(prevState: any, selectedVariantId: string | undefi
|
||||
}
|
||||
}
|
||||
|
||||
export async function removeItem(prevState: any, lineId: string) {
|
||||
const cartId = cookies().get('cartId')?.value;
|
||||
export async function removeItem(prevState: any, merchandiseId: string) {
|
||||
cartId = cartId || cookies().get('cartId')?.value;
|
||||
|
||||
if (!cartId) {
|
||||
return 'Missing cart ID';
|
||||
}
|
||||
|
||||
try {
|
||||
await removeFromCart(cartId, [lineId]);
|
||||
revalidateTag(TAGS.cart);
|
||||
const cart = await getCart(cartId);
|
||||
|
||||
if (!cart) {
|
||||
return 'Error fetching cart';
|
||||
}
|
||||
|
||||
const lineItem = cart.lines.find((line) => line.merchandise.id === merchandiseId);
|
||||
|
||||
if (lineItem) {
|
||||
await removeFromCart(cartId, [lineItem.id]);
|
||||
revalidateTag(TAGS.cart);
|
||||
} else {
|
||||
return 'Item not found in cart';
|
||||
}
|
||||
} catch (e) {
|
||||
return 'Error removing item from cart';
|
||||
}
|
||||
@ -50,47 +53,69 @@ export async function removeItem(prevState: any, lineId: string) {
|
||||
export async function updateItemQuantity(
|
||||
prevState: any,
|
||||
payload: {
|
||||
lineId: string;
|
||||
variantId: string;
|
||||
merchandiseId: string;
|
||||
quantity: number;
|
||||
}
|
||||
) {
|
||||
const cartId = cookies().get('cartId')?.value;
|
||||
cartId = cartId || cookies().get('cartId')?.value;
|
||||
|
||||
if (!cartId) {
|
||||
return 'Missing cart ID';
|
||||
}
|
||||
|
||||
const { lineId, variantId, quantity } = payload;
|
||||
const { merchandiseId, quantity } = payload;
|
||||
|
||||
try {
|
||||
if (quantity === 0) {
|
||||
await removeFromCart(cartId, [lineId]);
|
||||
revalidateTag(TAGS.cart);
|
||||
return;
|
||||
const cart = await getCart(cartId);
|
||||
|
||||
if (!cart) {
|
||||
return 'Error fetching cart';
|
||||
}
|
||||
|
||||
await updateCart(cartId, [
|
||||
{
|
||||
id: lineId,
|
||||
merchandiseId: variantId,
|
||||
quantity
|
||||
const lineItem = cart.lines.find((line) => line.merchandise.id === merchandiseId);
|
||||
|
||||
if (lineItem) {
|
||||
if (quantity === 0) {
|
||||
await removeFromCart(cartId, [lineItem.id]);
|
||||
} else {
|
||||
await updateCart(cartId, [
|
||||
{
|
||||
id: lineItem.id,
|
||||
merchandiseId,
|
||||
quantity
|
||||
}
|
||||
]);
|
||||
}
|
||||
]);
|
||||
} else if (quantity > 0) {
|
||||
// If the item doesn't exist in the cart and quantity > 0, add it
|
||||
await addToCart(cartId, [{ merchandiseId, quantity }]);
|
||||
}
|
||||
|
||||
revalidateTag(TAGS.cart);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
console.error(e);
|
||||
return 'Error updating item quantity';
|
||||
}
|
||||
}
|
||||
|
||||
export async function redirectToCheckout() {
|
||||
let cartId = cookies().get('cartId')?.value;
|
||||
cartId = cartId || cookies().get('cartId')?.value;
|
||||
|
||||
if (!cartId) {
|
||||
return 'Missing cart ID';
|
||||
}
|
||||
|
||||
let cart = await getCart(cartId);
|
||||
|
||||
if (!cart) {
|
||||
return;
|
||||
return 'Error fetching cart';
|
||||
}
|
||||
|
||||
redirect(cart.checkoutUrl);
|
||||
}
|
||||
|
||||
export async function createCartAndSetCookie() {
|
||||
let cart = await createCart();
|
||||
cartId = cart.id;
|
||||
cookies().set('cartId', cartId);
|
||||
}
|
||||
|
@ -6,12 +6,12 @@ import React, { createContext, use, useContext, useMemo, useOptimistic } from 'r
|
||||
type UpdateType = 'plus' | 'minus' | 'delete';
|
||||
|
||||
type CartAction =
|
||||
| { type: 'UPDATE_ITEM'; payload: { itemId: string; updateType: UpdateType } }
|
||||
| { type: 'UPDATE_ITEM'; payload: { merchandiseId: string; updateType: UpdateType } }
|
||||
| { type: 'ADD_ITEM'; payload: { variant: ProductVariant; product: Product } };
|
||||
|
||||
type CartContextType = {
|
||||
cart: Cart | undefined;
|
||||
updateCartItem: (itemId: string, updateType: UpdateType) => void;
|
||||
cart: Cart | null;
|
||||
updateCartItem: (merchandiseId: string, updateType: UpdateType) => void;
|
||||
addCartItem: (variant: ProductVariant, product: Product) => void;
|
||||
};
|
||||
|
||||
@ -89,40 +89,59 @@ function updateCartTotals(lines: CartItem[]): Pick<Cart, 'totalQuantity' | 'cost
|
||||
};
|
||||
}
|
||||
|
||||
function cartReducer(state: Cart | undefined, action: CartAction): Cart | undefined {
|
||||
if (!state) return state;
|
||||
function createEmptyCart(): Cart {
|
||||
return {
|
||||
id: `optimistic_${Date.now()}`,
|
||||
checkoutUrl: '',
|
||||
totalQuantity: 0,
|
||||
lines: [],
|
||||
cost: {
|
||||
subtotalAmount: { amount: '0', currencyCode: 'USD' },
|
||||
totalAmount: { amount: '0', currencyCode: 'USD' },
|
||||
totalTaxAmount: { amount: '0', currencyCode: 'USD' }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function cartReducer(state: Cart | null, action: CartAction): Cart {
|
||||
const currentCart = state || createEmptyCart();
|
||||
|
||||
switch (action.type) {
|
||||
case 'UPDATE_ITEM': {
|
||||
const { itemId, updateType } = action.payload;
|
||||
const updatedLines = state.lines
|
||||
.map((item) => (item.id === itemId ? updateCartItem(item, updateType) : item))
|
||||
const { merchandiseId, updateType } = action.payload;
|
||||
const updatedLines = currentCart.lines
|
||||
.map((item) =>
|
||||
item.merchandise.id === merchandiseId ? updateCartItem(item, updateType) : item
|
||||
)
|
||||
.filter(Boolean) as CartItem[];
|
||||
|
||||
if (updatedLines.length === 0) {
|
||||
return {
|
||||
...state,
|
||||
...currentCart,
|
||||
lines: [],
|
||||
totalQuantity: 0,
|
||||
cost: { ...state.cost, totalAmount: { ...state.cost.totalAmount, amount: '0' } }
|
||||
cost: {
|
||||
...currentCart.cost,
|
||||
totalAmount: { ...currentCart.cost.totalAmount, amount: '0' }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return { ...state, ...updateCartTotals(updatedLines), lines: updatedLines };
|
||||
return { ...currentCart, ...updateCartTotals(updatedLines), lines: updatedLines };
|
||||
}
|
||||
case 'ADD_ITEM': {
|
||||
const { variant, product } = action.payload;
|
||||
const existingItem = state.lines.find((item) => item.merchandise.id === variant.id);
|
||||
const existingItem = currentCart.lines.find((item) => item.merchandise.id === variant.id);
|
||||
const updatedItem = createOrUpdateCartItem(existingItem, variant, product);
|
||||
|
||||
const updatedLines = existingItem
|
||||
? state.lines.map((item) => (item.merchandise.id === variant.id ? updatedItem : item))
|
||||
: [...state.lines, updatedItem];
|
||||
? currentCart.lines.map((item) => (item.merchandise.id === variant.id ? updatedItem : item))
|
||||
: [...currentCart.lines, updatedItem];
|
||||
|
||||
return { ...state, ...updateCartTotals(updatedLines), lines: updatedLines };
|
||||
return { ...currentCart, ...updateCartTotals(updatedLines), lines: updatedLines };
|
||||
}
|
||||
default:
|
||||
return state;
|
||||
return currentCart;
|
||||
}
|
||||
}
|
||||
|
||||
@ -131,13 +150,13 @@ export function CartProvider({
|
||||
cartPromise
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
cartPromise: Promise<Cart | undefined>;
|
||||
cartPromise: Promise<Cart | null>;
|
||||
}) {
|
||||
const initialCart = use(cartPromise);
|
||||
const [optimisticCart, updateOptimisticCart] = useOptimistic(initialCart, cartReducer);
|
||||
|
||||
const updateCartItem = (itemId: string, updateType: UpdateType) => {
|
||||
updateOptimisticCart({ type: 'UPDATE_ITEM', payload: { itemId, updateType } });
|
||||
const updateCartItem = (merchandiseId: string, updateType: UpdateType) => {
|
||||
updateOptimisticCart({ type: 'UPDATE_ITEM', payload: { merchandiseId, updateType } });
|
||||
};
|
||||
|
||||
const addCartItem = (variant: ProductVariant, product: Product) => {
|
||||
|
@ -13,13 +13,13 @@ export function DeleteItemButton({
|
||||
optimisticUpdate: any;
|
||||
}) {
|
||||
const [message, formAction] = useFormState(removeItem, null);
|
||||
const itemId = item.id;
|
||||
const actionWithVariant = formAction.bind(null, itemId);
|
||||
const merchandiseId = item.merchandise.id;
|
||||
const actionWithVariant = formAction.bind(null, merchandiseId);
|
||||
|
||||
return (
|
||||
<form
|
||||
action={async () => {
|
||||
optimisticUpdate(itemId, 'delete');
|
||||
optimisticUpdate(merchandiseId, 'delete');
|
||||
await actionWithVariant();
|
||||
}}
|
||||
>
|
||||
|
@ -38,8 +38,7 @@ export function EditItemQuantityButton({
|
||||
}) {
|
||||
const [message, formAction] = useFormState(updateItemQuantity, null);
|
||||
const payload = {
|
||||
lineId: item.id,
|
||||
variantId: item.merchandise.id,
|
||||
merchandiseId: item.merchandise.id,
|
||||
quantity: type === 'plus' ? item.quantity + 1 : item.quantity - 1
|
||||
};
|
||||
const actionWithVariant = formAction.bind(null, payload);
|
||||
@ -47,7 +46,7 @@ export function EditItemQuantityButton({
|
||||
return (
|
||||
<form
|
||||
action={async () => {
|
||||
optimisticUpdate(payload.lineId, type);
|
||||
optimisticUpdate(payload.merchandiseId, type);
|
||||
await actionWithVariant();
|
||||
}}
|
||||
>
|
||||
|
@ -10,7 +10,7 @@ import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
import { Fragment, useEffect, useRef, useState } from 'react';
|
||||
import { useFormStatus } from 'react-dom';
|
||||
import { redirectToCheckout } from './actions';
|
||||
import { createCartAndSetCookie, redirectToCheckout } from './actions';
|
||||
import { useCart } from './cart-context';
|
||||
import CloseCart from './close-cart';
|
||||
import { DeleteItemButton } from './delete-item-button';
|
||||
@ -29,7 +29,17 @@ export default function CartModal() {
|
||||
const closeCart = () => setIsOpen(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (cart?.totalQuantity !== quantityRef.current) {
|
||||
if (!cart) {
|
||||
createCartAndSetCookie();
|
||||
}
|
||||
}, [cart]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
cart?.totalQuantity &&
|
||||
cart?.totalQuantity !== quantityRef.current &&
|
||||
cart?.totalQuantity > 0
|
||||
) {
|
||||
if (!isOpen) {
|
||||
setIsOpen(true);
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ export function WelcomeToast() {
|
||||
id: 'welcome-toast',
|
||||
duration: Infinity,
|
||||
onDismiss: () => {
|
||||
document.cookie += 'welcome-toast=2;max-age=31536000';
|
||||
document.cookie = 'welcome-toast=2; max-age=31536000; path=/';
|
||||
},
|
||||
description: (
|
||||
<>
|
||||
|
@ -262,8 +262,7 @@ export async function getCart(cartId: string | undefined): Promise<Cart | undefi
|
||||
const res = await shopifyFetch<ShopifyCartOperation>({
|
||||
query: getCartQuery,
|
||||
variables: { cartId },
|
||||
tags: [TAGS.cart],
|
||||
cache: 'no-store'
|
||||
tags: [TAGS.cart]
|
||||
});
|
||||
|
||||
// Old carts becomes `null` when you checkout.
|
||||
|
Loading…
x
Reference in New Issue
Block a user