stuff done

This commit is contained in:
2025-03-11 02:54:09 +02:00
parent 58e7ed2f06
commit 516b45fad9
90 changed files with 2950 additions and 9458 deletions

View File

@@ -0,0 +1,114 @@
'use client'
import {Trash2} from 'lucide-react'
import {useTranslations} from 'next-intl'
import Image from 'next/image'
import {useState} from 'react'
import CartPostSubmit from '@/app/[locale]/(root)/(shop)/cart/post-submit'
import styles from '@/components/pages/cart/cart.module.scss'
import CartItems from '@/components/pages/cart/items'
import RegisteredOrderForm from '@/components/pages/cart/registered-order-form'
import {Tabs, TabsContent, TabsList, TabsTrigger} from '@/components/ui/tabs'
import {Link} from '@/i18n/routing'
import {dump} from '@/lib/utils'
import EmptyCartImage from '@/public/images/empty-cart.svg'
import useCartStore from '@/store/cart-store'
import {SessionUser} from '@/types/auth'
import {Button} from '@/ui/button'
export default function Cart({user}: {user?: SessionUser | null}) {
const t = useTranslations('cart')
const [submitResult, setSubmitResult] = useState({})
const {cartItems, clearCart} = useCartStore()
const totalSum = cartItems.reduce(
(total, product) => total + parseFloat(product.price) * product.quantity,
0
)
const resultSubmit = (result: any) => {
setSubmitResult(result)
}
return (
<div className='mt-1'>
<div className='container'>
<section className='bw-cart-wrapper mx-auto my-8 max-w-[640px] text-brand-violet'>
{cartItems && cartItems.length > 0 ? (
<>
<div className='mb-6 flex items-center justify-between border-b border-b-brand-violet pb-2'>
<h1 className='text-3xl font-bold'>{t('basket')}</h1>
<Button
variant={'ghost'}
className='rounded-0 h-[3rem] w-[3rem] px-0 text-brand-violet'
onClick={() => clearCart()}
title={t('clear_cart')}
>
<Trash2 size={24} />
</Button>
</div>
<header className='flex text-xl'>
<div className='col'>{t('title')}</div>
<div className='flex-none'>{t('quantity')}</div>
<div className='col text-right'>{t('amount')}</div>
</header>
<CartItems cartItems={cartItems} />
<footer className='my-8 flex py-4 text-xl'>
<div className='col'></div>
<div className='flex-none'>{t('total')}:</div>
<div className='col text-right font-bold'>
{totalSum.toFixed(2)} грн
</div>
</footer>
<section className={styles.bwOrderForm}>
<h2 className='pb-9'>Оформлення замовлення</h2>
<Tabs defaultValue='registered' className='w-full'>
<TabsList className='grid w-full grid-cols-2'>
<TabsTrigger value='registered'>
Постійний клієнт
</TabsTrigger>
<TabsTrigger value='quick-order'>
Швидке замовлення
</TabsTrigger>
</TabsList>
<TabsContent value='registered' className='mt-5'>
<RegisteredOrderForm
styles={styles.registeredForm}
user={user}
onSubmitHandler={resultSubmit}
/>
</TabsContent>
<TabsContent value='quick-order'>quick-order</TabsContent>
</Tabs>
</section>
</>
) : Object.keys(submitResult).length === 0 ? (
<div className='flex flex-col items-center justify-center gap-y-8'>
<Image
src={EmptyCartImage}
sizes='88vw'
alt={t('empty')}
unoptimized={true}
style={{
width: '88%',
height: 'auto',
margin: '1rem auto'
}}
/>
<Link href={'/catalog'} className='px-6 py-2'>
<button className='rounded border border-brand-violet bg-transparent px-4 py-2 font-semibold text-brand-violet hover:border-transparent hover:bg-brand-yellow-300 hover:text-brand-violet-700'>
{t('do_purchase')}
</button>
</Link>
</div>
) : (
<CartPostSubmit result={submitResult} />
)}
</section>
</div>
</div>
)
}

View File

@@ -0,0 +1,236 @@
'use client'
import {
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList
} from 'cmdk'
import {
Check,
ChevronsUpDown,
MapPinCheck,
MapPinPlus,
Warehouse
} from 'lucide-react'
import {useLocale, useTranslations} from 'next-intl'
import {useState} from 'react'
import {useDebouncedCallback} from 'use-debounce'
import {
type Settlement,
type Warehouse as WarehouseType,
formatSettlement,
getApi
} from '@/lib/nova-post-helper'
import {cn} from '@/lib/utils'
import {dump} from '@/lib/utils'
import {Button} from '@/ui/button'
import {Command} from '@/ui/command'
import {Popover, PopoverContent, PopoverTrigger} from '@/ui/popover'
const url = '/api/nova-post'
export default function NovaPost({onSelectHandler}: {onSelectHandler: any}) {
const t = useTranslations('cart.post')
const [citiesOpen, setCitiesOpen] = useState(false)
const [warehousesOpen, setWarehousesOpen] = useState(false)
const [citiesValue, setCitiesValue] = useState('')
const [cityRef, setCityRef] = useState('')
//const [warehouseRef, setWarehouseRef] = useState('')
const [warehousesValue, setWarehousesValue] = useState('')
const [cities, setCities] = useState([])
const [warehouses, setWarehouses] = useState([])
const locale = useLocale()
const handleCitySearch = useDebouncedCallback(
async (e: string): Promise<void> => {
if (e.length < 3) {
setCities([])
return
}
const response = await getApi(url + `?scope=cities&q=` + encodeURI(e))
if (response.ok) {
let json = await response.json()
setCities(json)
} else {
setCities([])
}
},
1000
)
const handleWarehouseSearch = async (e: string): Promise<void> => {
const response = await getApi(`${url}?scope=warehouses&q=${e}`)
if (response.ok) {
let json = await response.json()
setWarehouses(json)
} else {
setWarehouses([])
}
}
const cityDescription = (citiesValue: string): string => {
const city: Settlement | undefined = cities.find(
(city: Settlement) => city.Description === citiesValue
)
if (!city) {
return ''
}
return formatSettlement(city, locale)
}
return (
<div className='py-2'>
<div>
<Popover open={citiesOpen} onOpenChange={setCitiesOpen}>
<PopoverTrigger asChild>
<Button
variant='outline'
role='combobox'
/*aria-expanded={open}*/
className='w-full justify-between border border-brand-violet'
>
<span className='inline-flex items-center gap-x-3'>
{citiesValue ? (
<>
<MapPinCheck />
{cityDescription(citiesValue)}
</>
) : (
<>
<MapPinPlus />
{t('findSettlement')}
</>
)}
</span>
<ChevronsUpDown className='opacity-50' />
</Button>
</PopoverTrigger>
<PopoverContent className='w-[640px] p-2'>
<Command>
<CommandInput
className='border border-brand-violet p-2'
placeholder={t('startSearchSettlement')}
onValueChange={(e: string) => handleCitySearch(e)}
/>
<CommandList>
<CommandEmpty className='my-1'>{t('notFount')}</CommandEmpty>
<CommandGroup className='max-h-[320px] w-full overflow-y-auto'>
{cities.map((city: Settlement) => (
<CommandItem
className='my-2 flex'
key={city?.Ref}
value={city?.Description}
onSelect={(currentValue: string) => {
setCitiesValue(
currentValue === citiesValue ? '' : currentValue
)
setCityRef(
currentValue === citiesValue ? '' : city?.Ref
)
handleWarehouseSearch(
currentValue === citiesValue ? '' : city?.Ref
).then(console.log)
setCitiesOpen(false)
}}
>
{formatSettlement(city, locale)}
<Check
className={cn(
'ml-auto',
citiesValue === city?.Description
? 'opacity-100'
: 'opacity-0'
)}
/>
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
</div>
{cityRef !== '' && (
<div className='pt-3'>
<Popover open={warehousesOpen} onOpenChange={setWarehousesOpen}>
<PopoverTrigger asChild>
<Button
variant='outline'
role='combobox'
/*aria-expanded={open}*/
className='w-full justify-between'
>
<span className='inline-flex items-center gap-x-3'>
<Warehouse />
{warehousesValue ? warehousesValue : t('selectWarehouse')}
</span>
<ChevronsUpDown className='opacity-50' />
</Button>
</PopoverTrigger>
<PopoverContent className='w-[640px] p-2'>
<Command>
<CommandInput
className='p-2'
placeholder={t('startSearchWarehouse')}
/*onValueChange={(e: string) => handleCitySearch(e)}*/
/>
<CommandList>
<CommandEmpty className='my-1'>{t('notFount')}</CommandEmpty>
<CommandGroup className='max-h-[320px] w-full overflow-y-auto'>
{warehouses.map((warehouse: WarehouseType) => (
<CommandItem
className='my-2 flex'
key={warehouse.Ref}
value={warehouse.Description}
onSelect={(currentValue: string) => {
setWarehousesValue(
currentValue === warehousesValue ? '' : currentValue
)
/*setWarehouseRef(
currentValue === warehousesValue
? ''
: warehouse.Ref
)*/
onSelectHandler(
currentValue === warehousesValue
? {}
: {
Ref: warehouse.Ref,
Description: warehouse.Description,
DescriptionRu: warehouse.DescriptionRu
}
)
setWarehousesOpen(false)
}}
>
{warehouse.Description}
<Check
className={cn(
'ml-auto',
warehousesValue === warehouse.Description
? 'opacity-100'
: 'opacity-0'
)}
/>
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
</div>
)}
</div>
)
}

View File

@@ -1,46 +1,18 @@
'use client'
import Cart from '@/app/[locale]/(root)/(shop)/cart/cart'
import {auth} from '@/auth'
import {SessionUser} from '@/types/auth'
import {useTranslations} from 'next-intl'
export default async function Page() {
const session = await auth()
if (!session) {
return <Cart />
}
import CartItems from '@/components/pages/cart/items'
import useCartStore from '@/store/cart-store'
const {user} = session
export default function Cart() {
const t = useTranslations('cart')
const {cartItems} = useCartStore()
const totalSum = cartItems.reduce(
(total, product) => total + parseFloat(product.price) * product.quantity,
0
)
// 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='bw-cart-wrapper mx-auto my-8 max-w-[640px] text-brand-violet'>
<h1 className='mb-6 border-b border-b-brand-violet pb-6 text-3xl font-bold'>
{t('basket')}
</h1>
<header className='flex text-xl'>
<div className='col'>{t('title')}</div>
<div className='flex-none'>{t('quantity')}</div>
<div className='col text-right'>{t('amount')}</div>
</header>
<CartItems />
<footer className='my-8 flex border-y border-y-brand-violet py-4 text-xl'>
<div className='col'></div>
<div className='flex-none'>{t('total')}:</div>
<div className='col text-right font-bold'>
{totalSum.toFixed(2)} грн
</div>
</footer>
</section>
</div>
</div>
return session ? (
<Cart user={user as unknown as SessionUser} />
) : (
<Cart user={null} />
)
}

View File

@@ -0,0 +1,23 @@
export default function CartPostSubmit({result}: any) {
if (result?.success) {
return (
<div>
<h1 className='text-2xl'>
Номер Вашого замовлення:{' '}
<span className='font-semibold text-brand-violet-950'>
{result?.success}
</span>{' '}
</h1>
</div>
)
}
return (
<div>
<h1 className='text-2xl'>
Сталася помилка:{' '}
<span className='font-semibold text-red-800'>{result?.error}</span>{' '}
</h1>
</div>
)
}