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

@@ -1,5 +1,7 @@
import {auth} from '@/auth'
import AdminPermission from '@/components/(protected)/admin/auth/permission'
import {CreateForm} from '@/components/(protected)/admin/category/create-form'
import ProductCreateEditForm from '@/components/(protected)/admin/product/create-edit-form'
import {dump} from '@/lib/utils'
export default async function Page({
@@ -12,7 +14,12 @@ export default async function Page({
switch ((slug || [])[0]) {
case 'create':
return <CreateForm />
return (
<>
<AdminPermission />
<CreateForm />
</>
)
}
return <div>{dump(slug)}</div>

View File

@@ -0,0 +1,23 @@
import AdminPermission from '@/components/(protected)/admin/auth/permission'
import {EntityCrudForm} from '@/components/(protected)/admin/entity/crud-form'
import {dump} from '@/lib/utils'
export default async function Page({
params
}: {
params: Promise<{slug?: string[]}>
}) {
const {slug} = await params
switch ((slug || [])[0]) {
case 'create':
return (
<>
<AdminPermission />
<EntityCrudForm />
</>
)
}
return <div>{dump(slug)}</div>
}

View File

@@ -0,0 +1,16 @@
import Link from 'next/link'
import AdminPermission from '@/components/(protected)/admin/auth/permission'
export default function AdminEntityPage() {
return (
<div>
<AdminPermission />
<p>
<Link href='/admin/entity/create'>
Створити блок / статтю / сторінку
</Link>
</p>
</div>
)
}

View File

@@ -0,0 +1,30 @@
import {Order} from '@prisma/client'
import Link from 'next/link'
import {getAllOrders} from '@/actions/admin/order'
import AdminPermission from '@/components/(protected)/admin/auth/permission'
import {dump} from '@/lib/utils'
export default async function AdminOrderPage() {
const orders = await getAllOrders()
return (
<div>
<AdminPermission />
{orders.map((order: Order) => (
<div
key={order.id}
className='flex items-center justify-start gap-x-9 gap-y-6'
>
<div>{order.orderNo}</div>
<div>
{order.firstName} {order.surname}
</div>
<div>{order.phone}</div>
<div>{order.email}</div>
<div>{order.notes}</div>
</div>
))}
</div>
)
}

View File

@@ -1,3 +1,5 @@
import AdminPermission from '@/components/(protected)/admin/auth/permission'
import {EntityCrudForm} from '@/components/(protected)/admin/entity/crud-form'
import ProductCreateEditForm from '@/components/(protected)/admin/product/create-edit-form'
import {getProductById} from '@/lib/data/models/product'
import {dump} from '@/lib/utils'
@@ -21,9 +23,19 @@ export default async function Page({
switch (method) {
case 'create':
return <ProductCreateEditForm />
return (
<>
<AdminPermission />
<ProductCreateEditForm />
</>
)
case 'update':
return <ProductCreateEditForm data={data} />
return (
<>
<AdminPermission />
<ProductCreateEditForm data={data} />
</>
)
default:
return <div>{dump(slug)}</div>
}

View File

@@ -1,100 +0,0 @@
import React from 'react'
export default function AboutUsPage() {
return (
<div className='mb-12 mt-8'>
<div className='container max-w-[922px] text-lg text-brand-violet-950'>
<h1 className='text-3xl font-bold text-brand-violet'>
Bewell: здоровий спосіб життя для всіх
</h1>
<div className='py-4'>
<strong>Інтернет-магазин біологічних добавок Bewell</strong> це
зручна і надійна крамниця для всіх, хто піклується про здоровя. У нас
ви знайдете якісні дієтичні добавки від європейських виробників і
зможете подбати про себе і про рідних без зайвих клопотів.
</div>
<h2 className='my-4 text-2xl font-bold text-brand-violet'>
Асортимент магазину <strong>Bewell</strong>
</h2>
<p>
У нас є все, що потрібно для профілактики різноманітних захворювань та
підтримки здоровя. Це комплексні препарати, у складі яких
переважають:
</p>
<ul className='my-4 ml-12 list-disc'>
<li>екстракти рослин;</li>
<li>мікро- та макроелементи;</li>
<li>вітаміни.</li>
</ul>
<p>
Усі ці компоненти необхідно споживати щоденно, щоб отримати добову
норму вітамінів, мікро- та макроелементів. Дієтичні добавки просте
доповнення до харчування, яке забезпечує організм необхідними
поживними речовинами.
</p>
<p>
Щоб визначитися, яка з добавок підійде саме вам, пропонуємо зануритися
в наш каталог і вибрати відповідний розділ:
</p>
<ul className='my-4 ml-12 list-decimal'>
<li>
Комплексні препарати. Універсальні добавки для поліпшення здоровя.
Це вітаміни та мікроелементи для волосся, шкіри, підтримки
імунітету, серця, нервової системи, травлення тощо.
</li>
<li>
Добавки для підтримки жіночого здоровя (
<strong>вітаміни для жінок</strong>). Це зокрема препарати для
відновлення менструального циклу, для полегшення симптомів
менопаузи.
</li>
<li>
Добавки для підтримки здоровя чоловіків (
<strong>вітаміни для чоловіків</strong>). Препарати для покращення
статевої функції, здоровя передміхурової залози.
</li>
<li>
Препарати для зміцнення імунітету. Такі добавки корисні не лише для
відновлення після захворювання та лікування, а також для
профілактики захворювань.
</li>
<li>
Добавки для відновлення енергії. Ці засоби допомагають боротися з
втомою, покращують обмін речовин, допомагають почуватися енергійніше
та краще спати.{' '}
</li>
</ul>
<h2 className='my-4 text-2xl font-bold text-brand-violet'>
Як замовити якісні дієтичні добавки?
</h2>
<p>
В <strong>Bewell</strong> можна замовити продукцію відомих
європейських виробників. Дієтичні добавки не є лікарськими засобами,
але це хороша профілактика захворювань та підтримки здоровя.
Препарати, представлені в нашому магазині, можна побачити в
асортименті аптек, адже це перевірена продукція, яка успішно
використовується на лише в Україні, а й в Європі. Щоб зробити
замовлення, виберіть потрібний препарат, додайте до кошика та зазначте
умови відправки та оплати.
</p>
<h2 className='my-4 text-2xl font-bold text-brand-violet'>
Bewell: наша філософія та принцип роботи
</h2>
<p>
Головний пріоритет <strong>Bewell</strong> підтримка здорового
способу життя. Ми віримо, що ключ до гарного самопочуття та довголіття
можна знайти в природі, збалансованому харчуванні та усвідомлений
підтримці організму. Саме тому ми прагнемо допомогти кожному клієнту
знайти найкращі добавки для підтримки організму та профілактики
захворювань.
</p>
<p>
Ми дбаємо про чесність і прозорість пропонуємо лише сертифіковані,
перевірені добавки, які сприяють зміцненню імунітету, відновленню
енергії та покращенню сну, а також внутрішній гармонії.
</p>
</div>
</div>
)
}

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>
)
}

View File

@@ -0,0 +1,65 @@
import {EntityLocale} from '@prisma/client'
import type {Metadata} from 'next'
import {notFound} from 'next/navigation'
import {Suspense} from 'react'
import {getPageEntityBySlug} from '@/actions/admin/entity'
import YoutubeComponent from '@/components/shared/youtube-component'
import {dump, normalizeData, thisLocale} from '@/lib/utils'
import {Skeleton} from '@/ui/skeleton'
type Props = {
params: Promise<{slug?: string}>
}
export const generateMetadata = async ({params}: Props): Promise<Metadata> => {
const {slug} = await params
const page = await getPageEntityBySlug(slug || '')
if (!page) {
notFound()
}
const {locales} = page
const locale: EntityLocale = await thisLocale(locales)
const {title, annotation} = locale
return {
title,
description: normalizeData(annotation, {
stripTags: true
})
}
}
export default async function Pages({params}: Props) {
const {slug} = await params
const page = await getPageEntityBySlug(slug || '')
if (!page) {
notFound()
}
const {locales} = page
const locale: EntityLocale = await thisLocale(locales)
const {title, annotation, body} = locale
return (
<div className='mb-12 mt-8'>
<div className='bw-page container max-w-[800px] text-lg text-brand-violet-950'>
<h1>{title}</h1>
<section className='min-h-[450px]'>
<Suspense fallback={<Skeleton className='h-full w-full' />}>
<YoutubeComponent id='qfg2UlQk__M' />
</Suspense>
</section>
<article
className='mt-6'
dangerouslySetInnerHTML={{
__html: ((annotation ?? '') + body) as string
}}
></article>
{/*{dump(locale)}*/}
</div>
</div>
)
}

View File

@@ -1,6 +1,9 @@
import {getProductByIdWitData} from '@prisma/client/sql'
import {notFound} from 'next/navigation'
import ProductPageIndex from '@/components/pages/product'
import {CategoryPageSqlSchema} from '@/lib/data/models/sqlSchemas'
import {db} from '@/lib/db/prisma/client'
export default async function Products({
params
@@ -12,5 +15,9 @@ export default async function Products({
const id = (uri || '').match(/^(\d+)-./)
if (!id) notFound()
return <ProductPageIndex id={id[1]} />
const data: CategoryPageSqlSchema[] = await db.$queryRawTyped(
getProductByIdWitData(id[1])
)
return <ProductPageIndex data={data} id={id[1]} />
}

View File

@@ -1,11 +1,13 @@
import {getCatalogIndexData} from '@prisma/client/sql'
import {getLocale} from 'next-intl/server'
import Image from 'next/image'
import React from 'react'
import FeatureCards from '@/components/shared/home/feature-cards'
import {HomeCarousel} from '@/components/shared/home/home-carousel'
import AppCatalog from '@/components/shared/sidebar/app-catalog'
import FeatureCardFront from '@/components/shared/store/feature-card-front'
import Terms from '@/components/shared/terms'
import {carousels} from '@/lib/data'
import {CategoryPageSqlSchema} from '@/lib/data/models/sqlSchemas'
import {db} from '@/lib/db/prisma/client'
@@ -31,9 +33,9 @@ import image from '@/public/uploads/products/IMG_6572.jpg'
// }
export default async function HomePage() {
const loc = await getLocale()
const locale = await getLocale()
const catalog: CategoryPageSqlSchema[] = await db.$queryRawTyped(
getCatalogIndexData(loc)
getCatalogIndexData(locale)
)
return (
@@ -51,8 +53,18 @@ export default async function HomePage() {
</div>
</div>
<section className='container mb-4 mt-[128px]'>
<section className='container mb-4 mt-8'>
<FeatureCards items={catalog} />
</section>
<Terms />
<section className='container mb-4 mt-12'>
<h2 className='font-heading text-center text-3xl font-bold uppercase tracking-tight text-brand-violet'>
{locale !== 'ru' ? "Цікаво про здоров'я" : 'Интересно о здоровье'}
</h2>
</section>
<section className='container mb-4 mt-8'>
<div className='re relative my-12 overflow-hidden'>
<Image
alt={''}

150
app/api/nova-post/route.ts Normal file
View File

@@ -0,0 +1,150 @@
'use server'
import {NextRequest} from 'next/server'
import {json} from 'node:stream/consumers'
import {type Warehouse} from '@/lib/nova-post-helper'
// , res: Response
export async function GET(req: NextRequest) {
const searchParams = req.nextUrl.searchParams
const scope = searchParams.get('scope')
let response: any = []
switch (scope) {
case 'cities':
response = await getCities(searchParams.get('q') || '...')
break
case 'warehouses':
response = await getWarehouses(searchParams.get('q') || '...')
break
case 'warehouse':
response = await getWarehouse(searchParams.get('q') || '...')
break
case 'streets':
response = await getStreet(searchParams.get('q') || '...')
break
}
return new Response(
JSON.stringify(response.success ? response.data : [], null, 2),
{
status: 200,
headers: {'Content-Type': 'application/json; charset=utf-8'}
}
)
}
async function fetchApi(init: RequestInit): Promise<Response> {
return await fetch(process.env.NOVA_POST_API_EP || '', init)
}
async function getWarehouses(CityRef: string, Page: number = 1) {
// const branches = []
let c = 0
// let n = 0
const Limit = 500
/*do {
const response = await fetchApi({
method: 'POST',
body: JSON.stringify({
apiKey: process.env.NOVA_POST_API_KEY || '',
modelName: 'AddressGeneral',
calledMethod: 'getWarehouses',
methodProperties: {
CityRef,
Page: ++c,
Limit,
FindByString: 'відд'
}
})
})
list = await response.json()
n = Math.ceil(list.info.totalCount / Limit)
for (const i in list.data) {
if (list.data[i].Description.trim().match(/^відді/iu)) {
branches.push(list.data[i])
}
}
} while (c < n)
list.data = branches*/
const response = await fetchApi({
method: 'POST',
body: JSON.stringify({
apiKey: process.env.NOVA_POST_API_KEY || '',
modelName: 'AddressGeneral',
calledMethod: 'getWarehouses',
methodProperties: {
CityRef,
Page: ++c,
Limit,
FindByString: 'відд'
}
})
})
const list = await response.json()
list.data = list.data.filter((item: Warehouse) =>
item.Description.trim().match(/^відді/iu)
)
//console.log(Math.ceil(list.info.totalCount / Limit), list.data.length)
return list
}
async function getWarehouse(Ref: string) {
const response = await fetch(process.env.NOVA_POST_API_EP || '', {
method: 'POST',
body: JSON.stringify({
apiKey: process.env.NOVA_POST_API_KEY || '',
modelName: 'AddressGeneral',
calledMethod: 'getWarehouses',
methodProperties: {
Ref,
Limit: 1
}
})
})
return await response.json()
}
async function getCities(searchString: string) {
const response = await fetch(process.env.NOVA_POST_API_EP || '', {
method: 'POST',
body: JSON.stringify({
apiKey: process.env.NOVA_POST_API_KEY || '',
modelName: 'AddressGeneral',
calledMethod: 'getCities',
methodProperties: {
FindByString: searchString,
Limit: 500
}
})
})
//console.log(searchString)
return await response.json()
}
async function getStreet(StreetName: string) {
const response = await fetch(process.env.NOVA_POST_API_EP || '', {
method: 'POST',
body: JSON.stringify({
apiKey: process.env.NOVA_POST_API_KEY || '',
modelName: 'AddressGeneral',
calledMethod: 'searchSettlementStreets',
methodProperties: {
SettlementRef: 'e718a680-4b33-11e4-ab6d-005056801329',
Page: 1,
Limit: 50,
StreetName
}
})
})
return await response.json()
}

View File

@@ -4,10 +4,8 @@
@tailwind components;
@tailwind utilities;
body {
margin: 0;
padding: 0;
overflow-y: scroll;
html, body {
@apply h-full m-0 p-0;
}
@layer base {
@@ -115,11 +113,11 @@ body {
}
.bw-layout-col-left {
@apply flex-1 sm:w-7/12 md:w-5/12 xl:w-4/12 lg:flex-col
@apply flex-1 sm:w-7/12 md:w-5/12 xl:w-5/12 lg:flex-col
}
.bw-layout-col-right {
@apply sm:w-5/12 md:w-7/12 xl:w-8/12 flex-1 sm:flex-auto sm:pl-4 md:pl-7 xl:pl-9
@apply sm:w-5/12 md:w-7/12 xl:w-7/12 flex-1 sm:flex-auto sm:pl-4 md:pl-7 xl:pl-9
}
.bw-product-col-left{
@@ -134,7 +132,7 @@ body {
}
.bw-header-col-right {
@apply flex-grow-0 flex-shrink-0 md:basis-[272px]
@apply flex-grow-0 flex-shrink-0 md:basis-[142px]
}
.bw-border-color {
@@ -146,6 +144,76 @@ body {
}
}
.bw-terms-section {
.bw-accordion-item {
@apply w-[100%] flex-none overflow-hidden border-b-0 md:w-[46.75%] lg:w-[29.25%]; /*shadow-md*/
/*&:hover {
@apply bg-brand-violet-800/25;
}*/
&[data-state="open"]{
@apply border-brand-violet-800/25 border-[2px] rounded-lg; /*shadow-md shadow-brand-violet-900/30*/
.bw-accordion-content {
@apply rounded-br-lg rounded-bl-lg bg-brand-violet-50/15; /*border-r-[2px] border-b-[2px] border-l-[2px]*/
}/*bg-brand-violet-50/50*/
}
button {
&[data-state="closed"] {
@apply border-[2px] rounded-lg; /*shadow-lg border-brand-violet-900/10*/
}
&[data-state="open"] {
@apply border-[2px] rounded-none py-2;
}
}
}
.bw-accordion-trigger {
@apply text-center text-base text-brand-violet antialiased font-semibold font-heading;
}
button {
padding: 10px 16px;
text-align: center;
font-size: 1rem;
&:hover, &[data-state="open"] {
@apply bg-brand-violet-800/25 text-white border-brand-violet-800/5; /*border-t-2*/
}
}
svg {
@apply w-6 h-6;
}
h3[data-state="open"], h3:hover {
& > button > svg {
@apply stroke-white;
}
}
.bw-accordion-content {
@apply px-4 text-[15px] leading-relaxed tracking-wide text-brand-violet-900;
p {
@apply block my-[1em] mx-0;
}
a {
@apply font-semibold text-brand-violet-600 hover:underline;
}
ul {
@apply block list-disc my-[1em] m-0 pl-10;
}
}
}
.bw-dd-menu {
/* since nested groupes are not supported we have to use
regular css for the nested dropdowns
@@ -165,6 +233,40 @@ body {
.group:hover .group-hover\:-rotate-180 { transform: rotate(180deg) }
}
.bw-page {
@apply leading-relaxed tracking-tight mt-10;
h1 {
@apply font-heading my-[0.67em] mx-0 text-3xl font-bold;
}
h2 {
@apply font-heading mt-[0.83em] mb-[0.25em] mx-0 text-xl font-semibold;
}
p {
@apply block mb-[1em] mx-0;
}
/*a {
@apply font-semibold text-brand-violet-600 hover:underline;
}*/
ul {
@apply block list-disc my-[1em] m-0 pl-10;
}
article {
@apply leading-relaxed;
}
/*display: block;
font-size: 2em;
margin-top: 0.67em;
margin-bottom: 0.67em;
margin-left: 0;
margin-right: 0;
font-weight: bold;*/
}
.jodit-wysiwyg > * {
all: revert;
@@ -180,6 +282,7 @@ body {
}*/
}
#admin-bw-panel form {
input {
@apply bg-white outline-0 text-[16px] leading-none;
@@ -265,3 +368,12 @@ body {
flex: 1;
}
}
/*
https://www.w3schools.com/cssref/css_default_values.php*/
.bw-yt-video {
@apply aspect-video w-full self-stretch md:min-h-96;
}

View File

@@ -1,4 +1,5 @@
import type {Metadata} from 'next'
import localFont from 'next/font/local'
import {headers} from 'next/headers'
import {ReactNode} from 'react'
import {Toaster} from 'react-hot-toast'
@@ -16,6 +17,42 @@ export const metadata: Metadata = {
description: APP_DESCRIPTION
}
const Myriad = localFont({
variable: '--font-myriad',
src: [
/*{
path: '../public/fonts/myriad-light.woff2',
weight: '300',
style: 'normal'
},*/
{
path: '../public/fonts/myriad-regular.woff2',
weight: '400',
style: 'normal'
},
/*{
path: '../public/fonts/myriad-it.woff2',
weight: '400',
style: 'italic'
},*/
{
path: '../public/fonts/myriad-semibold.woff2',
weight: '600',
style: 'normal'
},
{
path: '../public/fonts/myriad-bold.woff2',
weight: '700',
style: 'normal'
}
/*{
path: '../public/fonts/myriad-boldit.woff2',
weight: '700',
style: 'italic'
}*/
]
})
export default async function RootLayout({
children
}: Readonly<{children: ReactNode}>) {
@@ -23,8 +60,8 @@ export default async function RootLayout({
const locale = headersList.get('x-site-locale') ?? routing.defaultLocale
return (
<html lang={locale} suppressHydrationWarning>
<body className='min-h-screen antialiased'>
<html lang={locale} suppressHydrationWarning className={Myriad.variable}>
<body className={`min-h-screen antialiased`}>
{children}
{/*<Toaster />*/}
<Toaster position='top-right' reverseOrder={false} />