cart mechanism complete

This commit is contained in:
2025-02-05 21:17:25 +02:00
parent 5ac895ea3e
commit f594f001f6
24 changed files with 441 additions and 78 deletions

1
.gitignore vendored
View File

@@ -125,3 +125,4 @@ dist
.pnp.js .pnp.js
.env*.local .env*.local
/messages/*.d.json.ts /messages/*.d.json.ts
/public/uploads/

View File

@@ -54,7 +54,7 @@
<option name="KEEP_SIMPLE_METHODS_IN_ONE_LINE" value="true" /> <option name="KEEP_SIMPLE_METHODS_IN_ONE_LINE" value="true" />
<option name="ARRAY_INITIALIZER_WRAP" value="5" /> <option name="ARRAY_INITIALIZER_WRAP" value="5" />
<option name="ARRAY_INITIALIZER_LBRACE_ON_NEXT_LINE" value="true" /> <option name="ARRAY_INITIALIZER_LBRACE_ON_NEXT_LINE" value="true" />
<option name="SOFT_MARGINS" value="92" /> <option name="SOFT_MARGINS" value="80" />
<indentOptions> <indentOptions>
<option name="INDENT_SIZE" value="2" /> <option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" /> <option name="CONTINUATION_INDENT_SIZE" value="2" />

4
.idea/watcherTasks.xml generated Normal file
View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectTasksOptions" suppressed-tasks="SCSS" />
</project>

View File

@@ -0,0 +1,31 @@
'use client'
import {useTranslations} from 'next-intl'
import CartItems from '@/components/pages/cart/items'
export default function Cart() {
const t = useTranslations('cart')
// const subtotal = items.reduce(
// (total, item) => total + item.price * item.quantity,
// 0
// )
// const total = subtotal
return (
<div className='mt-1'>
<div className='container'>
<section className='mx-auto my-8 max-w-[640px] text-brand-violet'>
<h1 className='text-3xl font-bold'>{t('basket')}</h1>
<div className='bsdg-brand-violet-200 my-4 grid grid-cols-3 gap-4 border-t-2 border-brand-violet py-4'>
<div className='bg-brand-violet-100'>Назва</div>
<div className='bg-brand-violet-100'>Кількість</div>
<div className='bg-brand-violet-100'>Вартість</div>
</div>
<CartItems />
</section>
</div>
</div>
)
}

View File

@@ -1,12 +0,0 @@
import {Metadata} from 'next'
export const metadata: Metadata = {
title: 'Checkout'
}
export default function CheckoutPage() {
//throw new Error('NOT IMPLEMENTED')
//const session = await auth()
return <div>CheckoutPage</div>
}

View File

@@ -0,0 +1,16 @@
import {notFound} from 'next/navigation'
import ProductPageIndex from '@/components/pages/product'
export default async function Products({
params
}: {
params: Promise<{slug?: string}>
}) {
const {slug} = await params
const [uri] = slug || []
const id = (uri || '').match(/^(\d+)-./)
if (!id) notFound()
return <ProductPageIndex id={id[1]} />
}

View File

@@ -43,22 +43,6 @@ export default async function HomePage() {
</div> </div>
{/*<pre>{JSON.stringify(session)}</pre>*/} {/*<pre>{JSON.stringify(session)}</pre>*/}
<section className='relative mx-auto mt-8 h-[640px] w-[840px] bg-brand-violet-200'>
<Image
src={'/uploads/products/IMG_6572.jpg'}
//fill
//sizes='(min-width: 808px) 50vw, 100vw'
width={1280}
height={1280}
alt=''
title=''
priority
style={{
objectFit: 'contain' // cover, contain, none
}}
/>
</section>
<section className='mb-4 mt-[128px]'> <section className='mb-4 mt-[128px]'>
<div className='container'> <div className='container'>
<FeatureCards /> <FeatureCards />

View File

@@ -214,3 +214,25 @@ body {
} }
} }
} }
.bw-product__text * {
font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji" !important;
font-weight: 400 !important;
font-size: 1rem !important;
line-height: 1.45 !important;
color: rgb(40, 26, 76) !important;
}
.bw-product__text {
h2 * {
font-weight: 700 !important;
font-size: 1.375rem !important;
}
}
.bw-cart-item-counter {
input {
@apply text-xl leading-none border-0 text-brand-violet font-bold;
}
}

View File

@@ -1,9 +1,10 @@
import type {Metadata} from 'next' import type {Metadata} from 'next'
import {headers} from 'next/headers' import {headers} from 'next/headers'
import {ReactNode} from 'react' import {ReactNode} from 'react'
import {Toaster} from 'react-hot-toast'
import './globals.css' import './globals.css'
import {Toaster} from '@/components/ui/toaster' //import {Toaster} from '@/components/ui/toaster'
import {routing} from '@/i18n/routing' import {routing} from '@/i18n/routing'
import {APP_DESCRIPTION, APP_NAME, APP_SLOGAN} from '@/lib/constants' import {APP_DESCRIPTION, APP_NAME, APP_SLOGAN} from '@/lib/constants'
@@ -25,7 +26,8 @@ export default async function RootLayout({
<html lang={locale} suppressHydrationWarning> <html lang={locale} suppressHydrationWarning>
<body className='min-h-screen antialiased'> <body className='min-h-screen antialiased'>
{children} {children}
<Toaster /> {/*<Toaster />*/}
<Toaster position='top-right' reverseOrder={false} />
</body> </body>
</html> </html>
) )

View File

@@ -12,6 +12,7 @@ import {i18nDefaultLocale, i18nLocales} from '@/i18n-config'
import {BaseEditorConfig} from '@/lib/config/editor' import {BaseEditorConfig} from '@/lib/config/editor'
import {createProductFormSchema} from '@/lib/schemas/admin/product' import {createProductFormSchema} from '@/lib/schemas/admin/product'
import {toEmptyParams} from '@/lib/utils' import {toEmptyParams} from '@/lib/utils'
import useCountStore from '@/store/cart-store'
import {Button} from '@/ui/button' import {Button} from '@/ui/button'
import { import {
Form, Form,

View File

@@ -0,0 +1,4 @@
input.bw-cart-item-counter{
font-size: 36px;
background: chocolate;
}

View File

@@ -0,0 +1,74 @@
// import styles from '@/components/pages/cart/cart.module.scss'
import Link from 'next/link'
import useCartStore from '@/store/cart-store'
import {Button} from '@/ui/button'
import {Input} from '@/ui/input'
export default function CartItems() {
const {cartItems} = useCartStore()
const {increaseQuantity, decreaseQuantity, removeItemFromCart} =
useCartStore()
const onIncreaseQuantity = (productId: number) => {
increaseQuantity(productId)
}
const onDecreaseQuantity = (productId: number) => {
decreaseQuantity(productId)
}
const onRemoveItem = (productId: number) => {
removeItemFromCart(productId)
}
if (cartItems && cartItems.length < 1) {
return (
<div className='flex h-72 flex-col items-center justify-center'>
<h2 className='mb-5 mt-10 text-3xl font-bold'>Cart is Empty</h2>
<Link
href={'/products'}
className='rounded-md bg-orange-500 px-6 py-2 text-white'
>
Shop
</Link>
</div>
)
}
return (
<>
{cartItems?.map((item, i) => (
<div
key={i}
className='bsdg-brand-violet-200 bw-cart-item-counter my-4 flex items-center gap-4 py-4'
>
<div className='flex-auto bg-brand-violet-100'>{item.title}</div>
<div className='flex w-16 flex-none items-center justify-center'>
<Button
variant={'outline'}
className='rounded-0'
onClick={() => onDecreaseQuantity(item.id)}
>
-
</Button>
<div className='mx-4 border-0 text-xl font-bold leading-none text-brand-violet'>
{item.quantity}
</div>
<Button
variant={'outline'}
className='rounded-0'
onClick={() => onIncreaseQuantity(item.id)}
>
+
</Button>
</div>
<div className='text-3 flex-auto text-right font-bold'>
{(item.price * item.quantity).toFixed(2)} грн
</div>
</div>
))}
</>
)
}

View File

@@ -0,0 +1,28 @@
'use client'
import {ShoppingCartIcon} from 'lucide-react'
import {useTranslations} from 'next-intl'
import {dump} from '@/lib/utils'
import useCartStore, {CartItem} from '@/store/cart-store'
export default function AddCartButton({product}: {product: CartItem}) {
const t = useTranslations('cart')
const addItemToCart = useCartStore(state => state.addItemToCart)
const {cartItems} = useCartStore(state => state)
return (
<>
<button
className='flex flex-col items-center'
role='button'
title={t('basket')}
onClick={() => addItemToCart(product)}
>
<ShoppingCartIcon className='h-[21px] w-[21px]' />
</button>
<pre>{dump(cartItems)}</pre>
</>
)
}

View File

@@ -0,0 +1,57 @@
import {getLocale} from 'next-intl/server'
import {notFound} from 'next/navigation'
import {strict} from 'node:assert'
import AddCartButton from '@/components/pages/product/add-cart-button'
import {ProductProps, getProductById} from '@/lib/data/models/product'
import {dump} from '@/lib/utils'
import useCartStore from '@/store/cart-store'
import {Tabs, TabsContent, TabsList, TabsTrigger} from '@/ui/tabs'
export default async function ProductPageIndex({id}: {id: string}) {
const product = await getProductById(parseInt(id))
if (!product) notFound()
const {locales, toStore} = product as ProductProps
const lang = await getLocale()
const locale = locales[lang === 'uk' ? 0 : 1]
const store = JSON.parse(JSON.stringify(toStore[0]))
return (
<div className='mt-1'>
<div className='container flex flex-col sm:flex-row'>
<div>
<AddCartButton
product={{
id: product.id,
quantity: 1,
title: locale.title,
price: store.price as string, //parseFloat().toFixed(2),
image: product.image
}}
/>
<hr />
</div>
<div>
<Tabs defaultValue='article' className=''>
<TabsList className='grid w-full grid-cols-2'>
<TabsTrigger value='article'>Опис</TabsTrigger>
<TabsTrigger value='instuction'>Інструкція</TabsTrigger>
</TabsList>
<TabsContent value='article'>
<article
className='bw-product__text'
dangerouslySetInnerHTML={{__html: locale.content as string}}
></article>
</TabsContent>
<TabsContent value='instuction'>
<div
className='bw-product__text'
dangerouslySetInnerHTML={{__html: locale.instruction as string}}
></div>
</TabsContent>
</Tabs>
</div>
</div>
</div>
)
}

View File

@@ -23,7 +23,6 @@ export default async function CabinetButton() {
) : ( ) : (
<> <>
<CircleUserRound className='h-[21px] w-[21px]' /> <CircleUserRound className='h-[21px] w-[21px]' />
GA4_Ecommerce_View_Item_List_Trigger
</> </>
)} )}
{/*<span className='text-sm'>Кабінет</span>*/} {/*<span className='text-sm'>Кабінет</span>*/}

View File

@@ -1,7 +1,8 @@
import {Heart, ShoppingCartIcon, UserIcon} from 'lucide-react' import {Heart} from 'lucide-react'
import {useTranslations} from 'next-intl' import {useTranslations} from 'next-intl'
import CabinetButton from '@/components/shared/header/cabinet-button' import CabinetButton from '@/components/shared/header/cabinet-button'
import HeaderShoppingCartIcon from '@/components/shared/header/shopping-cart-icon'
import {Link} from '@/i18n/routing' import {Link} from '@/i18n/routing'
export default function HeaderControls() { export default function HeaderControls() {
@@ -18,17 +19,7 @@ export default function HeaderControls() {
</button> </button>
</Link> </Link>
<Link <HeaderShoppingCartIcon />
href={'/checkout' as never}
className='header-button'
aria-label='Кошик'
>
<button className='flex flex-col items-center' role='button'>
<ShoppingCartIcon className='h-[21px] w-[21px]' />
<span className='font1-bold text-sm'>{t('basket')}</span>
</button>
</Link>
</div> </div>
) )
} }

View File

@@ -0,0 +1,25 @@
'use client'
import {ShoppingCartIcon} from 'lucide-react'
import {useTranslations} from 'next-intl'
import {Link} from '@/i18n/routing'
import useCartStore from '@/store/cart-store'
export default function HeaderShoppingCartIcon() {
const t = useTranslations('cart')
const {cartItems} = useCartStore()
const cartCount = cartItems.length
return (
<Link href={'/cart' as never} className='header-button' aria-label='Кошик'>
<button className='flex flex-col items-center' role='button'>
<ShoppingCartIcon className='h-[21px] w-[21px]' />
<span className='font1-bold text-sm'>
{t('basket')} [{cartCount}]
</span>
</button>
</Link>
)
}

View File

@@ -2,7 +2,7 @@ import NavbarMenu from '@/components/shared/navbar/navbar-menu'
export default function Navbar() { export default function Navbar() {
return ( return (
<nav className='text-min flex w-full items-center justify-between text-sm font-medium leading-none'> <nav className='text-min flex w-full items-center justify-between text-sm font-medium leading-none text-brand-violet'>
<NavbarMenu /> <NavbarMenu />
</nav> </nav>
) )

View File

@@ -27,18 +27,18 @@ export default function NavbarMenu() {
))} ))}
</div> </div>
</div> </div>
<div className={`flex items-center ${bp}:hidden`}> {/*<div className={`flex items-center ${bp}:hidden`}>
<Button variant='ghost' onClick={ToggleNavbar}> <Button variant='ghost' onClick={ToggleNavbar}>
{menuOpened ? <X /> : <MenuIcon />} {menuOpened ? <X /> : <MenuIcon />}
</Button> </Button>
</div> </div>*/}
{menuOpened && ( {/*{menuOpened && (
<div className={`${bp}:hidden`}> <div className={`${bp}:hidden`}>
<div className='space-y-1 px-2 pb-3 pt-2'> <div className='space-y-1 px-2 pb-3 pt-2'>
<Link href={'#'}>Hidden Menu</Link> <Link href={'#'}>Hidden Menu</Link>
</div> </div>
</div> </div>
)} )}*/}
</> </>
) )
} }

View File

@@ -1,10 +1,14 @@
'use server' 'use server'
import {Lang, Product, ProductLocale} from '@prisma/client' import {Lang, Product, ProductLocale, ProductToStore} from '@prisma/client'
import internal from 'node:stream'
import {db, dbQueryLog} from '@/lib/db/prisma/client' import {db, dbQueryLog} from '@/lib/db/prisma/client'
export interface ProductProps extends Product {
locales: ProductLocale[]
toStore: ProductToStore[]
}
export const getProductBySlug = async (data: { export const getProductBySlug = async (data: {
slug: string slug: string
lang: string lang: string

85
package-lock.json generated
View File

@@ -41,15 +41,16 @@
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
"react-dropzone": "^14.3.5", "react-dropzone": "^14.3.5",
"react-hook-form": "^7.54.2", "react-hook-form": "^7.54.2",
"react-hot-toast": "^2.5.1",
"redis": "^4.7.0", "redis": "^4.7.0",
"sanitize-html": "^2.14.0", "sanitize-html": "^2.14.0",
"server-only": "^0.0.1", "server-only": "^0.0.1",
"slugify": "^1.6.6", "slugify": "^1.6.6",
"swr": "^2.3.0",
"tailwind-merge": "^2.6.0", "tailwind-merge": "^2.6.0",
"tailwind-scrollbar": "^3.1.0", "tailwind-scrollbar": "^3.1.0",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"zod": "^3.24.1" "zod": "^3.24.1",
"zustand": "^5.0.3"
}, },
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3", "@eslint/eslintrc": "^3",
@@ -3790,7 +3791,6 @@
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"devOptional": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/damerau-levenshtein": { "node_modules/damerau-levenshtein": {
@@ -3936,15 +3936,6 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/dequal": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/detect-libc": { "node_modules/detect-libc": {
"version": "2.0.3", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
@@ -5267,6 +5258,15 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/goober": {
"version": "2.1.16",
"resolved": "https://registry.npmjs.org/goober/-/goober-2.1.16.tgz",
"integrity": "sha512-erjk19y1U33+XAMe1VTvIONHYoSqE4iS7BYUZfHaqeohLmnC0FdxEh7rQU+6MZ4OajItzjZFSRtVANrQwNq6/g==",
"license": "MIT",
"peerDependencies": {
"csstype": "^3.0.10"
}
},
"node_modules/gopd": { "node_modules/gopd": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
@@ -7300,6 +7300,23 @@
"react": "^16.8.0 || ^17 || ^18 || ^19" "react": "^16.8.0 || ^17 || ^18 || ^19"
} }
}, },
"node_modules/react-hot-toast": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.5.1.tgz",
"integrity": "sha512-54Gq1ZD1JbmAb4psp9bvFHjS7lje+8ubboUmvKZkCsQBLH6AOpZ9JemfRvIdHcfb9AZXRaFLrb3qUobGYDJhFQ==",
"license": "MIT",
"dependencies": {
"csstype": "^3.1.3",
"goober": "^2.1.16"
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"react": ">=16",
"react-dom": ">=16"
}
},
"node_modules/react-is": { "node_modules/react-is": {
"version": "16.13.1", "version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
@@ -8167,19 +8184,6 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/swr": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/swr/-/swr-2.3.0.tgz",
"integrity": "sha512-NyZ76wA4yElZWBHzSgEJc28a0u6QZvhb6w0azeL2k7+Q1gAzVK+IqQYXhVOC/mzi+HZIozrZvBVeSeOZNR2bqA==",
"license": "MIT",
"dependencies": {
"dequal": "^2.0.3",
"use-sync-external-store": "^1.4.0"
},
"peerDependencies": {
"react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/synckit": { "node_modules/synckit": {
"version": "0.9.2", "version": "0.9.2",
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz", "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz",
@@ -8610,6 +8614,8 @@
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz",
"integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==", "integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==",
"license": "MIT", "license": "MIT",
"optional": true,
"peer": true,
"peerDependencies": { "peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
} }
@@ -8890,6 +8896,35 @@
"dependencies": { "dependencies": {
"@prisma/debug": "6.2.1" "@prisma/debug": "6.2.1"
} }
},
"node_modules/zustand": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.3.tgz",
"integrity": "sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==",
"license": "MIT",
"engines": {
"node": ">=12.20.0"
},
"peerDependencies": {
"@types/react": ">=18.0.0",
"immer": ">=9.0.6",
"react": ">=18.0.0",
"use-sync-external-store": ">=1.2.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"immer": {
"optional": true
},
"react": {
"optional": true
},
"use-sync-external-store": {
"optional": true
}
}
} }
} }
} }

View File

@@ -64,15 +64,16 @@
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
"react-dropzone": "^14.3.5", "react-dropzone": "^14.3.5",
"react-hook-form": "^7.54.2", "react-hook-form": "^7.54.2",
"react-hot-toast": "^2.5.1",
"redis": "^4.7.0", "redis": "^4.7.0",
"sanitize-html": "^2.14.0", "sanitize-html": "^2.14.0",
"server-only": "^0.0.1", "server-only": "^0.0.1",
"slugify": "^1.6.6", "slugify": "^1.6.6",
"swr": "^2.3.0",
"tailwind-merge": "^2.6.0", "tailwind-merge": "^2.6.0",
"tailwind-scrollbar": "^3.1.0", "tailwind-scrollbar": "^3.1.0",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"zod": "^3.24.1" "zod": "^3.24.1",
"zustand": "^5.0.3"
}, },
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3", "@eslint/eslintrc": "^3",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 381 KiB

96
store/cart-store.ts Normal file
View File

@@ -0,0 +1,96 @@
import toast from 'react-hot-toast'
import {create} from 'zustand'
import {persist} from 'zustand/middleware'
export interface CartItem {
id: number
quantity: number
title: string
price: string | any
image?: string | null
}
interface CartState {
cartItems: CartItem[]
addItemToCart: (item: CartItem) => void
increaseQuantity: (productId: number) => void
decreaseQuantity: (productId: number) => void
removeItemFromCart: (productId: number) => void
}
const useCartStore = create(
persist<CartState>(
(set, get) => ({
cartItems: [],
addItemToCart: item => {
const itemExists = get().cartItems.find(
cartItem => cartItem.id === item.id
)
if (itemExists) {
if (typeof itemExists.quantity === 'number') {
itemExists.quantity++
}
set({cartItems: [...get().cartItems]})
} else {
set({cartItems: [...get().cartItems, {...item, quantity: 1}]})
}
},
increaseQuantity: productId => {
const itemExists = get().cartItems.find(
cartItem => cartItem.id === productId
)
if (itemExists) {
if (typeof itemExists.quantity === 'number') {
itemExists.quantity++
}
set({cartItems: [...get().cartItems]})
}
},
decreaseQuantity: productId => {
const itemExists = get().cartItems.find(
cartItem => cartItem.id === productId
)
if (itemExists) {
if (typeof itemExists.quantity === 'number') {
if (itemExists.quantity === 1) {
const updatedCartItems = get().cartItems.filter(
item => item.id !== productId
)
set({cartItems: updatedCartItems})
} else {
itemExists.quantity--
set({cartItems: [...get().cartItems]})
}
}
}
},
removeItemFromCart: productId => {
const itemExists = get().cartItems.find(
cartItem => cartItem.id === productId
)
if (itemExists) {
if (typeof itemExists.quantity === 'number') {
const updatedCartItems = get().cartItems.filter(
item => item.id !== productId
)
set({cartItems: updatedCartItems})
}
}
}
}),
{
name: 'cart-items'
}
)
)
export default useCartStore