Co-authored-by: TakahashiNg <83152264+TakahashiNguyen@users.noreply.github.com> Reviewed-on: #39
This commit was merged in pull request #39.
This commit is contained in:
+144
-82
@@ -1,131 +1,193 @@
|
||||
"use client";
|
||||
|
||||
import { gql } from "@apollo/client";
|
||||
import { useMutation, useQuery } from "@apollo/client/react";
|
||||
import { createContext, useContext, useEffect, useMemo, useState } from "react";
|
||||
|
||||
import { CartItemEntity, MenuItemEntity } from "./types";
|
||||
import { cartClient, eateryClient } from "./apollo-clients";
|
||||
import {
|
||||
CartEntity,
|
||||
CartItemEntity,
|
||||
addMenuItemMutation,
|
||||
allEateriesQuery,
|
||||
createCartMutation,
|
||||
getCartQuery,
|
||||
} from "./types";
|
||||
|
||||
interface CartContextValue {
|
||||
items: CartItemEntity[];
|
||||
totalItems: number;
|
||||
totalPrice: number;
|
||||
addToCart: (product: MenuItemEntity) => void;
|
||||
addToCart: (product: CartItemEntity) => void;
|
||||
increaseQty: (id: string) => void;
|
||||
decreaseQty: (id: string) => void;
|
||||
removeFromCart: (id: string) => void;
|
||||
setQuantity: (id: string, quantity: number) => void;
|
||||
}
|
||||
|
||||
const STORAGE_KEY = "coffee-shop-cart";
|
||||
const CART_ID = "cartId";
|
||||
const CartContext = createContext<CartContextValue | null>(null);
|
||||
|
||||
const GET_CART_ITEMS = gql`
|
||||
query getCart($cartId: String!) {
|
||||
getCart(cartId: $cartId) {
|
||||
Id
|
||||
userId
|
||||
eateryId
|
||||
items {
|
||||
productId
|
||||
quantity
|
||||
priceAtTimeOfAdding
|
||||
subTotal
|
||||
}
|
||||
totalAmount
|
||||
paymentQrUrl
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const GET_EATERY = gql`
|
||||
query GetEateryMenu {
|
||||
allEateries {
|
||||
id
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const CREATE_CART = gql`
|
||||
mutation createCart($eateryId: String!) {
|
||||
createCart(eateryId: $eateryId)
|
||||
}
|
||||
`;
|
||||
|
||||
const ADD_ITEM = gql`
|
||||
mutation addItem(
|
||||
$cartId: String!
|
||||
$menuItemId: String!
|
||||
$quantity: BigInteger!
|
||||
) {
|
||||
addItem(cartId: $cartId, menuItemId: $menuItemId, quantity: $quantity) {
|
||||
Id
|
||||
userId
|
||||
eateryId
|
||||
items {
|
||||
productId
|
||||
quantity
|
||||
priceAtTimeOfAdding
|
||||
subTotal
|
||||
}
|
||||
totalAmount
|
||||
paymentQrUrl
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export function CartProvider({ children }: { children: React.ReactNode }) {
|
||||
const [items, setItems] = useState<CartItemEntity[]>([]);
|
||||
const [cart, setCart] = useState<CartEntity>(null!);
|
||||
const [cartId, setCartId] = useState<string | null>(null);
|
||||
|
||||
const { data: eateryData } = useQuery<allEateriesQuery>(GET_EATERY, {
|
||||
client: eateryClient,
|
||||
});
|
||||
|
||||
const [createCart] = useMutation<createCartMutation>(CREATE_CART, {
|
||||
client: cartClient,
|
||||
});
|
||||
|
||||
const { data, loading, error } = useQuery<getCartQuery>(GET_CART_ITEMS, {
|
||||
client: cartClient,
|
||||
variables: { cartId },
|
||||
skip: !cartId,
|
||||
});
|
||||
|
||||
const [addMenuItem] = useMutation<addMenuItemMutation>(ADD_ITEM, {
|
||||
client: cartClient,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
const raw = localStorage.getItem(STORAGE_KEY);
|
||||
if (!raw) return;
|
||||
const parsed = JSON.parse(raw) as CartItemEntity[];
|
||||
if (Array.isArray(parsed)) {
|
||||
setItems(parsed.filter((i) => i && i.productId && i.quantity > 0));
|
||||
const createCartFunc = async () => {
|
||||
if (eateryData && eateryData.allEateries?.length > 0) {
|
||||
try {
|
||||
const firstEateryId = eateryData.allEateries[0].id;
|
||||
const { data: mutationResult } = await createCart({
|
||||
variables: { eateryId: firstEateryId },
|
||||
});
|
||||
|
||||
const newCartId = mutationResult!.createCart;
|
||||
if (newCartId) {
|
||||
localStorage.setItem(CART_ID, newCartId);
|
||||
setCartId(newCartId);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Lỗi khi tạo giỏ hàng:", err);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
localStorage.removeItem(STORAGE_KEY);
|
||||
};
|
||||
|
||||
if (error) createCartFunc();
|
||||
else {
|
||||
const localCartId = localStorage.getItem(CART_ID);
|
||||
if (localCartId) setCartId(localCartId);
|
||||
}
|
||||
}, []);
|
||||
}, [eateryData, createCart, data, loading]);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(items));
|
||||
}, [items]);
|
||||
if (data?.getCart) setCart(data.getCart);
|
||||
}, [data]);
|
||||
|
||||
const addToCart = (product: MenuItemEntity) => {
|
||||
setItems((prev) => {
|
||||
const index = prev.findIndex((i) => i.productId === product.id);
|
||||
if (index === -1) {
|
||||
return [
|
||||
...prev,
|
||||
{
|
||||
productId: product.id!,
|
||||
name: product.name,
|
||||
description: product.description,
|
||||
priceAtTimeOfAdding: product.price,
|
||||
quantity: 1,
|
||||
},
|
||||
];
|
||||
}
|
||||
const addToCart = async (product: CartItemEntity) => {
|
||||
if (!cartId) return;
|
||||
|
||||
const next = [...prev];
|
||||
next[index] = { ...next[index], quantity: next[index].quantity + 1 };
|
||||
return next;
|
||||
const { data: result } = await addMenuItem({
|
||||
variables: {
|
||||
cartId,
|
||||
menuItemId: product.productId!,
|
||||
quantity: product.quantity,
|
||||
},
|
||||
});
|
||||
|
||||
if (result) setCart(result.addItem);
|
||||
};
|
||||
|
||||
const increaseQty = (id: string) => {
|
||||
setItems((prev) =>
|
||||
prev.map((item) =>
|
||||
item.productId === id ? { ...item, quantity: item.quantity + 1 } : item,
|
||||
),
|
||||
);
|
||||
const setQuantity = async (id: string, quantity: number) => {
|
||||
if (!cartId) return;
|
||||
|
||||
const { data: result } = await addMenuItem({
|
||||
variables: {
|
||||
cartId,
|
||||
menuItemId: id,
|
||||
quantity,
|
||||
},
|
||||
});
|
||||
|
||||
if (result) setCart(result.addItem);
|
||||
};
|
||||
|
||||
const decreaseQty = (id: string) => {
|
||||
setItems((prev) =>
|
||||
prev
|
||||
.map((item) =>
|
||||
item.productId === id
|
||||
? { ...item, quantity: Math.max(0, item.quantity - 1) }
|
||||
: item,
|
||||
)
|
||||
.filter((item) => item.quantity > 0),
|
||||
);
|
||||
};
|
||||
const removeFromCart = (id: string) => setQuantity(id, 0);
|
||||
|
||||
const removeFromCart = (id: string) => {
|
||||
setItems((prev) => prev.filter((item) => item.productId !== id));
|
||||
};
|
||||
const increaseQty = (id: string) =>
|
||||
setQuantity(id, cart.items.find((i) => i.productId == id)!.quantity + 1);
|
||||
|
||||
const setQuantity = (id: string, quantity: number) => {
|
||||
const safeQty = Number.isFinite(quantity)
|
||||
? Math.max(0, Math.floor(quantity))
|
||||
: 0;
|
||||
if (safeQty === 0) {
|
||||
removeFromCart(id);
|
||||
return;
|
||||
}
|
||||
|
||||
setItems((prev) =>
|
||||
prev.map((item) =>
|
||||
item.productId === id ? { ...item, quantity: safeQty } : item,
|
||||
),
|
||||
);
|
||||
};
|
||||
const decreaseQty = (id: string) =>
|
||||
setQuantity(id, cart.items.find((i) => i.productId == id)!.quantity - 1);
|
||||
|
||||
const totalItems = useMemo(
|
||||
() => items.reduce((sum, item) => sum + item.quantity, 0),
|
||||
[items],
|
||||
);
|
||||
|
||||
const totalPrice = useMemo(
|
||||
() =>
|
||||
items.reduce(
|
||||
(sum, item) => sum + item.priceAtTimeOfAdding * item.quantity,
|
||||
0,
|
||||
),
|
||||
[items],
|
||||
() => cart?.items.reduce((sum, item) => sum + item.quantity, 0),
|
||||
[cart],
|
||||
);
|
||||
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
items,
|
||||
items: cart?.items,
|
||||
totalItems,
|
||||
totalPrice,
|
||||
totalPrice: cart?.totalAmount,
|
||||
addToCart,
|
||||
increaseQty,
|
||||
decreaseQty,
|
||||
removeFromCart,
|
||||
setQuantity,
|
||||
}),
|
||||
[items, totalItems, totalPrice],
|
||||
[cart?.items, cart?.totalAmount],
|
||||
);
|
||||
|
||||
return <CartContext.Provider value={value}>{children}</CartContext.Provider>;
|
||||
|
||||
Reference in New Issue
Block a user