From c6c34f045349d8dad983d7900522162ca7ebf260 Mon Sep 17 00:00:00 2001 From: Yevhen Odynets Date: Fri, 7 Feb 2025 08:34:42 +0200 Subject: [PATCH] grand commit --- .gitignore | 1 + .idea/sqldialects.xml | 1 + actions/admin/product.ts | 12 +- actions/model/category.ts | 6 + app/[locale]/(root)/(shop)/about-us/page.tsx | 100 +++++++++ app/[locale]/(root)/(shop)/cart/page.tsx | 16 +- app/[locale]/(root)/(shop)/catalog/page.tsx | 22 ++ .../(shop)/category/[[...slug]]/page.tsx | 22 ++ app/[locale]/(root)/page.tsx | 29 ++- app/globals.css | 33 ++- .../admin/product/create-edit-form.tsx | 16 +- components/pages/cart/items.tsx | 73 ++++--- components/pages/category/page.tsx | 29 +++ components/pages/product/carousel.tsx | 116 +++++++++++ components/pages/product/embla.css | 81 ++++++++ components/pages/product/index.tsx | 102 +++++++--- components/pages/product/thumb.tsx | 46 +++++ components/shared/header/index.tsx | 7 - components/shared/home/feature-cards.tsx | 32 +-- components/shared/locale-switcher.tsx | 32 ++- components/shared/navbar/navbar-menu.tsx | 5 +- .../shared/sidebar/app-catalog-render.tsx | 28 +-- .../store}/add-cart-button.tsx | 17 +- components/shared/store/card-buy-button.tsx | 31 +++ .../shared/store/feature-card-front.tsx | 71 +++++++ components/shared/store/feature-card.tsx | 65 ++++++ components/shared/store/stars.tsx | 16 ++ components/ui/table.tsx | 120 +++++++++++ components/ui/toast.tsx | 135 ------------- components/ui/toaster.tsx | 35 ---- data/products.ts | 9 + hooks/use-toast.ts | 191 ------------------ lib/config/resources.ts | 67 ++++++ lib/data.ts | 2 +- lib/data/models/category.ts | 22 ++ lib/data/models/product.ts | 46 ++++- lib/data/models/sqlSchemas.ts | 23 +++ lib/db/prisma/schema/category.prisma | 4 +- lib/db/prisma/schema/product.prisma | 29 +-- lib/db/prisma/sql/getCatalogIndexData.sql | 20 ++ .../prisma/sql/getCategoryBySlugWitData.sql | 26 +++ lib/db/prisma/sql/getProductByIdWitData.sql | 26 +++ lib/utils.ts | 15 ++ messages/ru.json | 10 +- messages/uk.json | 11 +- package-lock.json | 100 ++------- package.json | 6 +- ...stacare_osteostrong_tablets_box_livo_1.png | Bin 1302735 -> 0 bytes .../189238-192172-orig-1500-1500-d76a.jpg | Bin 258219 -> 0 bytes ...aa7b-06ecb5f905b0-w1000-h1000-wm-frame.jpg | Bin 47060 -> 0 bytes public/uploads/637393-1500x1500-ea2f.jpg | Bin 124905 -> 0 bytes .../79282077-e324-4248-9f5d-184242ec4dd4.webp | Bin 44480 -> 0 bytes store/cart-store.ts | 2 + 53 files changed, 1283 insertions(+), 625 deletions(-) create mode 100644 app/[locale]/(root)/(shop)/about-us/page.tsx create mode 100644 app/[locale]/(root)/(shop)/catalog/page.tsx create mode 100644 app/[locale]/(root)/(shop)/category/[[...slug]]/page.tsx create mode 100644 components/pages/category/page.tsx create mode 100644 components/pages/product/carousel.tsx create mode 100644 components/pages/product/embla.css create mode 100644 components/pages/product/thumb.tsx rename components/{pages/product => shared/store}/add-cart-button.tsx (65%) create mode 100644 components/shared/store/card-buy-button.tsx create mode 100644 components/shared/store/feature-card-front.tsx create mode 100644 components/shared/store/feature-card.tsx create mode 100644 components/shared/store/stars.tsx create mode 100644 components/ui/table.tsx delete mode 100644 components/ui/toast.tsx delete mode 100644 components/ui/toaster.tsx create mode 100644 data/products.ts delete mode 100644 hooks/use-toast.ts create mode 100644 lib/config/resources.ts create mode 100644 lib/data/models/category.ts create mode 100644 lib/data/models/sqlSchemas.ts create mode 100644 lib/db/prisma/sql/getCatalogIndexData.sql create mode 100644 lib/db/prisma/sql/getCategoryBySlugWitData.sql create mode 100644 lib/db/prisma/sql/getProductByIdWitData.sql delete mode 100644 public/uploads/1256-vistacare_osteostrong_tablets_box_livo_1.png delete mode 100644 public/uploads/189238-192172-orig-1500-1500-d76a.jpg delete mode 100644 public/uploads/1ca3d021-a55d-4c0a-aa7b-06ecb5f905b0-w1000-h1000-wm-frame.jpg delete mode 100644 public/uploads/637393-1500x1500-ea2f.jpg delete mode 100644 public/uploads/79282077-e324-4248-9f5d-184242ec4dd4.webp diff --git a/.gitignore b/.gitignore index 87b5daa..7e6ed1a 100644 --- a/.gitignore +++ b/.gitignore @@ -126,3 +126,4 @@ dist .env*.local /messages/*.d.json.ts /public/uploads/ +/public/main-fallback.jpg diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml index fdf3b0b..9120d8f 100644 --- a/.idea/sqldialects.xml +++ b/.idea/sqldialects.xml @@ -2,6 +2,7 @@ + diff --git a/actions/admin/product.ts b/actions/admin/product.ts index ac545b8..0633d0c 100644 --- a/actions/admin/product.ts +++ b/actions/admin/product.ts @@ -1,7 +1,6 @@ 'use server' -import {Meta, ProductLocale, ProductToStore} from '@prisma/client' -import sanitizeHtml from 'sanitize-html' +import {Meta, ProductLocale} from '@prisma/client' import {z} from 'zod' import {i18nLocalesCodes} from '@/i18n-config' @@ -9,12 +8,7 @@ import {STORE_ID} from '@/lib/config/constants' import {getProductBySlug} from '@/lib/data/models/product' import {db} from '@/lib/db/prisma/client' import {createProductFormSchema} from '@/lib/schemas/admin/product' -import { - cleanEmptyParams, - dbErrorHandling, - isEmptyObj, - slug as slugger -} from '@/lib/utils' +import {cleanEmptyParams, dbErrorHandling, slug as slugger} from '@/lib/utils' export const onProductCreateAction = async ( formData: z.infer @@ -42,7 +36,6 @@ export const onProductCreateAction = async ( for (const i in validatedData.meta) { const normalizedMeta: any = cleanEmptyParams(validatedData.meta[i]) - //if (!isEmptyObj(normalizedMeta)) {} meta.push(normalizedMeta) } @@ -58,7 +51,6 @@ export const onProductCreateAction = async ( if (!result) { const normalized: any = cleanEmptyParams({slug, ...locale}) - //locales.push({...normalized, meta: {create: meta[i]}}) locales.push(normalized) } else { return {error: `Продукт з такою назвою ${title} вже існує`} diff --git a/actions/model/category.ts b/actions/model/category.ts index 3d79e22..4925ed4 100644 --- a/actions/model/category.ts +++ b/actions/model/category.ts @@ -15,3 +15,9 @@ export const getCategoryBySlug = async (data: { } }) } + +export const getCategoryLocalesById = async (id: number) => { + return db.categoryLocale.findMany({ + where: {categoryId: id} + }) +} diff --git a/app/[locale]/(root)/(shop)/about-us/page.tsx b/app/[locale]/(root)/(shop)/about-us/page.tsx new file mode 100644 index 0000000..b37bd99 --- /dev/null +++ b/app/[locale]/(root)/(shop)/about-us/page.tsx @@ -0,0 +1,100 @@ +import React from 'react' + +export default function AboutUsPage() { + return ( +
+
+

+ Bewell: здоровий спосіб життя для всіх +

+
+ Інтернет-магазин біологічних добавок Bewell — це + зручна і надійна крамниця для всіх, хто піклується про здоров’я. У нас + ви знайдете якісні дієтичні добавки від європейських виробників і + зможете подбати про себе і про рідних без зайвих клопотів. +
+ +

+ Асортимент магазину Bewell +

+

+ У нас є все, що потрібно для профілактики різноманітних захворювань та + підтримки здоров’я. Це комплексні препарати, у складі яких + переважають: +

+
    +
  • екстракти рослин;
  • +
  • мікро- та макроелементи;
  • +
  • вітаміни.
  • +
+

+ Усі ці компоненти необхідно споживати щоденно, щоб отримати добову + норму вітамінів, мікро- та макроелементів. Дієтичні добавки — просте + доповнення до харчування, яке забезпечує організм необхідними + поживними речовинами. +

+

+ Щоб визначитися, яка з добавок підійде саме вам, пропонуємо зануритися + в наш каталог і вибрати відповідний розділ: +

+
    +
  • + Комплексні препарати. Універсальні добавки для поліпшення здоров’я. + Це вітаміни та мікроелементи для волосся, шкіри, підтримки + імунітету, серця, нервової системи, травлення тощо. +
  • +
  • + Добавки для підтримки жіночого здоров’я ( + вітаміни для жінок). Це зокрема препарати для + відновлення менструального циклу, для полегшення симптомів + менопаузи. +
  • +
  • + Добавки для підтримки здоров’я чоловіків ( + вітаміни для чоловіків). Препарати для покращення + статевої функції, здоров’я передміхурової залози. +
  • +
  • + Препарати для зміцнення імунітету. Такі добавки корисні не лише для + відновлення після захворювання та лікування, а також для + профілактики захворювань. +
  • +
  • + Добавки для відновлення енергії. Ці засоби допомагають боротися з + втомою, покращують обмін речовин, допомагають почуватися енергійніше + та краще спати.{' '} +
  • +
+

+ Як замовити якісні дієтичні добавки? +

+

+ В Bewell можна замовити продукцію відомих + європейських виробників. Дієтичні добавки не є лікарськими засобами, + але це хороша профілактика захворювань та підтримки здоров’я. + Препарати, представлені в нашому магазині, можна побачити в + асортименті аптек, адже це перевірена продукція, яка успішно + використовується на лише в Україні, а й в Європі. Щоб зробити + замовлення, виберіть потрібний препарат, додайте до кошика та зазначте + умови відправки та оплати. +

+

+ Bewell: наша філософія та принцип роботи +

+

+ Головний пріоритет Bewell — підтримка здорового + способу життя. Ми віримо, що ключ до гарного самопочуття та довголіття + можна знайти в природі, збалансованому харчуванні та усвідомлений + підтримці організму. Саме тому ми прагнемо допомогти кожному клієнту + знайти найкращі добавки для підтримки організму та профілактики + захворювань. +

+

+ Ми дбаємо про чесність і прозорість — пропонуємо лише сертифіковані, + перевірені добавки, які сприяють зміцненню імунітету, відновленню + енергії та покращенню сну, а також — внутрішній гармонії. +

+
+
+ ) +} diff --git a/app/[locale]/(root)/(shop)/cart/page.tsx b/app/[locale]/(root)/(shop)/cart/page.tsx index 1366c53..7dbdb2b 100644 --- a/app/[locale]/(root)/(shop)/cart/page.tsx +++ b/app/[locale]/(root)/(shop)/cart/page.tsx @@ -16,13 +16,15 @@ export default function Cart() { return (
-
-

{t('basket')}

-
-
Назва
-
Кількість
-
Вартість
-
+
+

+ {t('basket')} +

+
+
{t('title')}
+
{t('quantity')}
+
{t('amount')}
+
diff --git a/app/[locale]/(root)/(shop)/catalog/page.tsx b/app/[locale]/(root)/(shop)/catalog/page.tsx new file mode 100644 index 0000000..0531686 --- /dev/null +++ b/app/[locale]/(root)/(shop)/catalog/page.tsx @@ -0,0 +1,22 @@ +import {getCatalogIndexData} from '@prisma/client/sql' +import {getLocale} from 'next-intl/server' +import {notFound} from 'next/navigation' + +import CategoryPageIndex from '@/components/pages/category/page' +import {CategoryPageSqlSchema} from '@/lib/data/models/sqlSchemas' +import {db} from '@/lib/db/prisma/client' + +export default async function CatalogPage({ + params +}: { + params: Promise<{slug?: string}> +}) { + const loc = await getLocale() + const catalog: CategoryPageSqlSchema[] = await db.$queryRawTyped( + getCatalogIndexData(loc) + ) + + if (catalog.length < 1) notFound() + + return +} diff --git a/app/[locale]/(root)/(shop)/category/[[...slug]]/page.tsx b/app/[locale]/(root)/(shop)/category/[[...slug]]/page.tsx new file mode 100644 index 0000000..5a211eb --- /dev/null +++ b/app/[locale]/(root)/(shop)/category/[[...slug]]/page.tsx @@ -0,0 +1,22 @@ +import {getCategoryBySlugWitData} from '@prisma/client/sql' +import {notFound} from 'next/navigation' + +import CategoryPageIndex from '@/components/pages/category/page' +import {CategoryPageSqlSchema} from '@/lib/data/models/sqlSchemas' +import {db} from '@/lib/db/prisma/client' + +export default async function Categories({ + params +}: { + params: Promise<{slug?: string}> +}) { + const {slug} = await params + const [uri] = slug || [] + const category: CategoryPageSqlSchema[] = await db.$queryRawTyped( + getCategoryBySlugWitData(uri) + ) + + if (category.length < 2) notFound() + + return +} diff --git a/app/[locale]/(root)/page.tsx b/app/[locale]/(root)/page.tsx index d295958..dccc29a 100644 --- a/app/[locale]/(root)/page.tsx +++ b/app/[locale]/(root)/page.tsx @@ -1,11 +1,16 @@ +import {getCatalogIndexData} from '@prisma/client/sql' +import {getLocale} from 'next-intl/server' import Image from 'next/image' 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 {carousels} from '@/lib/data' +import {CategoryPageSqlSchema} from '@/lib/data/models/sqlSchemas' import {db} from '@/lib/db/prisma/client' import {dump} from '@/lib/utils' +import mainFallback from '@/public/main-fallback.jpg' import image from '@/public/uploads/products/IMG_6572.jpg' // const storeModel = async (id: any) => { @@ -26,6 +31,11 @@ import image from '@/public/uploads/products/IMG_6572.jpg' // } export default async function HomePage() { + const loc = await getLocale() + const catalog: CategoryPageSqlSchema[] = await db.$queryRawTyped( + getCatalogIndexData(loc) + ) + return ( <>
@@ -34,18 +44,27 @@ export default async function HomePage() {
- {/*
{dump(await storeModel(1))}
*/}
- {/*
{JSON.stringify(session)}
*/} -
-
- +
+ +
+ {''}
diff --git a/app/globals.css b/app/globals.css index fc948d5..ac5ef2c 100644 --- a/app/globals.css +++ b/app/globals.css @@ -1,3 +1,5 @@ +@import "@/components/pages/product/embla.css"; + @tailwind base; @tailwind components; @tailwind utilities; @@ -120,6 +122,13 @@ body { @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 } + .bw-product-col-left{ + @apply flex-1 sm:w-5/12 sm:flex-auto sm:pl-4 md:w-7/12 md:pl-7 xl:w-auto xl:pl-9 + } + .bw-product-col-right{ + @apply flex-1 sm:w-7/12 md:w-5/12 lg:flex-col xl:w-[360px] xl:flex-none + } + .bw-header-col-left { @apply w-[9/12] flex-auto } @@ -221,8 +230,10 @@ body { font-size: 1rem !important; line-height: 1.45 !important; color: rgb(40, 26, 76) !important; - + background: transparent !important; + text-align: left !important; } + .bw-product__text { h2 * { font-weight: 700 !important; @@ -234,5 +245,23 @@ body { input { @apply text-xl leading-none border-0 text-brand-violet font-bold; } - +} + +.bw-cart-btn.lucide { + @apply text-brand-violet; + width: 32px; + height: 32px; + &:hover { + @apply drop-shadow-lg shadow-brand-violet-400; + } + } + +.bw-card-footer { + background: #f2f5fa !important; +} + +.bw-cart-wrapper { + .col{ + flex: 1; + } } diff --git a/components/(protected)/admin/product/create-edit-form.tsx b/components/(protected)/admin/product/create-edit-form.tsx index c9b58ba..e319b9c 100644 --- a/components/(protected)/admin/product/create-edit-form.tsx +++ b/components/(protected)/admin/product/create-edit-form.tsx @@ -4,15 +4,14 @@ import {zodResolver} from '@hookform/resolvers/zod' import dynamic from 'next/dynamic' import React, {useEffect, useMemo, useRef, useState} from 'react' import {useFieldArray, useForm} from 'react-hook-form' +import toast from 'react-hot-toast' import {z} from 'zod' import {onProductCreateAction} from '@/actions/admin/product' -import {useToast} from '@/hooks/use-toast' 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, @@ -64,7 +63,6 @@ export default function ProductCreateEditForm({data}: {data?: any}) { data?.locales[1].instruction || '' ) const editor = useRef(null) //declared a null value - const {toast} = useToast() const config = useMemo(() => BaseEditorConfig, []) @@ -129,20 +127,12 @@ export default function ProductCreateEditForm({data}: {data?: any}) { setError(res?.error) setSuccess('') setLoading(false) - toast({ - variant: 'destructive', - title: res?.error, - description: res?.message - }) + toast.error(res?.error) } else { setSuccess(res?.success as string) setError('') setLoading(false) - toast({ - variant: 'success', - title: res?.success, - description: res?.message - }) + toast.success(res?.success) } }) } diff --git a/components/pages/cart/items.tsx b/components/pages/cart/items.tsx index 615a0d3..fa9fb43 100644 --- a/components/pages/cart/items.tsx +++ b/components/pages/cart/items.tsx @@ -1,9 +1,10 @@ // import styles from '@/components/pages/cart/cart.module.scss' -import Link from 'next/link' +import {Minus, Plus} from 'lucide-react' +import Image from 'next/image' -import useCartStore from '@/store/cart-store' +import {Link} from '@/i18n/routing' +import useCartStore, {CartItem} from '@/store/cart-store' import {Button} from '@/ui/button' -import {Input} from '@/ui/input' export default function CartItems() { const {cartItems} = useCartStore() @@ -27,7 +28,7 @@ export default function CartItems() {

Cart is Empty

Shop @@ -38,33 +39,45 @@ export default function CartItems() { return ( <> - {cartItems?.map((item, i) => ( -
-
{item.title}
-
- -
- {item.quantity} -
- + {cartItems?.map((item: CartItem, i: number) => ( +
+
+ {item.title} +
- -
+
+
+ +
+ {item.quantity} +
+ +
+
+
{(item.price * item.quantity).toFixed(2)} грн
diff --git a/components/pages/category/page.tsx b/components/pages/category/page.tsx new file mode 100644 index 0000000..b6e31ec --- /dev/null +++ b/components/pages/category/page.tsx @@ -0,0 +1,29 @@ +import AppCatalog from '@/components/shared/sidebar/app-catalog' +import FeatureCard from '@/components/shared/store/feature-card' +import {CategoryPageSqlSchema} from '@/lib/data/models/sqlSchemas' +import {thisLocales} from '@/lib/utils' + +export default async function CategoryPageIndex({ + data +}: { + data: CategoryPageSqlSchema[] +}) { + const locales = await thisLocales(data) + + return ( +
+
+
+ +
+
+
+ {locales.map((card: CategoryPageSqlSchema, i: number) => ( + + ))} +
+
+
+
+ ) +} diff --git a/components/pages/product/carousel.tsx b/components/pages/product/carousel.tsx new file mode 100644 index 0000000..513df40 --- /dev/null +++ b/components/pages/product/carousel.tsx @@ -0,0 +1,116 @@ +'use client' + +import {ProductResource} from '@prisma/client' +import {EmblaOptionsType} from 'embla-carousel' +import useEmblaCarousel from 'embla-carousel-react' +import Image from 'next/image' +import React, {useCallback, useEffect, useState} from 'react' + +import {Thumb} from './thumb' +import {Dialog, DialogContent, DialogTitle} from '@/ui/dialog' + +type PropType = { + slides: number[] + options?: EmblaOptionsType +} + +export default function ProductCarousel({ + images, + title +}: { + images: ProductResource[] | null + title: string +}) { + //const {slides, options} = props + const [selectedIndex, setSelectedIndex] = useState(0) + const [emblaMainRef, emblaMainApi] = useEmblaCarousel({}) //options + const [emblaThumbsRef, emblaThumbsApi] = useEmblaCarousel({ + containScroll: 'keepSnaps', + dragFree: true + }) + + const onThumbClick = useCallback( + (index: number) => { + if (!emblaMainApi || !emblaThumbsApi) return + emblaMainApi.scrollTo(index) + }, + [emblaMainApi, emblaThumbsApi] + ) + + const onSelect = useCallback(() => { + if (!emblaMainApi || !emblaThumbsApi) return + setSelectedIndex(emblaMainApi.selectedScrollSnap()) + emblaThumbsApi.scrollTo(emblaMainApi.selectedScrollSnap()) + }, [emblaMainApi, emblaThumbsApi, setSelectedIndex]) + + useEffect(() => { + if (!emblaMainApi) return + onSelect() + + emblaMainApi.on('select', onSelect).on('reInit', onSelect) + }, [emblaMainApi, onSelect]) + //let featuredImage: ProductResource | null | undefined + /*if ((resources || []).length > 0) { + featuredImage = resources?.find(resource => resource.isFeature) + }*/ + + return ( +
+
+
+ {images?.map((image: ProductResource, index: number) => ( +
+
+ +
+
+ ))} +
+
+ +
+
+
+ {images?.map((image: ProductResource, index: number) => ( + + {title} + onThumbClick(index)} + selected={index === selectedIndex} + index={index} + /> + + + + + ))} +
+
+
+
+ ) +} diff --git a/components/pages/product/embla.css b/components/pages/product/embla.css new file mode 100644 index 0000000..10a299b --- /dev/null +++ b/components/pages/product/embla.css @@ -0,0 +1,81 @@ +.embla { + max-width: 48rem; + margin: auto; + --slide-height: 19rem; + --slide-spacing: 1rem; + --slide-size: 100%; +} +.embla__viewport { + overflow: hidden; +} +.embla__container { + display: flex; + touch-action: pan-y pinch-zoom; + margin-left: calc(var(--slide-spacing) * -1); +} +.embla__slide { + transform: translate3d(0, 0, 0); + flex: 0 0 var(--slide-size); + min-width: 0; + padding-left: var(--slide-spacing); +} +.embla__slide__number { + box-shadow: inset 0 0 0 0.2rem var(--detail-medium-contrast); + border-radius: 1.8rem; + font-size: 4rem; + font-weight: 600; + display: flex; + align-items: center; + justify-content: center; + height: var(--slide-height); + user-select: none; +} +.embla-thumbs { + --thumbs-slide-spacing: 0.8rem; + --thumbs-slide-height: 6rem; + margin-top: var(--thumbs-slide-spacing); +} +.embla-thumbs__viewport { + overflow: hidden; +} +.embla-thumbs__container { + display: flex; + flex-direction: row; + margin-left: calc(var(--thumbs-slide-spacing) * -1); +} +.embla-thumbs__slide { + flex: 0 0 22%; + min-width: 0; + padding-left: var(--thumbs-slide-spacing); +} +@media (min-width: 576px) { + .embla-thumbs__slide { + flex: 0 0 15%; + } +} +.embla-thumbs__slide__number { + border-radius: 1.8rem; + -webkit-tap-highlight-color: rgba(var(--text-high-contrast-rgb-value), 0.5); + -webkit-appearance: none; + appearance: none; + background-color: transparent; + touch-action: manipulation; + display: inline-flex; + text-decoration: none; + cursor: pointer; + border: 0; + padding: 0; + margin: 0; + box-shadow: inset 0 0 0 0.2rem var(--detail-medium-contrast); + font-size: 1.8rem; + font-weight: 600; + color: var(--detail-high-contrast); + display: flex; + align-items: center; + justify-content: center; + height: var(--thumbs-slide-height); + width: 100%; +} +.embla-thumbs__slide--selected .embla-thumbs__slide__number { + color: var(--text-body); +} diff --git a/components/pages/product/index.tsx b/components/pages/product/index.tsx index 4e8b1f7..7ef0d53 100644 --- a/components/pages/product/index.tsx +++ b/components/pages/product/index.tsx @@ -1,37 +1,79 @@ -import {getLocale} from 'next-intl/server' +import {ProductResource} from '@prisma/client' +import {getProductByIdWitData} from '@prisma/client/sql' +import {getTranslations} 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 ProductCarousel from '@/components/pages/product/carousel' +import AddCartButton from '@/components/shared/store/add-cart-button' +import { + Breadcrumb, + BreadcrumbItem, + BreadcrumbLink, + BreadcrumbList, + BreadcrumbPage, + BreadcrumbSeparator +} from '@/components/ui/breadcrumb' +import {getProductResources} from '@/lib/data/models/product' +import {CategoryPageSqlSchema} from '@/lib/data/models/sqlSchemas' +import {db} from '@/lib/db/prisma/client' +import {thisLocale, toPrice} from '@/lib/utils' +import {Separator} from '@/ui/separator' 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])) + const t = await getTranslations('Common') + + const data: CategoryPageSqlSchema[] = await db.$queryRawTyped( + getProductByIdWitData(id) + ) + + const locale = await thisLocale(data) + if (!locale) notFound() + + const resources: ProductResource[] | null = await getProductResources( + parseInt(id) + ) + + //const files = await getMetaOfFile(locale.productId) return (
-
-
- -
-
-
+
+ {/*
{dump(resources)}
*/} +
+ + + + {t('home')} + + + + + {locale.categoryTitle} + + + + + {locale.title} + + + +
+

+ {locale.title} +

+ +
+ + Опис @@ -51,6 +93,14 @@ export default async function ProductPageIndex({id}: {id: string}) {
+
+
+ {t('price')}: + + {toPrice(locale.price)} + +
+
) diff --git a/components/pages/product/thumb.tsx b/components/pages/product/thumb.tsx new file mode 100644 index 0000000..82ca458 --- /dev/null +++ b/components/pages/product/thumb.tsx @@ -0,0 +1,46 @@ +import {ProductResource} from '@prisma/client' +import Image from 'next/image' +import React from 'react' + +import {Button} from '@/ui/button' +import {DialogTrigger} from '@/ui/dialog' + +type PropType = { + selected: boolean + index: number + onClick: () => void + image: ProductResource +} + +export const Thumb: React.FC = props => { + const {selected, index, onClick, image} = props + + return ( +
+ + + +
+ ) +} diff --git a/components/shared/header/index.tsx b/components/shared/header/index.tsx index bc61f4f..37f536f 100644 --- a/components/shared/header/index.tsx +++ b/components/shared/header/index.tsx @@ -15,13 +15,6 @@ type MenuProps = { } export default async function Header() { - /*{ - searchParams - }: { - searchParams: Promise<{query?: string}> - }*/ - //const query = (await searchParams).query - return (
diff --git a/components/shared/home/feature-cards.tsx b/components/shared/home/feature-cards.tsx index abf35ab..5b2d378 100644 --- a/components/shared/home/feature-cards.tsx +++ b/components/shared/home/feature-cards.tsx @@ -1,34 +1,22 @@ -import Image from 'next/image' -import * as React from 'react' - -import {cards} from '@/lib/data' -import {Card, CardContent} from '@/ui/card' +import FeatureCardFront from '@/components/shared/store/feature-card-front' +import {CategoryPageSqlSchema} from '@/lib/data/models/sqlSchemas' import {Carousel, CarouselContent, CarouselItem} from '@/ui/carousel' -export default function FeatureCards() { +export default function FeatureCards({ + items +}: { + items: CategoryPageSqlSchema[] +}) { return ( - {cards.map((card: any) => ( + {items.map((card: any) => (
- - - - {''} - - - +
))} diff --git a/components/shared/locale-switcher.tsx b/components/shared/locale-switcher.tsx index 98c5dbe..6236fa5 100644 --- a/components/shared/locale-switcher.tsx +++ b/components/shared/locale-switcher.tsx @@ -1,39 +1,37 @@ 'use client' +import cookies from 'js-cookie' import {useLocale} from 'next-intl' -import {redirect} from 'next/navigation' -import {Link, usePathname, useRouter} from '@/i18n/routing' +import {useRouter} from '@/i18n/routing' +import {Link} from '@/i18n/routing' import {Label} from '@/ui/label' import {Switch} from '@/ui/switch' export default function LocaleSwitcher() { const router = useRouter() - const pathname = usePathname() const locale = useLocale() const initialState = locale !== 'uk' + //const [localeState, setLocaleState] = useState(initialState) - const handler = (state: boolean) => { - const newPath = `/${locale}${pathname}` - //window.history.replaceState(null, '', newPath) - const link = document.getElementById('lang-switch') - if (link) { - link.innerText = `${state ? '/ru' : ''}${pathname}` - link.setAttribute('href', `${state ? '/ru' : ''}${pathname}`) - link.click() - } + const handleLocaleChange = (state: boolean) => { + const locale = cookies.get('NEXT_LOCALE') === 'ru' ? 'uk' : 'ru' + cookies.set('NEXT_LOCALE', locale, { + expires: 7, + path: '/', + sameSite: 'Lax' + }) + router.replace(window.location.pathname.replace(/^\/ru/, ''), {locale}) } - // router.replace('/cabinet', {locale: checked ? 'ru' : 'uk'} + return (
- - LA -
diff --git a/components/shared/navbar/navbar-menu.tsx b/components/shared/navbar/navbar-menu.tsx index dc8d786..eafe551 100644 --- a/components/shared/navbar/navbar-menu.tsx +++ b/components/shared/navbar/navbar-menu.tsx @@ -1,9 +1,8 @@ 'use client' -import {Menu as MenuIcon, X} from 'lucide-react' -import Link from 'next/link' import {useState} from 'react' +import {Link} from '@/i18n/routing' import {data} from '@/lib/data' import {Button} from '@/ui/button' @@ -21,7 +20,7 @@ export default function NavbarMenu() {
{data.headerMenus.map(item => ( - + {item.name} ))} diff --git a/components/shared/sidebar/app-catalog-render.tsx b/components/shared/sidebar/app-catalog-render.tsx index f0ef31c..b3be2df 100644 --- a/components/shared/sidebar/app-catalog-render.tsx +++ b/components/shared/sidebar/app-catalog-render.tsx @@ -12,18 +12,20 @@ export default function AppCatalogRender(data: {items: Category[]}) { return (
- + + +