online and offline base cart
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
import * as api from "../service/Service";
|
||||
import { useQuery, useMutation } from "@tanstack/react-query";
|
||||
|
||||
export const useGetCart = () => {
|
||||
return useQuery({
|
||||
queryKey: ["cart"],
|
||||
queryFn: api.getCart,
|
||||
});
|
||||
};
|
||||
|
||||
export const useAddToCart = () => {
|
||||
return useMutation({
|
||||
mutationFn: api.addToCart,
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdateCart = () => {
|
||||
return useMutation({
|
||||
mutationFn: api.updateCart,
|
||||
});
|
||||
};
|
||||
|
||||
export const useRemoveFromCart = () => {
|
||||
return useMutation({
|
||||
mutationFn: api.removeFromCart,
|
||||
});
|
||||
};
|
||||
@@ -1,11 +1,16 @@
|
||||
'use client'
|
||||
import { Button } from "@/components/ui/button"
|
||||
import CartItem from "@/app/cart/components/CartItem"
|
||||
import CartSummary from "@/components/CartSummary"
|
||||
import Layout from "@/hoc/Layout"
|
||||
import { Trash } from "iconsax-react"
|
||||
import { NextPage } from "next"
|
||||
import { useGetCart } from "./hooks/useCartData"
|
||||
|
||||
const Cart: NextPage = () => {
|
||||
|
||||
const { data } = useGetCart();
|
||||
|
||||
return (
|
||||
<div className="mt-12 sm:mt-14 px-3 sm:px-4 md:px-8 lg:px-20">
|
||||
<div className="border-b border-border flex flex-row justify-between items-center gap-3 sm:gap-4">
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import axios from "@/config/axios";
|
||||
import {
|
||||
AddCartType,
|
||||
UpdateCartType,
|
||||
RemoveCartType,
|
||||
CartResponseType,
|
||||
} from "../types/Types";
|
||||
|
||||
export const getCart = async (): Promise<CartResponseType> => {
|
||||
const { data } = await axios.get("/cart");
|
||||
return data;
|
||||
};
|
||||
|
||||
export const addToCart = async (params: AddCartType) => {
|
||||
const { data } = await axios.post("/cart/add", params);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const updateCart = async (params: UpdateCartType) => {
|
||||
const { data } = await axios.post("/cart/update", params);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const removeFromCart = async (params: RemoveCartType) => {
|
||||
const { data } = await axios.post("/cart/remove", params);
|
||||
return data;
|
||||
};
|
||||
@@ -0,0 +1,168 @@
|
||||
// Cart Item Types
|
||||
export type CartItemType = {
|
||||
product: {
|
||||
_id: number;
|
||||
title_fa: string;
|
||||
title_en: string;
|
||||
seoTitle: string | null;
|
||||
seoDescription: string | null;
|
||||
source: string;
|
||||
description: string;
|
||||
metaDescription: string;
|
||||
tags: string[];
|
||||
advantages: string[];
|
||||
disAdvantages: string[];
|
||||
imagesUrl: {
|
||||
cover: string;
|
||||
list: string[];
|
||||
};
|
||||
isFake: string;
|
||||
voice: string | null;
|
||||
specifications: Array<{
|
||||
title: string;
|
||||
values: string[];
|
||||
}>;
|
||||
category: {
|
||||
_id: string;
|
||||
};
|
||||
};
|
||||
variant: {
|
||||
_id: string;
|
||||
market_status: string;
|
||||
price: {
|
||||
order_limit: number;
|
||||
retailPrice: number;
|
||||
selling_price: number;
|
||||
is_specialSale: boolean;
|
||||
discount_percent: number;
|
||||
specialSale_order_limit: number | null;
|
||||
specialSale_quantity: number | null;
|
||||
specialSale_endDate: string | null;
|
||||
};
|
||||
stock: number;
|
||||
postingTime: number;
|
||||
isFreeShip: boolean;
|
||||
isWholeSale: boolean;
|
||||
shop: {
|
||||
_id: string;
|
||||
shopName: string;
|
||||
shopCode: number;
|
||||
owner: string;
|
||||
shopDescription: string;
|
||||
telephoneNumber: string;
|
||||
isChatActive: boolean;
|
||||
shopPostalCode: string;
|
||||
logo: string;
|
||||
};
|
||||
shipmentMethod: Array<{
|
||||
_id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
deliveryTime: number;
|
||||
deliveryType: string;
|
||||
}>;
|
||||
warranty: {
|
||||
_id: number;
|
||||
duration: string;
|
||||
logoUrl: string;
|
||||
name: string;
|
||||
};
|
||||
meterage?: {
|
||||
_id: number;
|
||||
value: string;
|
||||
};
|
||||
size?: {
|
||||
_id: number;
|
||||
value: string;
|
||||
};
|
||||
};
|
||||
quantity: number;
|
||||
};
|
||||
|
||||
// Cart Types
|
||||
export type CartType = {
|
||||
_id: string;
|
||||
coupon_discount: number;
|
||||
items: CartItemType[];
|
||||
items_count: number;
|
||||
payable_price: number;
|
||||
retail_price: number;
|
||||
total_discount: number;
|
||||
user: {
|
||||
_id: string;
|
||||
fullName: string;
|
||||
phoneNumber: string;
|
||||
dateOfBirth: string | null;
|
||||
address: {
|
||||
_id: string;
|
||||
};
|
||||
nationalCode: string | null;
|
||||
homeNumber: string | null;
|
||||
};
|
||||
isWholeSale: boolean;
|
||||
};
|
||||
|
||||
// Recommended Product Type
|
||||
export type RecommendedProductType = {
|
||||
_id: number;
|
||||
title_fa: string;
|
||||
title_en: string;
|
||||
seoTitle: string | null;
|
||||
seoDescription: string | null;
|
||||
model: string;
|
||||
source: string;
|
||||
description: string;
|
||||
metaDescription: string;
|
||||
tags: string[];
|
||||
advantages: string[];
|
||||
disAdvantages: string[];
|
||||
totalRate: number;
|
||||
category: string;
|
||||
shop: string;
|
||||
brand: string;
|
||||
status: string;
|
||||
adminComments: string | null;
|
||||
step: number;
|
||||
isFake: boolean;
|
||||
variants: string[];
|
||||
voice: string | null;
|
||||
deleted: boolean;
|
||||
specifications: Array<{
|
||||
title: string;
|
||||
values: string[];
|
||||
}>;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
imagesUrl: {
|
||||
cover: string;
|
||||
list: string[];
|
||||
};
|
||||
url: string;
|
||||
};
|
||||
|
||||
// Cart API Response Type
|
||||
export type CartResponseType = {
|
||||
status: number;
|
||||
success: boolean;
|
||||
results: {
|
||||
cart: CartType;
|
||||
recommended: RecommendedProductType[];
|
||||
};
|
||||
};
|
||||
|
||||
// Cart Action Types
|
||||
export type AddCartType = {
|
||||
productId: number;
|
||||
variantId: string;
|
||||
};
|
||||
|
||||
export type UpdateCartType = {
|
||||
productId: number;
|
||||
variantId: string;
|
||||
quantity: number;
|
||||
};
|
||||
|
||||
export type RemoveCartType = {
|
||||
productId: number;
|
||||
variantId: string;
|
||||
};
|
||||
@@ -1,27 +1,32 @@
|
||||
'use client'
|
||||
import { Star1 } from 'iconsax-react'
|
||||
import { FC, useState } from 'react'
|
||||
import { FC, useEffect } from 'react'
|
||||
import { Product } from '@/types/product.types'
|
||||
import Image from 'next/image'
|
||||
import { clx } from '@/helpers/utils'
|
||||
import { useProductStore } from '../store/Store'
|
||||
|
||||
interface BaseInformationProps {
|
||||
product: Product
|
||||
}
|
||||
|
||||
const BaseInformation: FC<BaseInformationProps> = ({ product }) => {
|
||||
|
||||
const { setVariantId, variantId } = useProductStore()
|
||||
|
||||
const brand = product.brand
|
||||
|
||||
// State برای انتخاب variant ها
|
||||
const [selectedSize, setSelectedSize] = useState<string | null>(
|
||||
product.variants.find(v => v.size)?.size?._id?.toString() || null
|
||||
)
|
||||
const [selectedColor, setSelectedColor] = useState<string | null>(
|
||||
product.variants.find(v => v.color)?.color?._id?.toString() || null
|
||||
)
|
||||
const [selectedMeterage, setSelectedMeterage] = useState<string | null>(
|
||||
product.variants.find(v => v.meterage)?.meterage?._id?.toString() || null
|
||||
)
|
||||
// تابع برای انتخاب variant
|
||||
const handleVariantSelect = (variantId: string) => {
|
||||
setVariantId(variantId)
|
||||
}
|
||||
|
||||
// انتخاب اولین variant به صورت پیشفرض
|
||||
useEffect(() => {
|
||||
if (product.variants.length > 0 && !variantId) {
|
||||
setVariantId(product.variants[0]._id)
|
||||
}
|
||||
}, [product.variants, variantId, setVariantId])
|
||||
|
||||
return (
|
||||
<div className='flex-1'>
|
||||
@@ -54,11 +59,11 @@ const BaseInformation: FC<BaseInformationProps> = ({ product }) => {
|
||||
<div className='flex flex-wrap items-center gap-1.5 mb-4'>
|
||||
{product.variants.map((variant) => {
|
||||
if (variant.meterage) {
|
||||
const isSelected = selectedMeterage === variant.meterage._id.toString()
|
||||
const isSelected = variantId === variant._id
|
||||
return (
|
||||
<button
|
||||
key={variant._id}
|
||||
onClick={() => setSelectedMeterage(variant.meterage!._id.toString())}
|
||||
onClick={() => handleVariantSelect(variant._id)}
|
||||
className={`text-[13px] px-4 py-2 border border-border rounded-lg whitespace-nowrap transition-colors ${isSelected
|
||||
? 'bg-primary text-white border-primary'
|
||||
: 'hover:border-primary/50'
|
||||
@@ -76,11 +81,11 @@ const BaseInformation: FC<BaseInformationProps> = ({ product }) => {
|
||||
<div className='flex flex-wrap items-center gap-1.5 mb-4'>
|
||||
{product.variants.map((variant) => {
|
||||
if (variant.color) {
|
||||
const isSelected = selectedColor === variant.color._id.toString()
|
||||
const isSelected = variantId === variant._id
|
||||
return (
|
||||
<button
|
||||
key={variant._id}
|
||||
onClick={() => setSelectedColor(variant.color!._id.toString())}
|
||||
onClick={() => handleVariantSelect(variant._id)}
|
||||
className={clx(
|
||||
'size-10 rounded-full flex justify-center items-center',
|
||||
variant.color.hexColor === '#FFFFFF' ? 'border border-border' : ''
|
||||
@@ -102,11 +107,11 @@ const BaseInformation: FC<BaseInformationProps> = ({ product }) => {
|
||||
<div className='flex flex-wrap items-center gap-1.5'>
|
||||
{product.variants.map((variant) => {
|
||||
if (variant.size) {
|
||||
const isSelected = selectedSize === variant.size._id.toString()
|
||||
const isSelected = variantId === variant._id
|
||||
return (
|
||||
<button
|
||||
key={variant._id}
|
||||
onClick={() => setSelectedSize(variant.size!._id.toString())}
|
||||
onClick={() => handleVariantSelect(variant._id)}
|
||||
className={`text-[13px] px-4 py-2 border border-border rounded-lg whitespace-nowrap transition-colors ${isSelected
|
||||
? 'bg-primary text-white border-primary'
|
||||
: 'hover:border-primary/50'
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import React from 'react'
|
||||
import { useProductStore } from '../store/Store'
|
||||
import { useSharedStore } from '@/share/store/sharedStore'
|
||||
import OnlineCart from './OnlineCart'
|
||||
import OfflineCart from './OfflineCart'
|
||||
|
||||
const Cart = () => {
|
||||
const { variantId } = useProductStore()
|
||||
const { isLogin } = useSharedStore()
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLogin ? (
|
||||
<OnlineCart variantId={variantId} />
|
||||
) : (
|
||||
<OfflineCart variantId={variantId} />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Cart
|
||||
@@ -0,0 +1,107 @@
|
||||
import { Button } from '@/components/ui/button'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useParams } from 'next/navigation'
|
||||
import { Add, Minus, Trash } from 'iconsax-react'
|
||||
import { PRIMARY_COLOR } from '@/config/const'
|
||||
import { toast } from '@/components/Toast'
|
||||
import { useLocalCart } from '../hooks/useLocalCart'
|
||||
|
||||
interface OfflineCartProps {
|
||||
variantId: string
|
||||
}
|
||||
|
||||
const OfflineCart: React.FC<OfflineCartProps> = ({ variantId }) => {
|
||||
const { id } = useParams()
|
||||
|
||||
// Local cart hooks for non-logged in users
|
||||
const {
|
||||
addToLocalCart,
|
||||
updateLocalCartItem,
|
||||
removeFromLocalCart,
|
||||
getLocalCartItem,
|
||||
} = useLocalCart()
|
||||
|
||||
const [isInCart, setIsInCart] = useState<boolean>(false)
|
||||
const [quantity, setQuantity] = useState<number>(1)
|
||||
|
||||
const handleAddToCart = () => {
|
||||
addToLocalCart(Number(id), variantId, 1)
|
||||
setIsInCart(true)
|
||||
setQuantity(1)
|
||||
toast('محصول به سبد خرید اضافه شد', 'success')
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
// Check local cart for non-logged in users
|
||||
const localItem = getLocalCartItem(Number(id), variantId)
|
||||
if (localItem) {
|
||||
setIsInCart(true)
|
||||
setQuantity(localItem.quantity)
|
||||
} else {
|
||||
setIsInCart(false)
|
||||
}
|
||||
}, [variantId, id, getLocalCartItem])
|
||||
|
||||
const handleQuantityChange = (number: number) => {
|
||||
if (number === 0) {
|
||||
handleEmptyCart()
|
||||
return
|
||||
}
|
||||
|
||||
setQuantity(number)
|
||||
updateLocalCartItem(Number(id), variantId, number)
|
||||
}
|
||||
|
||||
const handleEmptyCart = () => {
|
||||
removeFromLocalCart(Number(id), variantId)
|
||||
toast('محصول از سبد خرید حذف شد', 'success')
|
||||
setIsInCart(false)
|
||||
setQuantity(0)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
!isInCart ?
|
||||
<Button className='w-full mt-8' onClick={handleAddToCart}>
|
||||
افزودن به سبد خرید
|
||||
</Button>
|
||||
:
|
||||
<div className={"flex items-center gap-2 sm:gap-4 mt-8"}>
|
||||
<div className="h-10 sm:h-12 rounded-lg sm:rounded-xl border border-border bg-white px-2 sm:px-4 flex-1 flex items-center justify-between">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleQuantityChange(quantity + 1)}
|
||||
className="p-1"
|
||||
aria-label="increase quantity"
|
||||
>
|
||||
<Add size={18} color={PRIMARY_COLOR} className="sm:w-5 sm:h-5" />
|
||||
</button>
|
||||
<span className="text-sm sm:text-[18px] font-medium text-primary">
|
||||
{quantity}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleQuantityChange(quantity - 1)}
|
||||
className="p-1"
|
||||
aria-label="decrease quantity"
|
||||
>
|
||||
<Minus size={18} color={PRIMARY_COLOR} className="sm:w-5 sm:h-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleEmptyCart}
|
||||
className="h-10 w-10 sm:h-12 sm:w-12 rounded-lg sm:rounded-xl border border-border grid place-items-center bg-white"
|
||||
aria-label="remove from cart"
|
||||
>
|
||||
<Trash size={18} color="#AD3434" className="sm:w-5 sm:h-5" />
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default OfflineCart
|
||||
@@ -0,0 +1,138 @@
|
||||
import { useAddToCart, useGetCart, useRemoveFromCart, useUpdateCart } from '@/app/cart/hooks/useCartData'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useParams } from 'next/navigation'
|
||||
import { Add, Minus, Trash } from 'iconsax-react'
|
||||
import { PRIMARY_COLOR } from '@/config/const'
|
||||
import { toast } from '@/components/Toast'
|
||||
import { extractErrorMessage } from '@/helpers/errorUtils'
|
||||
|
||||
interface OnlineCartProps {
|
||||
variantId: string
|
||||
}
|
||||
|
||||
const OnlineCart: React.FC<OnlineCartProps> = ({ variantId }) => {
|
||||
const { id } = useParams()
|
||||
|
||||
// API hooks for logged in users
|
||||
const { mutate: addToCart, isPending: isAddingToCart } = useAddToCart()
|
||||
const { mutate: updateCart, isPending: isUpdatingCart } = useUpdateCart()
|
||||
const { mutate: removeFromCart, isPending: isRemovingFromCart } = useRemoveFromCart()
|
||||
const { data: cart, isPending } = useGetCart()
|
||||
|
||||
const [isInCart, setIsInCart] = useState<boolean>(false)
|
||||
const [quantity, setQuantity] = useState<number>(1)
|
||||
|
||||
const handleAddToCart = () => {
|
||||
addToCart({
|
||||
productId: Number(id),
|
||||
variantId: variantId,
|
||||
}, {
|
||||
onSuccess: () => {
|
||||
setIsInCart(true)
|
||||
setQuantity(1)
|
||||
toast('محصول به سبد خرید اضافه شد', 'success')
|
||||
},
|
||||
onError: (error: Error) => {
|
||||
toast(extractErrorMessage(error), 'error')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
// Check API cart for logged in users
|
||||
const item = cart?.results?.cart?.items?.find(item => item.variant?._id === variantId)
|
||||
if (item) {
|
||||
setIsInCart(true)
|
||||
setQuantity(item.quantity)
|
||||
} else {
|
||||
setIsInCart(false)
|
||||
}
|
||||
}, [cart, variantId])
|
||||
|
||||
const handleQuantityChange = (number: number) => {
|
||||
if (number === 0) {
|
||||
handleEmptyCart()
|
||||
return
|
||||
}
|
||||
|
||||
const oldQuantity = quantity
|
||||
setQuantity(number)
|
||||
|
||||
updateCart({
|
||||
productId: Number(id),
|
||||
variantId: variantId,
|
||||
quantity: number,
|
||||
}, {
|
||||
onError: (error: Error) => {
|
||||
toast(extractErrorMessage(error), 'error')
|
||||
setQuantity(oldQuantity)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleEmptyCart = () => {
|
||||
removeFromCart({
|
||||
productId: Number(id),
|
||||
variantId: variantId,
|
||||
}, {
|
||||
onError: (error: Error) => {
|
||||
toast(extractErrorMessage(error), 'error')
|
||||
},
|
||||
onSuccess: () => {
|
||||
toast('محصول از سبد خرید حذف شد', 'success')
|
||||
setIsInCart(false)
|
||||
setQuantity(0)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
!isInCart && !isPending ?
|
||||
<Button isLoading={isAddingToCart} className='w-full mt-8' onClick={handleAddToCart}>
|
||||
افزودن به سبد خرید
|
||||
</Button>
|
||||
:
|
||||
<div className={"flex items-center gap-2 sm:gap-4 mt-8"}>
|
||||
<div className="h-10 sm:h-12 rounded-lg sm:rounded-xl border border-border bg-white px-2 sm:px-4 flex-1 flex items-center justify-between">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleQuantityChange(quantity + 1)}
|
||||
className="disabled:opacity-40 disabled:cursor-not-allowed p-1"
|
||||
aria-label="increase quantity"
|
||||
disabled={isAddingToCart || isUpdatingCart}
|
||||
>
|
||||
<Add size={18} color={PRIMARY_COLOR} className="sm:w-5 sm:h-5" />
|
||||
</button>
|
||||
<span className="text-sm sm:text-[18px] font-medium text-primary">
|
||||
{quantity}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleQuantityChange(quantity - 1)}
|
||||
className="disabled:opacity-40 disabled:cursor-not-allowed p-1"
|
||||
disabled={isAddingToCart || isUpdatingCart}
|
||||
aria-label="decrease quantity"
|
||||
>
|
||||
<Minus size={18} color={PRIMARY_COLOR} className="sm:w-5 sm:h-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleEmptyCart}
|
||||
className="h-10 w-10 sm:h-12 sm:w-12 rounded-lg sm:rounded-xl border border-border grid place-items-center bg-white"
|
||||
aria-label="remove from cart"
|
||||
disabled={isRemovingFromCart}
|
||||
>
|
||||
<Trash size={18} color="#AD3434" className="sm:w-5 sm:h-5" />
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default OnlineCart
|
||||
@@ -5,12 +5,14 @@ import { BoxTick, I3DRotate } from 'iconsax-react'
|
||||
import React from 'react'
|
||||
import { Product } from '@/types/product.types'
|
||||
import Image from 'next/image'
|
||||
import Cart from './Cart'
|
||||
|
||||
interface ShopInformationProps {
|
||||
product: Product
|
||||
}
|
||||
|
||||
const ShopInformation = ({ product }: ShopInformationProps) => {
|
||||
|
||||
const defaultVariant = product.default_variant
|
||||
const shop = defaultVariant.shop
|
||||
const price = defaultVariant.price
|
||||
@@ -94,9 +96,7 @@ const ShopInformation = ({ product }: ShopInformationProps) => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button className='w-full mt-8'>
|
||||
افزودن به سبد خرید
|
||||
</Button>
|
||||
<Cart />
|
||||
<Button className='w-full mt-3 bg-[#323232]'>
|
||||
گفتگو با فروشنده
|
||||
</Button>
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import { useLocalCartStore, LocalCartItem } from "../store/LocalCartStore";
|
||||
|
||||
export const useLocalCart = () => {
|
||||
const {
|
||||
items,
|
||||
addItem,
|
||||
updateItem,
|
||||
removeItem,
|
||||
getItem,
|
||||
clearCart,
|
||||
} = useLocalCartStore();
|
||||
|
||||
const addToLocalCart = (
|
||||
productId: number,
|
||||
variantId: string,
|
||||
quantity: number = 1
|
||||
) => {
|
||||
addItem({ productId, variantId, quantity });
|
||||
};
|
||||
|
||||
const updateLocalCartItem = (
|
||||
productId: number,
|
||||
variantId: string,
|
||||
quantity: number
|
||||
) => {
|
||||
if (quantity <= 0) {
|
||||
removeFromLocalCart(productId, variantId);
|
||||
} else {
|
||||
updateItem(productId, variantId, quantity);
|
||||
}
|
||||
};
|
||||
|
||||
const removeFromLocalCart = (productId: number, variantId: string) => {
|
||||
removeItem(productId, variantId);
|
||||
};
|
||||
|
||||
const getLocalCartItem = (productId: number, variantId: string) => {
|
||||
return getItem(productId, variantId);
|
||||
};
|
||||
|
||||
const getLocalCartItems = () => {
|
||||
return items;
|
||||
};
|
||||
|
||||
const getLocalCartItemsCount = () => {
|
||||
return items.reduce((total, item) => total + item.quantity, 0);
|
||||
};
|
||||
|
||||
const clearLocalCart = () => {
|
||||
clearCart();
|
||||
};
|
||||
|
||||
return {
|
||||
localCartItems: items,
|
||||
addToLocalCart,
|
||||
updateLocalCartItem,
|
||||
removeFromLocalCart,
|
||||
getLocalCartItem,
|
||||
getLocalCartItems,
|
||||
getLocalCartItemsCount,
|
||||
clearLocalCart,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,78 @@
|
||||
import { create } from "zustand";
|
||||
import { persist } from "zustand/middleware";
|
||||
|
||||
export type LocalCartItem = {
|
||||
productId: number;
|
||||
variantId: string;
|
||||
quantity: number;
|
||||
};
|
||||
|
||||
export type LocalCartStoreType = {
|
||||
items: LocalCartItem[];
|
||||
addItem: (item: LocalCartItem) => void;
|
||||
updateItem: (productId: number, variantId: string, quantity: number) => void;
|
||||
removeItem: (productId: number, variantId: string) => void;
|
||||
getItem: (productId: number, variantId: string) => LocalCartItem | undefined;
|
||||
clearCart: () => void;
|
||||
};
|
||||
|
||||
export const useLocalCartStore = create<LocalCartStoreType>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
items: [],
|
||||
|
||||
addItem: (item: LocalCartItem) => {
|
||||
const existingItem = get().items.find(
|
||||
(i) =>
|
||||
i.productId === item.productId && i.variantId === item.variantId
|
||||
);
|
||||
|
||||
if (existingItem) {
|
||||
set((state) => ({
|
||||
items: state.items.map((i) =>
|
||||
i.productId === item.productId && i.variantId === item.variantId
|
||||
? { ...i, quantity: i.quantity + item.quantity }
|
||||
: i
|
||||
),
|
||||
}));
|
||||
} else {
|
||||
set((state) => ({
|
||||
items: [...state.items, item],
|
||||
}));
|
||||
}
|
||||
},
|
||||
|
||||
updateItem: (productId: number, variantId: string, quantity: number) => {
|
||||
set((state) => ({
|
||||
items: state.items.map((item) =>
|
||||
item.productId === productId && item.variantId === variantId
|
||||
? { ...item, quantity }
|
||||
: item
|
||||
),
|
||||
}));
|
||||
},
|
||||
|
||||
removeItem: (productId: number, variantId: string) => {
|
||||
set((state) => ({
|
||||
items: state.items.filter(
|
||||
(item) =>
|
||||
!(item.productId === productId && item.variantId === variantId)
|
||||
),
|
||||
}));
|
||||
},
|
||||
|
||||
getItem: (productId: number, variantId: string) => {
|
||||
return get().items.find(
|
||||
(item) => item.productId === productId && item.variantId === variantId
|
||||
);
|
||||
},
|
||||
|
||||
clearCart: () => {
|
||||
set({ items: [] });
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: "local-cart-storage",
|
||||
}
|
||||
)
|
||||
);
|
||||
@@ -0,0 +1,7 @@
|
||||
import { create } from "zustand";
|
||||
import { ProductStoreType } from "../types/Types";
|
||||
|
||||
export const useProductStore = create<ProductStoreType>((set) => ({
|
||||
variantId: "",
|
||||
setVariantId: (value) => set({ variantId: value }),
|
||||
}));
|
||||
@@ -11,3 +11,8 @@ export type AddQuestionProps = {
|
||||
content: string;
|
||||
productId?: string;
|
||||
};
|
||||
|
||||
export type ProductStoreType = {
|
||||
variantId: string;
|
||||
setVariantId: (value: string) => void;
|
||||
};
|
||||
|
||||
@@ -29,7 +29,7 @@ const ToastContainer: React.FC = () => {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="fixed top-4 text-sm right-0 left-0 mx-auto w-fit z-50 flex flex-col gap-2">
|
||||
<div className="fixed top-4 text-sm right-0 left-0 mx-auto w-fit z-[9999999999] flex flex-col gap-2">
|
||||
{toasts.map((toast) => (
|
||||
<div
|
||||
key={toast.id}
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
export const TOKEN_NAME = "sh_token";
|
||||
export const REFRESH_TOKEN_NAME = "sh_refresh_token";
|
||||
// export const BASE_URL = "https://api.shinan.ir";
|
||||
export const BASE_URL = "https://api-sondos.run.danakcorp.com";
|
||||
export const BASE_URL = "https://api.shinan.ir";
|
||||
// export const BASE_URL = "https://api-sondos.run.danakcorp.com";
|
||||
|
||||
export const PRIMARY_COLOR = "#015699";
|
||||
|
||||
+15
-1
@@ -2,11 +2,25 @@
|
||||
import Input from '@/components/Input'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { DocumentText, Home, Profile, ShoppingCart, HambergerMenu } from 'iconsax-react'
|
||||
import { FC, Fragment, useState } from 'react'
|
||||
import { FC, Fragment, useEffect, useState } from 'react'
|
||||
import Menu from './components/Menu'
|
||||
import { useSharedStore } from './store/sharedStore'
|
||||
import { getToken } from '@/config/func'
|
||||
|
||||
const Header: FC = () => {
|
||||
|
||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false)
|
||||
const { setIsLogin } = useSharedStore()
|
||||
|
||||
const handleSetIsLogin = async () => {
|
||||
const token = await getToken()
|
||||
setIsLogin(token ? true : false)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
handleSetIsLogin()
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
|
||||
Reference in New Issue
Block a user