cart mechanism complete
This commit is contained in:
@@ -12,6 +12,7 @@ import {i18nDefaultLocale, i18nLocales} from '@/i18n-config'
|
||||
import {BaseEditorConfig} from '@/lib/config/editor'
|
||||
import {createProductFormSchema} from '@/lib/schemas/admin/product'
|
||||
import {toEmptyParams} from '@/lib/utils'
|
||||
import useCountStore from '@/store/cart-store'
|
||||
import {Button} from '@/ui/button'
|
||||
import {
|
||||
Form,
|
||||
|
||||
4
components/pages/cart/cart.module.scss
Normal file
4
components/pages/cart/cart.module.scss
Normal file
@@ -0,0 +1,4 @@
|
||||
input.bw-cart-item-counter{
|
||||
font-size: 36px;
|
||||
background: chocolate;
|
||||
}
|
||||
74
components/pages/cart/items.tsx
Normal file
74
components/pages/cart/items.tsx
Normal 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>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
}
|
||||
28
components/pages/product/add-cart-button.tsx
Normal file
28
components/pages/product/add-cart-button.tsx
Normal 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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
57
components/pages/product/index.tsx
Normal file
57
components/pages/product/index.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -23,7 +23,6 @@ export default async function CabinetButton() {
|
||||
) : (
|
||||
<>
|
||||
<CircleUserRound className='h-[21px] w-[21px]' />
|
||||
GA4_Ecommerce_View_Item_List_Trigger
|
||||
</>
|
||||
)}
|
||||
{/*<span className='text-sm'>Кабінет</span>*/}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import {Heart, ShoppingCartIcon, UserIcon} from 'lucide-react'
|
||||
import {Heart} from 'lucide-react'
|
||||
import {useTranslations} from 'next-intl'
|
||||
|
||||
import CabinetButton from '@/components/shared/header/cabinet-button'
|
||||
import HeaderShoppingCartIcon from '@/components/shared/header/shopping-cart-icon'
|
||||
import {Link} from '@/i18n/routing'
|
||||
|
||||
export default function HeaderControls() {
|
||||
@@ -18,17 +19,7 @@ export default function HeaderControls() {
|
||||
</button>
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
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>
|
||||
<HeaderShoppingCartIcon />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
25
components/shared/header/shopping-cart-icon.tsx
Normal file
25
components/shared/header/shopping-cart-icon.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import NavbarMenu from '@/components/shared/navbar/navbar-menu'
|
||||
|
||||
export default function Navbar() {
|
||||
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 />
|
||||
</nav>
|
||||
)
|
||||
|
||||
@@ -27,18 +27,18 @@ export default function NavbarMenu() {
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className={`flex items-center ${bp}:hidden`}>
|
||||
{/*<div className={`flex items-center ${bp}:hidden`}>
|
||||
<Button variant='ghost' onClick={ToggleNavbar}>
|
||||
{menuOpened ? <X /> : <MenuIcon />}
|
||||
</Button>
|
||||
</div>
|
||||
{menuOpened && (
|
||||
</div>*/}
|
||||
{/*{menuOpened && (
|
||||
<div className={`${bp}:hidden`}>
|
||||
<div className='space-y-1 px-2 pb-3 pt-2'>
|
||||
<Link href={'#'}>Hidden Menu</Link>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
)}*/}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user