update templates
parent
42a36cab93
commit
b94292b479
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
Binary file not shown.
After Width: | Height: | Size: 8.6 KiB |
|
@ -0,0 +1,21 @@
|
||||||
|
<svg width="45" height="40" viewBox="0 0 45 40" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect width="45" height="40" rx="8" fill-opacity="0.7"/>
|
||||||
|
<g clip-path="url(#clip0_79_274)">
|
||||||
|
<g clip-path="url(#clip1_79_274)">
|
||||||
|
<g clip-path="url(#clip2_79_274)">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.1733 19.0227C19.7338 17.0385 22.7748 15.7304 24.2964 15.0984C28.6408 13.2939 29.5436 12.9804 30.132 12.9701C30.2614 12.9678 30.5508 12.9998 30.7382 13.1517C30.8965 13.28 30.94 13.4532 30.9609 13.5748C30.9816 13.6964 31.0076 13.9734 30.987 14.1898C30.7515 16.66 29.7329 22.6547 29.2146 25.4213C28.9953 26.5921 28.5635 26.9846 28.1455 27.023C27.2371 27.1065 26.5472 26.4235 25.6673 25.8475C24.2904 24.9462 23.5126 24.3851 22.1761 23.5056C20.6316 22.4892 21.6329 21.9305 22.5131 21.0176C22.7434 20.7786 26.7461 17.1429 26.8236 16.813C26.8333 16.7718 26.8423 16.618 26.7508 16.5369C26.6594 16.4557 26.5243 16.4834 26.4269 16.5055C26.2888 16.5368 24.0893 17.9886 19.8283 20.861C19.204 21.2891 18.6385 21.4977 18.1318 21.4868C17.5732 21.4747 16.4988 21.1713 15.7 20.9121C14.7203 20.5941 13.9417 20.4259 14.0095 19.8858C14.0448 19.6045 14.4327 19.3168 15.1733 19.0227Z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_79_274">
|
||||||
|
<rect width="17" height="14.06" fill="white" transform="translate(14 12.97)"/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="clip1_79_274">
|
||||||
|
<rect width="17" height="14.06" fill="white" transform="translate(14 12.97)"/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="clip2_79_274">
|
||||||
|
<rect width="16.9892" height="14.06" fill="white" transform="translate(14.0054 12.97)"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
|
@ -0,0 +1,21 @@
|
||||||
|
<svg width="45" height="40" viewBox="0 0 45 40" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect width="45" height="40" rx="8" fill-opacity="0.7"/>
|
||||||
|
<g clip-path="url(#clip0_79_269)">
|
||||||
|
<g clip-path="url(#clip1_79_269)">
|
||||||
|
<g clip-path="url(#clip2_79_269)">
|
||||||
|
<path d="M24.0203 28.4289C24.0203 28.4289 24.5798 28.3692 24.8663 28.0733C25.1286 27.8022 25.1195 27.2907 25.1195 27.2907C25.1195 27.2907 25.0847 24.9019 26.2385 24.5492C27.3757 24.2024 28.8358 26.8593 30.3853 27.8809C31.5559 28.6534 32.4444 28.4843 32.4444 28.4843L36.5851 28.4289C36.5851 28.4289 38.7503 28.3007 37.7238 26.664C37.6389 26.5299 37.1249 25.4528 34.6459 23.2404C32.0486 20.9246 32.3974 21.2991 35.5238 17.2926C37.4281 14.8529 38.1893 13.3633 37.9512 12.7264C37.7253 12.1172 36.3243 12.279 36.3243 12.279L31.6635 12.3067C31.6635 12.3067 31.3178 12.2615 31.0616 12.4087C30.8114 12.553 30.6492 12.8897 30.6492 12.8897C30.6492 12.8897 29.9123 14.7771 28.9283 16.3832C26.8526 19.7703 26.0232 19.9495 25.6836 19.7397C24.8936 19.2485 25.0907 17.7692 25.0907 16.7184C25.0907 13.4348 25.6093 12.0662 24.0825 11.7121C23.576 11.594 23.203 11.5168 21.9067 11.5036C20.2434 11.4876 18.8364 11.5095 18.0388 11.884C17.5082 12.1333 17.0988 12.69 17.349 12.7221C17.6567 12.7614 18.3542 12.9028 18.7242 13.3867C19.2018 14.0119 19.1851 15.414 19.1851 15.414C19.1851 15.414 19.4595 19.2791 18.5437 19.7586C17.916 20.088 17.0548 19.4161 15.2035 16.3424C14.2559 14.7683 13.5402 13.0281 13.5402 13.0281C13.5402 13.0281 13.4023 12.7031 13.1551 12.5282C12.8564 12.3169 12.4395 12.2513 12.4395 12.2513L8.01061 12.279C8.01061 12.279 7.345 12.2965 7.10089 12.5749C6.88407 12.8212 7.08421 13.3327 7.08421 13.3327C7.08421 13.3327 10.5518 21.1315 14.4788 25.0622C18.0798 28.6651 22.1675 28.4289 22.1675 28.4289H24.0203Z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_79_269">
|
||||||
|
<rect width="31" height="17" transform="translate(7 11.5)"/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="clip1_79_269">
|
||||||
|
<rect width="31" height="17" transform="translate(7 11.5)"/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="clip2_79_269">
|
||||||
|
<rect width="31" height="17" transform="translate(7 11.5)"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.0 KiB |
|
@ -23,7 +23,7 @@ const OrderInfo = (props: OrderInfoProps) => {
|
||||||
<h2 className={"text-subtitle-2 font-bold mb-2"}>Информация о заказе</h2>
|
<h2 className={"text-subtitle-2 font-bold mb-2"}>Информация о заказе</h2>
|
||||||
<div className="flex justify-between mb-2">
|
<div className="flex justify-between mb-2">
|
||||||
<span>Товаров на:</span>
|
<span>Товаров на:</span>
|
||||||
<span>{String(totalProductPriceQs.data?.total_product_price).replace(/\B(?=(\d{3})+(?!\d))/g, " ")} ₽</span>
|
<span>{String(totalProductPriceQs.data ? totalProductPriceQs.data.data!.total : 0).replace(/\B(?=(\d{3})+(?!\d))/g, " ")} ₽</span>
|
||||||
</div>
|
</div>
|
||||||
<form className="flex justify-between h-[50px] mb-2">
|
<form className="flex justify-between h-[50px] mb-2">
|
||||||
<Input type="text" label="Введите промокод" variant={"bordered"}
|
<Input type="text" label="Введите промокод" variant={"bordered"}
|
||||||
|
@ -43,12 +43,12 @@ const OrderInfo = (props: OrderInfoProps) => {
|
||||||
|
|
||||||
<div className="flex justify-between mb-2 items-center">
|
<div className="flex justify-between mb-2 items-center">
|
||||||
<span>Итого: </span>
|
<span>Итого: </span>
|
||||||
<span className={"text-title-3 font-bold"}>{String(totalProductPriceQs.data?.total_product_price).replace(/\B(?=(\d{3})+(?!\d))/g, " ")} ₽</span>
|
<span className={"text-title-3 font-bold"}>{String(totalProductPriceQs.data ? totalProductPriceQs.data.data!.total : 0).replace(/\B(?=(\d{3})+(?!\d))/g, " ")} ₽</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-between mb-8 text-[#808080] text-subtitle-5">
|
<div className="flex justify-between mb-8 text-[#808080] text-subtitle-5">
|
||||||
<span>Сумма НДС: </span>
|
<span>Сумма НДС: </span>
|
||||||
<span>{totalProductPriceQs.data?.total_product_price * 0.2} ₽</span>
|
<span>{totalProductPriceQs.data ? totalProductPriceQs.data.data!.total * 20 / 120 : 0} ₽</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Checkbox isSelected={!props.isDisabled} onChange={() => {
|
<Checkbox isSelected={!props.isDisabled} onChange={() => {
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
import {Divider} from "@nextui-org/react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import Logo from "../../../public/header_logo.svg";
|
||||||
|
import TgIcon from "../../../public/tg_icon.svg"
|
||||||
|
import VkIcon from "../../../public/vk_icon.svg"
|
||||||
|
import {Img} from "react-image";
|
||||||
|
|
||||||
|
const Footer = () => {
|
||||||
|
return (
|
||||||
|
<footer className={"bg-black-2 pt-4"}>
|
||||||
|
<div className="wrapper text-white grid grid-cols-12">
|
||||||
|
<div className="logo col-span-12 xl:col-span-3">
|
||||||
|
<Link href={"/"} className={"transition-none pt-8 pb-2 block"}>
|
||||||
|
<Logo/>
|
||||||
|
</Link>
|
||||||
|
<span className={"block mb-2 text-gray-3"}>Московская область, г. Домодедово,
|
||||||
|
Каширское ш, 4, к.1, оф.330</span>
|
||||||
|
<a className={"block"} href={"tel:+74951919720"}>+7(495)191-97-20</a>
|
||||||
|
|
||||||
|
<div className="socials my-4 flex [&_a]:mr-2">
|
||||||
|
<a href="#" className={"group"}><VkIcon className={"fill-gray-3 group-hover:fill-primary transition-colors [&_path]:fill-white"}/></a>
|
||||||
|
<a href="#" className={"group"}><TgIcon className={"fill-gray-3 group-hover:fill-primary transition-colors [&_path]:fill-white"} /></a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={"markets my-2"}>
|
||||||
|
<a href="#" className={"group"}><Img src={'/ozon_icon.png'} className={"w-[40px] h-[40px] grayscale group-hover:grayscale-0 transition-all"} /></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="col-span-12 xl:col-span-3 xl:col-start-5 mt-5">
|
||||||
|
<h2 className={"text-2xl hover:text-primary transition-colors"}>Бренд</h2>
|
||||||
|
<ul className={"text-gray-3 text-sm font-semibold my-2"}>
|
||||||
|
<li className={"hover:text-primary transition-colors"}>Персонализация</li>
|
||||||
|
<li className={"hover:text-primary transition-colors"}>Технологии</li>
|
||||||
|
<li className={"hover:text-primary transition-colors"}>Производство</li>
|
||||||
|
<li className={"hover:text-primary transition-colors"}>Новости</li>
|
||||||
|
<li className={"hover:text-primary transition-colors"}>Карьера</li>
|
||||||
|
<li className={"hover:text-primary transition-colors"}>Миссия</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div className="col-span-12 xl:col-span-3 mt-5">
|
||||||
|
<h2 className={"text-2xl hover:text-primary transition-colors"}>Продукция</h2>
|
||||||
|
<ul className={"text-gray-3 text-sm font-semibold my-2"}>
|
||||||
|
<li className={"hover:text-primary transition-colors"}>Relynolli ® Standart M</li>
|
||||||
|
<li className={"hover:text-primary transition-colors"}>Relynolli ® Premium M</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div className="col-span-12 xl:col-span-2 mt-5">
|
||||||
|
<h2 className={"text-2xl hover:text-primary transition-colors"}>Информация</h2>
|
||||||
|
<ul className={"text-gray-3 text-sm font-semibold my-2"}>
|
||||||
|
<li className={"hover:text-primary transition-colors"}>Оплата</li>
|
||||||
|
<li className={"hover:text-primary transition-colors"}>Контакты</li>
|
||||||
|
<li className={"hover:text-primary transition-colors"}>Поддержка и рекламации</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<Divider className={"col-span-12 my-8 h-[1px] w-full bg-gray-3"}/>
|
||||||
|
<div className="col-span-12">
|
||||||
|
<h2>© ООО "ТД Технохим Групп" 2024</h2>
|
||||||
|
<p className={"text-gray-3 text-sm font-semibold my-2 mt-4"}>Политика конфиденциальности</p>
|
||||||
|
<p className={"text-gray-3 text-sm font-semibold my-2"}>Обработка персональных данных</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</footer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Footer
|
|
@ -136,7 +136,7 @@ const Header = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{cart.data &&
|
{cart.data &&
|
||||||
<Badge isInvisible={cart.data?.length === 0} color={"primary"} content={cart.data?.length}
|
<Badge isInvisible={!Boolean(cart.data.data)} color={"primary"} content={cart.data.data && cart.data.data.length}
|
||||||
className={"text-black font-semibold"}>
|
className={"text-black font-semibold"}>
|
||||||
<Link href={'/cart'}
|
<Link href={'/cart'}
|
||||||
className={"rounded-[8px] group transition-colors cursor-pointer bg-transparent hover:bg-primary flex items-center px-3 h-[50px]"}>
|
className={"rounded-[8px] group transition-colors cursor-pointer bg-transparent hover:bg-primary flex items-center px-3 h-[50px]"}>
|
||||||
|
@ -198,7 +198,7 @@ const Header = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{cart.data &&
|
{cart.data &&
|
||||||
<Badge isInvisible={cart.data?.length === 0} color={"primary"} content={cart.data?.length}
|
<Badge isInvisible={!Boolean(cart.data.data)} color={"primary"} content={cart.data.data && cart.data.data.length}
|
||||||
className={"text-black font-semibold"}>
|
className={"text-black font-semibold"}>
|
||||||
<Link href={'/cart'}
|
<Link href={'/cart'}
|
||||||
className={"rounded-[8px] group transition-colors cursor-pointer bg-transparent hover:bg-primary flex items-center px-3 h-[50px]"}>
|
className={"rounded-[8px] group transition-colors cursor-pointer bg-transparent hover:bg-primary flex items-center px-3 h-[50px]"}>
|
||||||
|
@ -206,15 +206,9 @@ const Header = () => {
|
||||||
</Link>
|
</Link>
|
||||||
</Badge>
|
</Badge>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</header>
|
</header>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import Header from "@/components/reusable/header";
|
import Header from "@/components/reusable/header";
|
||||||
import {Mulish} from "next/font/google";
|
import {Mulish} from "next/font/google";
|
||||||
|
import Footer from "@/components/reusable/footer";
|
||||||
|
|
||||||
const mulish = Mulish({
|
const mulish = Mulish({
|
||||||
subsets: ["cyrillic", "latin"],
|
subsets: ["cyrillic", "latin"],
|
||||||
|
@ -12,6 +13,7 @@ const Layout = ({ children }: { children: React.ReactNode }) => {
|
||||||
<main className={`${mulish.variable} font-mulish`}>
|
<main className={`${mulish.variable} font-mulish`}>
|
||||||
{children}
|
{children}
|
||||||
</main>
|
</main>
|
||||||
|
<Footer />
|
||||||
</>)
|
</>)
|
||||||
}
|
}
|
||||||
export default Layout
|
export default Layout
|
|
@ -16,7 +16,7 @@ type WrapperProps = {
|
||||||
|
|
||||||
const Wrapper = (props: WrapperProps) => {
|
const Wrapper = (props: WrapperProps) => {
|
||||||
return <>
|
return <>
|
||||||
<section className={"bg-white text-black pt-7"}>
|
<section className={"bg-white text-black py-7"}>
|
||||||
<div className="wrapper">
|
<div className="wrapper">
|
||||||
{
|
{
|
||||||
props.breadcrumbs &&
|
props.breadcrumbs &&
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
import {BreadcrumbItem, Breadcrumbs, Button, Checkbox} from "@nextui-org/react";
|
import {BreadcrumbItem, Breadcrumbs, Button, Checkbox} from "@nextui-org/react";
|
||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import Image from "next/image";
|
|
||||||
|
|
||||||
import MinusIcon from "@/../public/minus_icon.svg"
|
import MinusIcon from "@/../public/minus_icon.svg"
|
||||||
import PlusIcon from "@/../public/plus_icon.svg"
|
import PlusIcon from "@/../public/plus_icon.svg"
|
||||||
import CrossIcon from "@/../public/cross.svg"
|
import CrossIcon from "@/../public/cross.svg"
|
||||||
|
@ -15,9 +13,11 @@ import Wrapper from "@/components/reusable/wrapper";
|
||||||
import OrderInfo from "@/components/pages/cart/orderInfo";
|
import OrderInfo from "@/components/pages/cart/orderInfo";
|
||||||
import {useMutation, useQuery, useQueryClient} from "@tanstack/react-query";
|
import {useMutation, useQuery, useQueryClient} from "@tanstack/react-query";
|
||||||
import LocalAPI from "@/service/localAPI";
|
import LocalAPI from "@/service/localAPI";
|
||||||
|
import {CartItem} from "@/service/types/local";
|
||||||
|
import {Img} from "react-image";
|
||||||
|
|
||||||
|
|
||||||
const CartCard = (product: Partial<ResponseData> & { quantity: number, available_quantity: number }) => {
|
const CartCard = (item: CartItem) => {
|
||||||
|
|
||||||
const qc = useQueryClient()
|
const qc = useQueryClient()
|
||||||
|
|
||||||
|
@ -36,39 +36,39 @@ const CartCard = (product: Partial<ResponseData> & { quantity: number, available
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="card relative flex bg-gray-card rounded-[20px] w-full px-7 py-4 group hover:shadow-md transition-shadow cursor-pointer justify-between items-center text-[#151515] [&>*]:mr-7">
|
className="card relative grid grid-cols-10 gap-4 bg-gray-card rounded-[20px] w-full px-7 py-4 group hover:shadow-md transition-shadow cursor-pointer justify-between items-center text-[#151515] [&>*]:mr-7 mb-5">
|
||||||
|
|
||||||
<Image className={"grow"} src={`https://tehnohimgrupp.ru/upload/${product.properties!.main_image![0]}`}
|
<Img className={"col-span-10 xl:col-span-3 max-w-[200px] max-h-[250px]"} src={`https://relynolli.ru/upload/${item.product.properties!.main_image![0]}`} alt={"oil"}/>
|
||||||
width={100} height={126} alt={"oil"}/>
|
<div className="col-span-10 xl:col-span-3 text-block flex flex-col justify-between font-bold text-xl xl:text-base">
|
||||||
<div className="text-block flex flex-col h-1/2 justify-between py-5 w-1/3">
|
<p>{item.product.name}</p>
|
||||||
<p>{product.name}</p>
|
<p className={"text-[#8F8F8F] font-normal pt-2"}>Артикул: {item.product.properties.vendor_code}</p>
|
||||||
<p className={"text-[#8F8F8F]"}>Артикул: {product.properties!.vendor_code}</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
<span className={"col-span-3 xl:col-span-1"}>{`${+(item.product.properties!.weight!) / 1000} кг`}</span>
|
||||||
|
|
||||||
<span className={"grow"}>{`${+(product.properties!.weight!) / 1000} кг`}</span>
|
<span className={"col-span-3 xl:col-span-1"}>{item.product.properties!.volume}</span>
|
||||||
|
|
||||||
<span className={"grow"}>{product.properties!.volume}</span>
|
<div className="flex col-start-8 col-span-3 xl:col-span-2 justify-between relative z-20">
|
||||||
|
<button onClick={() => changeQuantity.mutate({productId: item.product.id!, quantity: item.quantity - 1})
|
||||||
<div className="flex grow justify-between relative z-20">
|
|
||||||
<button onClick={() => changeQuantity.mutate({productId: product.id!, quantity: product.quantity - 1})
|
|
||||||
}
|
}
|
||||||
className={"bg-white flex drop-shadow w-[30px] h-[30px] rounded items-center justify-center hover:bg-primary transition-colors"}>
|
className={"bg-white flex drop-shadow w-[30px] h-[30px] rounded items-center justify-center hover:bg-primary transition-colors"}>
|
||||||
<MinusIcon/></button>
|
<MinusIcon/></button>
|
||||||
<span>{product.quantity}</span>
|
<span>{item.quantity}</span>
|
||||||
<button onClick={() => changeQuantity.mutate({productId: product.id!, quantity: product.quantity + 1})}
|
<button onClick={() => changeQuantity.mutate({productId: item.product.id!, quantity: item.quantity + 1})}
|
||||||
className={"bg-white flex drop-shadow w-[30px] h-[30px] rounded items-center justify-center hover:bg-primary transition-colors"}>
|
className={"bg-white flex drop-shadow w-[30px] h-[30px] rounded items-center justify-center hover:bg-primary transition-colors"}>
|
||||||
<PlusIcon/></button>
|
<PlusIcon/></button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col font-bold grow text-center">
|
<div className="flex flex-col font-bold text-right text-3xl col-span-10 border-t-1 border-t-primary pt-4">
|
||||||
{product.price!.BASE} ₽
|
{item.product.price!.BASE} ₽
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button className={"relative z-20"} onClick={() => toggleCart(product as ResponseData)}>
|
<button className={"z-20 absolute top-8 right-0"} onClick={() => {
|
||||||
|
// TODO DO
|
||||||
|
}}>
|
||||||
<CrossIcon className={"hover:fill-red-500 fill-[#8F8F8F] transition-colors"}/>
|
<CrossIcon className={"hover:fill-red-500 fill-[#8F8F8F] transition-colors"}/>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<Link href={"/catalog/" + product.code} className={"absolute top-0 left-0 w-full h-full z-10"}></Link>
|
<Link href={"/catalog/" + item.product.code} className={"absolute top-0 left-0 w-full h-full z-10"}></Link>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -77,7 +77,7 @@ const PlaceHolder = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="card relative flex flex-col bg-gray-card rounded-[20px] w-full px-7 py-4 justify-between items-center text-[#151515] [&>*]:mr-7 text-subtitle-3">
|
className="card relative col-span-10 flex flex-col bg-gray-card rounded-[20px] w-full px-7 py-4 justify-between items-center text-[#151515] [&>*]:mr-7 text-subtitle-3">
|
||||||
|
|
||||||
<div className="flex flex-col w-1/3 text-center py-16">
|
<div className="flex flex-col w-1/3 text-center py-16">
|
||||||
<span className={"pb-7"}>В корзине пока что ничего нет, выберите необходимые товары в каталоге</span>
|
<span className={"pb-7"}>В корзине пока что ничего нет, выберите необходимые товары в каталоге</span>
|
||||||
|
@ -108,17 +108,16 @@ const Cart = () => {
|
||||||
return (
|
return (
|
||||||
<Wrapper title={"Корзина"} breadcrumbs={[{name: "Корзина", link: "/cart"}]}>
|
<Wrapper title={"Корзина"} breadcrumbs={[{name: "Корзина", link: "/cart"}]}>
|
||||||
|
|
||||||
<div className="flex w-full pb-16 justify-between">
|
<div className="grid grid-cols-10 pb-16 justify-between gap-5">
|
||||||
|
|
||||||
{
|
{(cart.data && cart.data.data) ? (
|
||||||
cart.data?.length ? (
|
|
||||||
<>
|
<>
|
||||||
<div className="cards w-8/12 [&>*]:mb-7">
|
<div className="cards order-2 col-span-10 xl:order-none xl:col-span-7 flex-col">
|
||||||
{cart.data.map(item => <CartCard key={item.id} {...item}/>)}
|
{cart.data.data.map(item => <CartCard key={item.id} {...item} />)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div className="flex flex-col w-[30%]">
|
<div className="flex order-1 col-span-10 xl:order-none xl:col-span-3 flex-col">
|
||||||
|
|
||||||
<OrderInfo
|
<OrderInfo
|
||||||
setIsDisabled={setIsDisabled} isDisabled={isDisabled}/>
|
setIsDisabled={setIsDisabled} isDisabled={isDisabled}/>
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
import {BreadcrumbItem, Breadcrumbs, Button, Skeleton, Spinner} from "@nextui-org/react";
|
import {BreadcrumbItem, Breadcrumbs, Button, Skeleton, Spinner} from "@nextui-org/react";
|
||||||
import HomeIcon from "../../../public/home_icon.svg";
|
import HomeIcon from "../../../public/home_icon.svg";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import axios from "axios";
|
|
||||||
import {ResponseData} from "@/pages/api/v1/catalog";
|
|
||||||
import {InferGetStaticPropsType} from "next";
|
import {InferGetStaticPropsType} from "next";
|
||||||
import Cart from "@/../public/cart.svg"
|
import Cart from "@/../public/cart.svg"
|
||||||
import Favourites from "@/../public/favourites_icon.svg"
|
import Favourites from "@/../public/favourites_icon.svg"
|
||||||
|
@ -12,10 +10,11 @@ import useClient from "@/hooks/useClient";
|
||||||
import LocalAPI from "@/service/localAPI";
|
import LocalAPI from "@/service/localAPI";
|
||||||
import {useQuery, useQueryClient, useMutation} from "@tanstack/react-query";
|
import {useQuery, useQueryClient, useMutation} from "@tanstack/react-query";
|
||||||
import {Img} from 'react-image'
|
import {Img} from 'react-image'
|
||||||
|
import {CartItem, Wrapper} from "@/service/types/local";
|
||||||
|
|
||||||
const OilCard = ({product}: InferGetStaticPropsType<typeof getStaticProps>) => {
|
const OilCard = ({product}: InferGetStaticPropsType<typeof getStaticProps>) => {
|
||||||
const {favourites} = useSnapshot(favouritesStore)
|
const {favourites} = useSnapshot(favouritesStore)
|
||||||
const cartItems = useQuery<any[]>({
|
const cartItems = useQuery<Wrapper<CartItem[]>>({
|
||||||
queryKey: ['cart']
|
queryKey: ['cart']
|
||||||
})
|
})
|
||||||
const isClient = useClient()
|
const isClient = useClient()
|
||||||
|
@ -25,7 +24,7 @@ const OilCard = ({product}: InferGetStaticPropsType<typeof getStaticProps>) => {
|
||||||
const toggleCart = useMutation({
|
const toggleCart = useMutation({
|
||||||
mutationFn: async ({productId, quantity}: { productId: number, quantity: number }) => {
|
mutationFn: async ({productId, quantity}: { productId: number, quantity: number }) => {
|
||||||
const service = new LocalAPI()
|
const service = new LocalAPI()
|
||||||
if (cartItems.data!.find(({id}) => id === productId)) {
|
if (cartItems.data && cartItems.data.data && cartItems.data.data.find(item => item.product.id === productId)) {
|
||||||
return await service.deleteCartItem(productId)
|
return await service.deleteCartItem(productId)
|
||||||
}
|
}
|
||||||
return await service.addCartItem(productId, quantity)
|
return await service.addCartItem(productId, quantity)
|
||||||
|
@ -85,7 +84,7 @@ const OilCard = ({product}: InferGetStaticPropsType<typeof getStaticProps>) => {
|
||||||
<Button onClick={() => toggleCart.mutate({productId: product.id, quantity: 1})} color={"primary"} className={"order-3 col-span-2 lg:order-none text-black font-extrabold uppercase italic h-[70px]"}
|
<Button onClick={() => toggleCart.mutate({productId: product.id, quantity: 1})} color={"primary"} className={"order-3 col-span-2 lg:order-none text-black font-extrabold uppercase italic h-[70px]"}
|
||||||
startContent={<Cart/>}>
|
startContent={<Cart/>}>
|
||||||
|
|
||||||
{cartItems.data && cartItems.data.map(({id})=> id).includes(product.id) ? "В корзине" : "Добавить в корзину"}
|
{(cartItems.data && cartItems.data.data && cartItems.data.data.map((item)=> item.product.id).includes(product.id)) ? "В корзине" : "Добавить в корзину"}
|
||||||
|
|
||||||
</Button>}
|
</Button>}
|
||||||
{
|
{
|
||||||
|
@ -181,8 +180,8 @@ export default OilCard
|
||||||
|
|
||||||
export const getStaticPaths = async () => {
|
export const getStaticPaths = async () => {
|
||||||
const service = new LocalAPI()
|
const service = new LocalAPI()
|
||||||
const data = await service.getCatalogItems({}, 1) as {code: string}[]
|
const {data} = await service.getCatalogItems({}, 1)
|
||||||
const codes = data.map(item => ({params: {code: item.code}}))
|
const codes = data!.map(item => ({params: {code: item.code}}))
|
||||||
return {
|
return {
|
||||||
paths: [
|
paths: [
|
||||||
...codes
|
...codes
|
||||||
|
@ -194,10 +193,20 @@ export const getStaticPaths = async () => {
|
||||||
|
|
||||||
export const getStaticProps = async ({params: {code}}: { params: { code: string } }) => {
|
export const getStaticProps = async ({params: {code}}: { params: { code: string } }) => {
|
||||||
const service = new LocalAPI()
|
const service = new LocalAPI()
|
||||||
|
try {
|
||||||
const data = await service.getCatalogItemByCode(code)
|
const data = await service.getCatalogItemByCode(code)
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
product: data
|
product: data.data!
|
||||||
|
},
|
||||||
|
revalidate: 15*60
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (e) {
|
||||||
|
return {
|
||||||
|
redirect: {"destination": "/404", "permanent": false},
|
||||||
|
revalidate: 15*60
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,7 +1,12 @@
|
||||||
import {
|
import {
|
||||||
CheckboxGroup,
|
CheckboxGroup,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
Button, Skeleton, Accordion, AccordionItem, Pagination, Spinner
|
Button,
|
||||||
|
Skeleton,
|
||||||
|
Accordion,
|
||||||
|
AccordionItem,
|
||||||
|
Pagination,
|
||||||
|
Spinner,
|
||||||
} from "@nextui-org/react";
|
} from "@nextui-org/react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
||||||
|
@ -17,221 +22,283 @@ import {useMutation, useQuery, useQueryClient} from "@tanstack/react-query";
|
||||||
import LocalAPI from "@/service/localAPI";
|
import LocalAPI from "@/service/localAPI";
|
||||||
import { Img } from "react-image";
|
import { Img } from "react-image";
|
||||||
import { Dispatch, SetStateAction, useEffect, useState } from "react";
|
import { Dispatch, SetStateAction, useEffect, useState } from "react";
|
||||||
import {element} from "prop-types";
|
import { Product } from "@/service/types/local";
|
||||||
|
|
||||||
type SelectedFiltersStruct = {
|
type SelectedFiltersStruct = {
|
||||||
[key: string]: string[]
|
[key: string]: string[];
|
||||||
}
|
};
|
||||||
type SelectedFiltersDispatcher = Dispatch<SetStateAction<SelectedFiltersStruct>>
|
type SelectedFiltersDispatcher = Dispatch<
|
||||||
|
SetStateAction<SelectedFiltersStruct>
|
||||||
const CheckboxUI = (obj: { id: number, code: string, name: string, values: { id: number, value: string }[] } & {
|
>;
|
||||||
dispatcher: SelectedFiltersDispatcher
|
|
||||||
}) => {
|
|
||||||
|
|
||||||
|
const CheckboxUI = (
|
||||||
|
obj: {
|
||||||
|
id: number;
|
||||||
|
code: string;
|
||||||
|
name: string;
|
||||||
|
values: { id: number; value: string }[];
|
||||||
|
} & {
|
||||||
|
dispatcher: SelectedFiltersDispatcher;
|
||||||
|
},
|
||||||
|
) => {
|
||||||
return (
|
return (
|
||||||
<Accordion>
|
<Accordion>
|
||||||
<AccordionItem title={obj.name}>
|
<AccordionItem title={obj.name}>
|
||||||
<CheckboxGroup>
|
<CheckboxGroup>
|
||||||
{obj.values && obj.values.map(val => (
|
{obj.values &&
|
||||||
<Checkbox onChange={
|
obj.values.map((val) => (
|
||||||
() => {
|
<Checkbox
|
||||||
|
onChange={() => {
|
||||||
obj.dispatcher((prevState) => {
|
obj.dispatcher((prevState) => {
|
||||||
|
if (
|
||||||
if (prevState[obj.code] && prevState[obj.code].includes(val.value)) {
|
prevState[obj.code] &&
|
||||||
|
prevState[obj.code].includes(val.value)
|
||||||
|
) {
|
||||||
return {
|
return {
|
||||||
...prevState,
|
...prevState,
|
||||||
[obj.code]: prevState[obj.code].filter(v => v !== val.value)
|
[obj.code]: prevState[obj.code].filter(
|
||||||
}
|
(v) => v !== val.value,
|
||||||
|
),
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
...prevState,
|
...prevState,
|
||||||
[obj.code]: [...(prevState[obj.code] || []), val.value]
|
[obj.code]: [...(prevState[obj.code] || []), val.value],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
})
|
}}
|
||||||
}
|
value={String(val.id)}
|
||||||
} value={String(val.id)} key={"VALUE_" + val.id}>{val.value}</Checkbox>
|
key={"VALUE_" + val.id}
|
||||||
|
>
|
||||||
|
{val.value}
|
||||||
|
</Checkbox>
|
||||||
))}
|
))}
|
||||||
</CheckboxGroup>
|
</CheckboxGroup>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
|
|
||||||
</Accordion>
|
</Accordion>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
const FilterGenerator = ({
|
const FilterGenerator = ({
|
||||||
filterPropertiesData,
|
filterPropertiesData,
|
||||||
setSelectedFilter
|
setSelectedFilter,
|
||||||
}: Pick<InferGetStaticPropsType<typeof getStaticProps>, "filterPropertiesData"> & {
|
}: Pick<
|
||||||
setSelectedFilter: SelectedFiltersDispatcher
|
InferGetStaticPropsType<typeof getStaticProps>,
|
||||||
|
"filterPropertiesData"
|
||||||
|
> & {
|
||||||
|
setSelectedFilter: SelectedFiltersDispatcher;
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<Accordion className={'filters mb-10 mr-4 lg:w-1/4 w-full'} defaultExpandedKeys={['1']}>
|
<Accordion
|
||||||
<AccordionItem title={"Фильтры"} key={"1"} aria-label={"Фильтры"}
|
className={"filters mb-10 mr-4 w-full lg:w-1/4"}
|
||||||
classNames={{trigger: "bg-primary px-4", base: "border-1 rounded", title: "font-bold"}}>
|
defaultExpandedKeys={["1"]}
|
||||||
{filterPropertiesData.map(obj => (
|
>
|
||||||
|
<AccordionItem
|
||||||
|
title={"Фильтры"}
|
||||||
|
key={"1"}
|
||||||
|
aria-label={"Фильтры"}
|
||||||
|
classNames={{
|
||||||
|
trigger: "bg-primary px-4",
|
||||||
|
base: "border-1 rounded",
|
||||||
|
title: "font-bold",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{filterPropertiesData.map((obj) => (
|
||||||
<CheckboxUI key={obj.id} {...obj} dispatcher={setSelectedFilter} />
|
<CheckboxUI key={obj.id} {...obj} dispatcher={setSelectedFilter} />
|
||||||
))}
|
))}
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
type CatalogItemStruct = {
|
const CatalogCard = (product: Product & { isFavourite: boolean }) => {
|
||||||
id: number
|
const isClient = useClient();
|
||||||
code: string,
|
|
||||||
name: string,
|
|
||||||
properties: {
|
|
||||||
[key: string]: string | string[]
|
|
||||||
}
|
|
||||||
detailText: string,
|
|
||||||
price: {
|
|
||||||
[key: string]: number | null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
type CardProps = CatalogItemStruct & { isFavourite?: boolean }
|
|
||||||
|
|
||||||
|
|
||||||
const CatalogCard = (product: CardProps) => {
|
|
||||||
const isClient = useClient()
|
|
||||||
const cartItems = useQuery({
|
const cartItems = useQuery({
|
||||||
queryKey: ['cart'], queryFn: async () => {
|
queryKey: ["cart"],
|
||||||
const service = new LocalAPI()
|
queryFn: async () => {
|
||||||
return await service.getCartItems()
|
const service = new LocalAPI();
|
||||||
}
|
return await service.getCartItems();
|
||||||
})
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const qs = useQueryClient();
|
||||||
const qs = useQueryClient()
|
|
||||||
|
|
||||||
const toggleCart = useMutation({
|
const toggleCart = useMutation({
|
||||||
mutationFn: async ({productId, quantity}: { productId: number, quantity: number }) => {
|
mutationFn: async ({
|
||||||
const service = new LocalAPI()
|
productId,
|
||||||
if (cartItems.data!.find(({id}) => id === productId)) {
|
quantity,
|
||||||
return await service.deleteCartItem(productId)
|
}: {
|
||||||
|
productId: number;
|
||||||
|
quantity: number;
|
||||||
|
}) => {
|
||||||
|
const service = new LocalAPI();
|
||||||
|
if (
|
||||||
|
cartItems.data &&
|
||||||
|
cartItems.data.data &&
|
||||||
|
cartItems.data.data.find((item ) => item.product.id === productId)
|
||||||
|
) {
|
||||||
|
return await service.deleteCartItem(productId);
|
||||||
}
|
}
|
||||||
return await service.addCartItem(productId, quantity)
|
return await service.addCartItem(productId, quantity);
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
qs.invalidateQueries({queryKey: ["cart"]})
|
qs.invalidateQueries({ queryKey: ["cart"] });
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={"bg-gray-card w-full h-fit min-h-[250px] py-4 px-7 rounded-[20px] hover:shadow-md transition-shadow hover:cursor-pointer"}
|
className={
|
||||||
key={product.id}>
|
"bg-gray-card h-fit min-h-[250px] w-full rounded-[20px] px-7 py-4 transition-shadow hover:cursor-pointer hover:shadow-md"
|
||||||
<div className={"grid grid-cols-1 sm:grid-cols-10 w-fit relative items-center"}>
|
|
||||||
{
|
|
||||||
isClient && product.properties.main_image &&
|
|
||||||
<Img src={`https://relynolli.ru/upload/${product.properties.main_image[0]}`} alt={product.name}
|
|
||||||
className={"col-auto mx-auto mb-4 sm:col-span-2 sm:row-span-2"}
|
|
||||||
loader={<Spinner/>}/>
|
|
||||||
}
|
}
|
||||||
<div className="col-auto sm:col-start-4 sm:col-span-6 h-full flex flex-col justify-center">
|
key={product.id}
|
||||||
<span
|
>
|
||||||
className={"text-[#52525C] font-normal text-subtitle-4 mb-2 block"}>Стандарт API: {product.properties.api_standart} Тип: {product.properties.oil_type}</span>
|
|
||||||
<h3 className={"font-bold text-lg uppercase text-black-3"}>{product.name}</h3>
|
|
||||||
</div>
|
|
||||||
{
|
|
||||||
isClient ?
|
|
||||||
<FavouriteIcon onClick={() => toggleFavourite(product.id)}
|
|
||||||
className={`transition-colors absolute z-20 top-0 right-0 ${product.isFavourite ? "fill-primary" : "fill-[#E0E3E3]"}`}/> : null
|
|
||||||
}
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className="flex col-auto row-auto sm:row-start-2 sm:col-start-4 sm:col-span-7 justify-between w-full items-center pt-4">
|
className={
|
||||||
<span className="font-bold text-xl text-black-3">
|
"relative grid w-fit grid-cols-1 items-center sm:grid-cols-10"
|
||||||
{`${product.price.BASE}`.replace(/\B(?=(\d{3})+(?!\d))/g, ' ')} ₽
|
}
|
||||||
|
>
|
||||||
|
{isClient && product.properties.main_image && (
|
||||||
|
<Img
|
||||||
|
src={`https://relynolli.ru/upload/${product.properties.main_image[0]}`}
|
||||||
|
alt={product.name}
|
||||||
|
className={"col-auto mx-auto mb-4 sm:col-span-2 sm:row-span-2 max-h-[250px]"}
|
||||||
|
loader={<Spinner />}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<div className="col-auto flex h-full flex-col justify-center sm:col-span-6 sm:col-start-4">
|
||||||
|
<span
|
||||||
|
className={"text-subtitle-4 mb-2 block font-normal text-[#52525C]"}
|
||||||
|
>
|
||||||
|
Стандарт API: {product.properties.api_standart} Тип:{" "}
|
||||||
|
{product.properties.oil_type}
|
||||||
</span>
|
</span>
|
||||||
{
|
<h3 className={"text-black-3 text-lg font-bold uppercase"}>
|
||||||
isClient ?
|
{product.name}
|
||||||
<Button onClick={() => toggleCart.mutate({
|
</h3>
|
||||||
|
</div>
|
||||||
|
{isClient ? (
|
||||||
|
<FavouriteIcon
|
||||||
|
onClick={() => toggleFavourite(product.id)}
|
||||||
|
className={`absolute right-0 top-0 z-20 transition-colors ${product.isFavourite ? "fill-primary" : "fill-[#E0E3E3]"}`}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<div className="col-auto row-auto flex w-full items-center justify-between pt-4 sm:col-span-7 sm:col-start-4 sm:row-start-2">
|
||||||
|
<span className="text-black-3 text-xl font-bold">
|
||||||
|
{`${product.price.BASE}`.replace(/\B(?=(\d{3})+(?!\d))/g, " ")} ₽
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{(cartItems.data) ? (
|
||||||
|
<Button
|
||||||
|
onClick={() =>
|
||||||
|
toggleCart.mutate({
|
||||||
productId: product.id,
|
productId: product.id,
|
||||||
quantity: 1
|
quantity: 1,
|
||||||
})}
|
})
|
||||||
className={"font-bold text-lg bg-green-2 uppercase italic text-black-3 relative z-20"}>{cartItems.data?.map(({id}) => id).includes(product.id) ? "В корзине" : "В корзину"}</Button> : null
|
|
||||||
}
|
}
|
||||||
|
className={
|
||||||
<Link href={'/catalog/' + product.code} className={'absolute top-0 left-0 z-10 w-full h-full'}/>
|
"bg-green-2 text-black-3 relative z-20 text-lg font-bold uppercase italic"
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
>
|
||||||
|
{(cartItems.data && cartItems.data.data && cartItems.data.data
|
||||||
|
.map(({ productId }) => productId)
|
||||||
|
.includes(product.id))
|
||||||
|
? "В корзине"
|
||||||
|
: "В корзину"}
|
||||||
|
</Button>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<Link
|
||||||
|
href={"/catalog/" + product.code}
|
||||||
|
className={"absolute left-0 top-0 z-10 h-full w-full"}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const Catalog = (props: InferGetStaticPropsType<typeof getStaticProps>) => {
|
const Catalog = (props: InferGetStaticPropsType<typeof getStaticProps>) => {
|
||||||
|
const { favourites } = useSnapshot(favouritesStore);
|
||||||
const {favourites} = useSnapshot(favouritesStore)
|
const [selectedFilters, setSelectedFilters] = useState<SelectedFiltersStruct>(
|
||||||
const [selectedFilters, setSelectedFilters] = useState<SelectedFiltersStruct>({})
|
{},
|
||||||
|
);
|
||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
const [pageCount, setPageCount] = useState(1)
|
const [totalPageCount, setTotalPageCount] = useState(1);
|
||||||
const [showPagination, setShowPagination] = useState(true);
|
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const service = new LocalAPI()
|
setPage(1);
|
||||||
service.getCatalogItemsCount().then(data => setPageCount(Math.ceil(data / 10)))
|
}, [selectedFilters]);
|
||||||
|
|
||||||
|
const catalogQuery = useQuery({
|
||||||
|
queryKey: ["catalog", { selectedFilters, page }],
|
||||||
|
queryFn: async () => {
|
||||||
|
const service = new LocalAPI();
|
||||||
|
const data = await service.getCatalogItems(selectedFilters, page);
|
||||||
|
setTotalPageCount(Math.ceil(data.meta.count! / 10));
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
})
|
return (
|
||||||
|
<Wrapper
|
||||||
const catalogQuery = useQuery<CatalogItemStruct[]>({
|
title={"Каталог"}
|
||||||
queryKey: ["catalog", {selectedFilters, page}], queryFn: async () => {
|
breadcrumbs={[{ name: "Каталог", link: "/catalog" }]}
|
||||||
const service = new LocalAPI()
|
>
|
||||||
return await service.getCatalogItems(selectedFilters, page)
|
<div className="mb-4 flex justify-end">
|
||||||
}
|
<Pagination
|
||||||
})
|
page={page}
|
||||||
|
total={totalPageCount}
|
||||||
|
initialPage={1}
|
||||||
return (<Wrapper title={"Каталог"} breadcrumbs={[{name: "Каталог", link: "/catalog"}]}>
|
onChange={(page) => {
|
||||||
<div className="flex justify-end mb-4">
|
setPage(page);
|
||||||
{ !Object.values(selectedFilters).filter(elem => elem.length).length &&
|
}}
|
||||||
<Pagination total={pageCount} initialPage={1} onChange={(page) => {
|
/>
|
||||||
setPage(page)
|
|
||||||
}}/>
|
|
||||||
}
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col justify-between lg:flex-row">
|
<div className="flex flex-col justify-between lg:flex-row">
|
||||||
<FilterGenerator filterPropertiesData={props.filterPropertiesData}
|
<FilterGenerator
|
||||||
setSelectedFilter={setSelectedFilters}/>
|
filterPropertiesData={props.filterPropertiesData}
|
||||||
<div className="products grid grid-cols-1 2xl:grid-cols-2 gap-7 lg:w-3/4 h-fit w-full">
|
setSelectedFilter={setSelectedFilters}
|
||||||
|
/>
|
||||||
|
<div className="products grid h-fit w-full grid-cols-1 gap-7 lg:w-3/4 2xl:grid-cols-2">
|
||||||
{catalogQuery.isFetching &&
|
{catalogQuery.isFetching &&
|
||||||
Array(10).fill(null).map((_, index) => <Skeleton key={index}
|
Array(10)
|
||||||
className={"h-[250px] rounded-[20px]"}/>)
|
.fill(null)
|
||||||
}
|
.map((_, index) => (
|
||||||
|
<Skeleton key={index} className={"h-[250px] rounded-[20px]"} />
|
||||||
|
))}
|
||||||
|
|
||||||
{catalogQuery.data &&
|
{catalogQuery.data &&
|
||||||
catalogQuery.data.map(product =>
|
catalogQuery.data.data!.map((product) => (
|
||||||
<>
|
<>
|
||||||
<CatalogCard key={product.id} {...product}
|
<CatalogCard
|
||||||
isFavourite={favourites.includes(product.id)}/>
|
key={product.id}
|
||||||
|
{...product}
|
||||||
|
isFavourite={favourites.includes(product.id)}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
)
|
))}
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default Catalog;
|
export default Catalog;
|
||||||
|
|
||||||
export const getStaticProps = async () => {
|
export const getStaticProps = async () => {
|
||||||
const service = new LocalAPI()
|
const service = new LocalAPI();
|
||||||
const filterData = await service.getFilters() as {
|
const filterData = (await service.getFilters()) as {
|
||||||
id: number,
|
id: number;
|
||||||
name: string,
|
name: string;
|
||||||
code: string,
|
code: string;
|
||||||
values: { id: number, value: string }[]
|
values: { id: number; value: string }[];
|
||||||
}[]
|
}[];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
filterPropertiesData: filterData,
|
filterPropertiesData: filterData,
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {Button, dropdownSection, Tooltip} from "@nextui-org/react";
|
import {Button, Divider, dropdownSection, Spinner, Tooltip} from "@nextui-org/react";
|
||||||
import ChevronBannerIcon from "@/../public/banner_arr_btn.svg.svg"
|
import ChevronBannerIcon from "@/../public/banner_arr_btn.svg.svg"
|
||||||
import {Card, CardHeader, Image} from "@nextui-org/react";
|
import {Card, CardHeader, Image} from "@nextui-org/react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
@ -9,6 +9,12 @@ import "swiper/css"
|
||||||
import 'swiper/css/navigation';
|
import 'swiper/css/navigation';
|
||||||
import 'swiper/css/pagination';
|
import 'swiper/css/pagination';
|
||||||
import "swiper/css/autoplay";
|
import "swiper/css/autoplay";
|
||||||
|
import * as http2 from "http2";
|
||||||
|
import {Img} from "react-image";
|
||||||
|
import {News, Wrapper} from "@/service/types/local";
|
||||||
|
import useClient from "@/hooks/useClient";
|
||||||
|
import {useQuery} from "@tanstack/react-query";
|
||||||
|
import LocalAPI from "@/service/localAPI";
|
||||||
|
|
||||||
const Hero = () => {
|
const Hero = () => {
|
||||||
return (
|
return (
|
||||||
|
@ -26,23 +32,26 @@ const Hero = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
navigation={
|
navigation={
|
||||||
{
|
{}
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
pagination={{clickable: true}}
|
pagination={{clickable: true}}
|
||||||
|
|
||||||
>
|
>
|
||||||
<SwiperSlide>
|
<SwiperSlide>
|
||||||
<section className={"w-full h-[796px] mb-2 text-white font-bold bg-cover bg-center xl:bg-right relative"}>
|
<section
|
||||||
|
className={"w-full h-[796px] mb-2 text-white font-bold bg-cover bg-center xl:bg-right relative"}>
|
||||||
<img src={"/cars.PNG"} className={"object-cover h-full w-full z-10 absolute"} alt={"cars"}/>
|
<img src={"/cars.PNG"} className={"object-cover h-full w-full z-10 absolute"} alt={"cars"}/>
|
||||||
<div className={"wrapper h-full flex flex-col justify-center relative z-20"}>
|
<div className={"wrapper h-full flex flex-col justify-center relative z-20"}>
|
||||||
<div className="w-11/12 mx-auto lg:m-0 xl:w-1/2 flex flex-col bg-black bg-opacity-25 py-4 xl:px-7 px-2 rounded-[20px]">
|
<div
|
||||||
<h2 className={"text-2xl leading-[100%] xl:text-title-4 2xl:text-title-2 mb-4 xl:mb-8"}>Моторные масла для <span className={"text-primary"}>дизельных</span> двигателей</h2>
|
className="w-11/12 mx-auto lg:m-0 xl:w-1/2 flex flex-col bg-black bg-opacity-25 py-4 xl:px-7 px-2 rounded-[20px]">
|
||||||
|
<h2 className={"text-2xl leading-[100%] xl:text-title-4 2xl:text-title-2 mb-4 xl:mb-8"}>Моторные
|
||||||
|
масла для <span className={"text-primary"}>дизельных</span> двигателей</h2>
|
||||||
<span className={"text-sm xl:text-subtitle-1 leading-inherit text-subtitle-1 mb-4 xl:mb-6"}>Грузового транспорта и спецтехники</span>
|
<span className={"text-sm xl:text-subtitle-1 leading-inherit text-subtitle-1 mb-4 xl:mb-6"}>Грузового транспорта и спецтехники</span>
|
||||||
|
|
||||||
<Link href={"/catalog"} className={'w-full lg:w-1/2 bg-green-2 rounded-[8px] p-6 flex justify-between items-center text-black hover:scale-105 transition'}>
|
<Link href={"/catalog"}
|
||||||
<span className={"text-sm text-[1.125rem] font-extrabold italic uppercase"}>Купить</span>
|
className={'w-full lg:w-1/2 bg-green-2 rounded-[8px] p-6 flex justify-between items-center text-black hover:scale-105 transition'}>
|
||||||
|
<span
|
||||||
|
className={"text-sm text-[1.125rem] font-extrabold italic uppercase"}>Купить</span>
|
||||||
<ChevronBannerIcon className={"stroke-[3px] stroke-black"}/>
|
<ChevronBannerIcon className={"stroke-[3px] stroke-black"}/>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
@ -51,13 +60,19 @@ const Hero = () => {
|
||||||
</section>
|
</section>
|
||||||
</SwiperSlide>
|
</SwiperSlide>
|
||||||
<SwiperSlide>
|
<SwiperSlide>
|
||||||
<section className={"w-full bg-no-repeat h-[796px] mb-2 text-white font-bold bg-cover bg-center xl:bg-right relative"}>
|
<section
|
||||||
|
className={"w-full bg-no-repeat h-[796px] mb-2 text-white font-bold bg-cover bg-center xl:bg-right relative"}>
|
||||||
<img src={"/hands.PNG"} className={"object-cover h-full w-full z-10 absolute"} alt={"hands"}/>
|
<img src={"/hands.PNG"} className={"object-cover h-full w-full z-10 absolute"} alt={"hands"}/>
|
||||||
<div className={"wrapper h-full flex flex-col justify-center relative z-20"}>
|
<div className={"wrapper h-full flex flex-col justify-center relative z-20"}>
|
||||||
<div className="w-11/12 mx-auto lg:m-0 xl:w-1/2 flex flex-col bg-black bg-opacity-25 py-4 xl:px-7 px-2 rounded-[20px]">
|
<div
|
||||||
<h2 className={"text-2xl leading-[100%] xl:text-title-4 2xl:text-title-3 mb-4 xl:mb-8"}><span className={"2xl:text-title-3 text-primary"}>Начать бизнес</span> по продажам качественного моторного масла </h2>
|
className="w-11/12 mx-auto lg:m-0 xl:w-1/2 flex flex-col bg-black bg-opacity-25 py-4 xl:px-7 px-2 rounded-[20px]">
|
||||||
<span className={"text-sm xl:text-title-2 leading-inherit text-subtitle-1 mb-4 xl:mb-6 text-primary"}>-ЛЕГКО</span>
|
<h2 className={"text-2xl leading-[100%] xl:text-title-4 2xl:text-title-3 mb-4 xl:mb-8"}>
|
||||||
<Link href={"https://forms.yandex.ru/u/65e4c1e9eb6146024f8e3234/"} className={'w-full lg:w-1/2 bg-green-2 rounded-[8px] p-6 flex justify-between items-center text-black hover:scale-105 transition'}>
|
<span className={"2xl:text-title-3 text-primary"}>Начать бизнес</span> по продажам
|
||||||
|
качественного моторного масла </h2>
|
||||||
|
<span
|
||||||
|
className={"text-sm xl:text-title-2 leading-inherit text-subtitle-1 mb-4 xl:mb-6 text-primary"}>-ЛЕГКО</span>
|
||||||
|
<Link href={"https://forms.yandex.ru/u/65e4c1e9eb6146024f8e3234/"}
|
||||||
|
className={'w-full lg:w-1/2 bg-green-2 rounded-[8px] p-6 flex justify-between items-center text-black hover:scale-105 transition'}>
|
||||||
<span className={"text-sm text-[1.125rem] font-extrabold italic uppercase"}>Получить предложение о партнерстве</span>
|
<span className={"text-sm text-[1.125rem] font-extrabold italic uppercase"}>Получить предложение о партнерстве</span>
|
||||||
<ChevronBannerIcon className={"stroke-[3px] stroke-black"}/>
|
<ChevronBannerIcon className={"stroke-[3px] stroke-black"}/>
|
||||||
</Link>
|
</Link>
|
||||||
|
@ -67,14 +82,19 @@ const Hero = () => {
|
||||||
</section>
|
</section>
|
||||||
</SwiperSlide>
|
</SwiperSlide>
|
||||||
<SwiperSlide key={Math.random()}>
|
<SwiperSlide key={Math.random()}>
|
||||||
<section className={"w-full bg-cars bg-no-repeat h-[796px] mb-2 text-white font-bold bg-cover bg-center xl:bg-right relative"}>
|
<section
|
||||||
|
className={"w-full bg-cars bg-no-repeat h-[796px] mb-2 text-white font-bold bg-cover bg-center xl:bg-right relative"}>
|
||||||
<img src={"/sto.PNG"} className={"object-cover h-full w-full z-10 absolute"} alt={"auto services"}/>
|
<img src={"/sto.PNG"} className={"object-cover h-full w-full z-10 absolute"} alt={"auto services"}/>
|
||||||
<div className={"wrapper h-full flex flex-col justify-center z-20 relative"}>
|
<div className={"wrapper h-full flex flex-col justify-center z-20 relative"}>
|
||||||
<div className="w-11/12 mx-auto lg:m-0 xl:w-1/2 flex flex-col bg-black bg-opacity-25 py-4 xl:px-7 px-2 rounded-[20px]">
|
<div
|
||||||
<h2 className={"text-2xl leading-[100%] xl:text-title-4 2xl:text-title-2 mb-4 xl:mb-8"}>Приглашаем к сотрудничеству СТО и автосервисы</h2>
|
className="w-11/12 mx-auto lg:m-0 xl:w-1/2 flex flex-col bg-black bg-opacity-25 py-4 xl:px-7 px-2 rounded-[20px]">
|
||||||
<span className={"text-sm xl:text-subtitle-1 leading-inherit text-subtitle-1 mb-4 xl:mb-6"}>Поставляем масло <span className={"text-primary"}>RELYNOLLI ®</span> в партнерские СТО на льготных условиях</span>
|
<h2 className={"text-2xl leading-[100%] xl:text-title-4 2xl:text-title-2 mb-4 xl:mb-8"}>Приглашаем
|
||||||
|
к сотрудничеству СТО и автосервисы</h2>
|
||||||
|
<span className={"text-sm xl:text-subtitle-1 leading-inherit text-subtitle-1 mb-4 xl:mb-6"}>Поставляем масло <span
|
||||||
|
className={"text-primary"}>RELYNOLLI ®</span> в партнерские СТО на льготных условиях</span>
|
||||||
|
|
||||||
<Link href={"https://forms.yandex.ru/u/65e4c1e9eb6146024f8e3234/"} className={'w-full lg:w-1/2 bg-green-2 rounded-[8px] p-6 flex justify-between items-center text-black hover:scale-105 transition'}>
|
<Link href={"https://forms.yandex.ru/u/65e4c1e9eb6146024f8e3234/"}
|
||||||
|
className={'w-full lg:w-1/2 bg-green-2 rounded-[8px] p-6 flex justify-between items-center text-black hover:scale-105 transition'}>
|
||||||
<span className={"text-sm text-[1.125rem] font-extrabold italic uppercase"}>Узнать больше</span>
|
<span className={"text-sm text-[1.125rem] font-extrabold italic uppercase"}>Узнать больше</span>
|
||||||
<ChevronBannerIcon className={"stroke-[3px] stroke-black"}/>
|
<ChevronBannerIcon className={"stroke-[3px] stroke-black"}/>
|
||||||
</Link>
|
</Link>
|
||||||
|
@ -88,69 +108,97 @@ const Hero = () => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const MainInfo = () => {
|
const MainInfo = () => {
|
||||||
return (
|
return (
|
||||||
<section className={"bg-white text-black rounded-[8px]"}>
|
<section className={"bg-white text-black rounded-[8px]"}>
|
||||||
<div className="wrapper py-8 mb-8">
|
<div className="wrapper py-8 mb-8">
|
||||||
<h1 className={"text-4xl xl:text-title-1 leading-normal font-bold text-center italic uppercase text-black mb-8"}>Моторные масла и смазочные материалы <br/>
|
<h1 className={"text-4xl xl:text-title-1 leading-normal font-bold text-center italic uppercase text-black mb-8"}>Моторные
|
||||||
|
масла и смазочные материалы <br/>
|
||||||
<span className={"text-primary"}>Relynolli ®</span></h1>
|
<span className={"text-primary"}>Relynolli ®</span></h1>
|
||||||
|
|
||||||
<span className={"text-base lg:text-2xl xl:text-4xl text-center block w-1/2 mx-auto"}>Технологичные решения для надежной работы двигателя</span>
|
<span className={"text-base lg:text-2xl xl:text-4xl text-center block w-1/2 mx-auto"}>Технологичные решения для надежной работы двигателя</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="wrapper mx-auto py-8 grid grid-cols-1 lg:grid-cols-2 grid-rows-3 lg:grid-rows-2 gap-4 h-auto [&>*]:h-[390px] [&_span]:text-base [&_span]:md:text-xl [&_span]:leading-[110%]">
|
<div
|
||||||
|
className="wrapper mx-auto py-8 grid grid-cols-1 lg:grid-cols-2 grid-rows-3 lg:grid-rows-2 gap-4 h-auto [&>*]:h-[390px] [&_span]:text-base [&_span]:md:text-xl [&_span]:leading-[110%]">
|
||||||
|
|
||||||
<Card className={"hover:scale-105 relative group grayscale hover:grayscale-0 transition-all"}>
|
<Card className={"lg:hover:scale-105 relative group lg:grayscale hover:grayscale-0 transition-all"}>
|
||||||
<Link href={"/catalog"} className={"absolute top-0 left-0 z-20 w-full h-full"}/>
|
<Link href={"/catalog"} className={"absolute top-0 left-0 z-20 w-full h-full"}/>
|
||||||
<Image removeWrapper className={"w-full h-full object-cover brightness-75 relative z-10"} src={"/oil2.png"}></Image>
|
<Image removeWrapper className={"w-full h-full object-cover brightness-75 relative z-10"}
|
||||||
|
src={"/oil2.png"}></Image>
|
||||||
<CardHeader
|
<CardHeader
|
||||||
className="absolute z-10 top-10 left-10 bottom-5 flex-col !items-start w-3/4 justify-between">
|
className="absolute z-10 top-10 left-10 bottom-5 flex-col !items-start w-3/4 justify-between">
|
||||||
<h3 className={"text-title-3 leading-[35px] text-white opacity-0 translate-y-1/2 group-hover:opacity-100 group-hover:translate-y-0 transition-all"}>Масла для легковых автомобилей</h3>
|
<h3 className={"text-3xl lg:text-title-3 leading-[100%] text-white lg:opacity-0 lg:translate-y-1/2 group-hover:opacity-100 group-hover:translate-y-0 transition-all"}>Масла
|
||||||
|
для легковых автомобилей</h3>
|
||||||
<span className={"text-gray-2 opacity-0 translate-y-1/2 group-hover:opacity-100 group-hover:translate-y-0 transition-all delay-100"}>Серия Standart и Premium для легковых автомобилей для наилучшей энергоэффективности двигателя вашего автомобиля</span>
|
<span
|
||||||
|
className={"text-gray-2 lg:opacity-0 lg:translate-y-1/2 lg:group-hover:opacity-100 lg:group-hover:translate-y-0 transition-all delay-100"}>Серия Standart и Premium для легковых автомобилей для наилучшей энергоэффективности двигателя вашего автомобиля</span>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Tooltip content={"Выпуск Апрель 2024"} className={"text-black"} size={"lg"}>
|
<Tooltip content={"Выпуск Апрель 2024"} className={"text-black"} size={"lg"}>
|
||||||
<Card className={"rounded-[30px] group transition-size grayscale"}>
|
<Card className={"rounded-[30px] group transition-size"}>
|
||||||
<Image removeWrapper className={"z-0 w-full h-full object-cover brightness-50"} src={"/oil1.png"}></Image>
|
<Image removeWrapper className={"z-0 w-full h-full object-cover brightness-50 grayscale"}
|
||||||
|
src={"/oil1.png"}></Image>
|
||||||
<CardHeader
|
<CardHeader
|
||||||
className="absolute z-10 top-10 left-10 bottom-5 flex-col !items-start w-3/4 justify-between">
|
className="absolute z-10 top-10 left-10 bottom-5 flex-col !items-start w-3/4 justify-between">
|
||||||
<h3 className={"text-title-3 leading-[35px] text-white opacity-0 translate-y-1/2 group-hover:opacity-100 group-hover:translate-y-0 transition-all"}>Специальная серия масел XMR</h3>
|
<h3 className={"text-3xl lg:text-title-3 leading-[100%] text-white lg:opacity-0 lg:translate-y-1/2 group-hover:opacity-100 group-hover:translate-y-0 transition-all"}>Специальная
|
||||||
|
серия масел XMR</h3>
|
||||||
|
|
||||||
<span className={"text-gray-2 opacity-0 translate-y-1/2 group-hover:opacity-100 group-hover:translate-y-0 transition-all delay-100"}>Серия моторных масел XMR - премиальная линейка для двигателей со спортивным характером</span>
|
<span
|
||||||
|
className={"text-gray-2 lg:opacity-0 lg:translate-y-1/2 group-hover:opacity-100 group-hover:translate-y-0 transition-all delay-100"}>Серия моторных масел XMR - премиальная линейка для двигателей со спортивным характером</span>
|
||||||
|
|
||||||
|
<span
|
||||||
|
className={"block bg-primary text-black absolute top-[-10%] left-0 w-full font-bold lg:hidden px-4 py-2"}>
|
||||||
|
Выпуск Апрель 2024
|
||||||
|
</span>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
</Card>
|
</Card>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<Tooltip content={"Выпуск Июнь 2024"} className={"text-black"} size={"lg"}>
|
<Tooltip content={"Выпуск Июнь 2024"} className={"text-black"} size={"lg"}>
|
||||||
|
|
||||||
<Card className={"rounded-[30px] transition-size group grayscale"}>
|
<Card className={"rounded-[30px] transition-size group"}>
|
||||||
<Image removeWrapper className={"z-0 w-full h-full object-cover brightness-50"} src={"/oil3.png"}></Image>
|
<Image removeWrapper className={"z-0 w-full h-full object-cover brightness-50 grayscale"}
|
||||||
|
src={"/oil3.png"}></Image>
|
||||||
<CardHeader
|
<CardHeader
|
||||||
className="absolute z-10 top-10 left-10 bottom-5 flex-col !items-start w-3/4 justify-between">
|
className="absolute z-10 top-10 left-10 bottom-5 flex-col !items-start w-3/4 justify-between">
|
||||||
<h3 className={"text-title-3 leading-[35px] text-white opacity-0 translate-y-1/2 group-hover:opacity-100 group-hover:translate-y-0 transition-all"}>Масла для коммерческого транспорта и спецтехники</h3>
|
<h3 className={"text-3xl lg:text-title-3 leading-[100%] text-white lg:opacity-0 lg:translate-y-1/2 group-hover:opacity-100 group-hover:translate-y-0 transition-all"}>Масла
|
||||||
|
для коммерческого транспорта и спецтехники</h3>
|
||||||
|
|
||||||
<span className={"text-gray-2 opacity-0 translate-y-1/2 group-hover:opacity-100 group-hover:translate-y-0 transition-all delay-100"}>Синтетические и полусинтетические масла для двигателей, которые эксплуатируются под высокими нагрузками. Подходят для бензиновых и дизельных двигателей</span>
|
<span
|
||||||
|
className={"text-gray-2 lg:opacity-0 lg:translate-y-1/2 group-hover:opacity-100 group-hover:translate-y-0 transition-all delay-100"}>Синтетические и полусинтетические масла для двигателей, которые эксплуатируются под высокими нагрузками. Подходят для бензиновых и дизельных двигателей</span>
|
||||||
|
|
||||||
|
<span
|
||||||
|
className={"block bg-primary text-black absolute top-[-10%] left-0 w-full font-bold lg:hidden px-4 py-2"}>
|
||||||
|
Выпуск Апрель 2024
|
||||||
|
</span>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
</Card>
|
</Card>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
|
|
||||||
<Tooltip content={"Выпуск Апрель 2024"} className={"text-black"} size={"lg"}>
|
<Tooltip content={"Выпуск Апрель 2024"} className={"text-black"} size={"lg"}>
|
||||||
<Card className={"rounded-[30px] transition-size group grayscale"}>
|
<Card className={"rounded-[30px] transition-size group"}>
|
||||||
<Image removeWrapper className={"z-0 w-full h-full object-cover brightness-50"} src={"/oil4.png"}></Image>
|
<Image removeWrapper className={"z-0 w-full h-full object-cover brightness-50 grayscale"}
|
||||||
|
src={"/oil4.png"}></Image>
|
||||||
<CardHeader
|
<CardHeader
|
||||||
className="absolute z-10 top-10 left-10 bottom-5 flex-col !items-start w-3/4 justify-between">
|
className="absolute z-10 top-10 left-10 bottom-5 flex-col !items-start w-3/4 justify-between">
|
||||||
<h3 className={"text-title-3 leading-[35px] text-white opacity-0 translate-y-1/2 group-hover:opacity-100 group-hover:translate-y-0 transition-all"}>Масла для мототехники</h3>
|
<h3 className={"text-3xl lg:text-title-3 leading-[100%] text-white lg:opacity-0 lg:translate-y-1/2 group-hover:opacity-100 group-hover:translate-y-0 transition-all"}>Масла
|
||||||
|
для мототехники</h3>
|
||||||
|
|
||||||
<span className={"text-gray-2 opacity-0 translate-y-1/2 group-hover:opacity-100 group-hover:translate-y-0 transition-all delay-100"}>Масла для четырех и двухтактных двигателей, которые помогут раскрыть неудержимый характер вашей мототехники</span>
|
<span
|
||||||
|
className={"text-gray-2 lg:opacity-0 lg:translate-y-1/2 group-hover:opacity-100 group-hover:translate-y-0 transition-all delay-100"}>Масла для четырех и двухтактных двигателей, которые помогут раскрыть неудержимый характер вашей мототехники</span>
|
||||||
|
|
||||||
|
<span
|
||||||
|
className={"block bg-primary text-black absolute top-[-10%] left-0 w-full font-bold lg:hidden px-4 py-2"}>
|
||||||
|
Выпуск Апрель 2024
|
||||||
|
</span>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -162,7 +210,9 @@ const Achievements = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className={"bg-cover backdrop-brightness-50 relative"}>
|
<section className={"bg-cover backdrop-brightness-50 relative"}>
|
||||||
<Image removeWrapper alt={"oiltypeImage"} className={"absolute top-0 left-0 w-full h-full brightness-50 object-fill -z-10"} src={"/oiltypeImage.png"}></Image>
|
<Image removeWrapper alt={"oiltypeImage"}
|
||||||
|
className={"absolute top-0 left-0 w-full h-full brightness-50 object-fill -z-10"}
|
||||||
|
src={"/oiltypeImage.png"}></Image>
|
||||||
<div className="wrapper py-8 mb-8 flex flex-col justify-center">
|
<div className="wrapper py-8 mb-8 flex flex-col justify-center">
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 mb-6 [&>*]:h-[500px] [&>*]:md:h-[700px]">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 mb-6 [&>*]:h-[500px] [&>*]:md:h-[700px]">
|
||||||
<Card className={"z-10 relative rounded-[30px] font-bold [&_span]:font-normal transition-size"}>
|
<Card className={"z-10 relative rounded-[30px] font-bold [&_span]:font-normal transition-size"}>
|
||||||
|
@ -192,28 +242,122 @@ const Achievements = () => {
|
||||||
className="absolute z-10 top-10 left-10 bottom-5 flex-col !items-start w-1/2 justify-between">
|
className="absolute z-10 top-10 left-10 bottom-5 flex-col !items-start w-1/2 justify-between">
|
||||||
|
|
||||||
<div className="block">
|
<div className="block">
|
||||||
<h3 className={"text-2xl md:text-3xl leading-[35px] text-white mb-6 "}>Моторные масла Relynolli ®</h3>
|
<h3 className={"text-2xl md:text-3xl leading-[35px] text-white mb-6 "}>Моторные масла
|
||||||
|
Relynolli ®</h3>
|
||||||
<span className={"text-base md:text-xl text-white opacity-50"}>Обладают высокой смазывающей способностью и обеспечивают надёжную защиту двигателя от износа</span>
|
<span className={"text-base md:text-xl text-white opacity-50"}>Обладают высокой смазывающей способностью и обеспечивают надёжную защиту двигателя от износа</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button className={"bg-green-2 font-bold uppercase italic"} endContent={<ChevronBannerIcon
|
<Button className={"bg-green-2 font-bold uppercase italic w-[200px] h-fit text-wrap"}
|
||||||
className={"stroke-[3px] stroke-black"}/>}>Расшифровка масел группы - N</Button>
|
endContent={<ChevronBannerIcon
|
||||||
|
className={"stroke-[3px] stroke-black"}/>}>
|
||||||
|
<span
|
||||||
|
className={"block w-2/3 text-left !font-bold italic"}>Расшифровка масел группы - N</span>
|
||||||
|
</Button>
|
||||||
|
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
<Button className={"bg-green-2 font-bold uppercase italic mx-auto"} endContent={<ChevronBannerIcon className={"stroke-[3px] stroke-black"}/>}>Перейти в продукцию</Button>
|
<Link className={"mx-auto"} href={"/catalog"}><Button
|
||||||
|
className={"bg-green-2 font-bold uppercase italic"}
|
||||||
|
endContent={<ChevronBannerIcon className={"stroke-[3px] stroke-black"}/>}>Перейти в
|
||||||
|
продукцию</Button></Link>
|
||||||
</div>
|
</div>
|
||||||
</section>)
|
</section>)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const NewsCard = (news: News) => {
|
||||||
|
return (
|
||||||
|
<Link href={"/news/" + news.code}>
|
||||||
|
|
||||||
|
|
||||||
|
<article className={"h-[300px] px-16 py-4"}>
|
||||||
|
<div className="block h-[200px] overflow-hidden mb-6">
|
||||||
|
<Img className={"object-fill"} src={"https://relynolli.ru/upload/" + news.picture} loader={<Spinner/>}/>
|
||||||
|
</div>
|
||||||
|
<div className="text">
|
||||||
|
<h3 className={"font-bold text-xl"}>{news.name}</h3>
|
||||||
|
<span className={"opacity-50"}>{new Date(news.date).toLocaleDateString()}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</article>
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const NewsSlider = () => {
|
||||||
|
const newsData = useQuery<Wrapper<News[]>>({
|
||||||
|
queryKey: ["news"], queryFn: async () => {
|
||||||
|
const service = new LocalAPI()
|
||||||
|
return await service.fetchNews()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return (
|
||||||
|
<section className={""}>
|
||||||
|
<div className="wrapper">
|
||||||
|
<div className="flex flex-wrap items-center mb-5">
|
||||||
|
<h2 className={"text-2xl md:text-3xl leading-[35px] text-white m-0 mr-4 "}>Новости</h2>
|
||||||
|
<Link
|
||||||
|
className={"block border-primary border-1 text-primary rounded px-4 py-2 italic text-2xl hover:bg-primary hover:text-black transition-colors font-bold"}
|
||||||
|
href={"/brand/news"}>Больше новостей</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{newsData.data &&
|
||||||
|
<Swiper
|
||||||
|
breakpoints={
|
||||||
|
{
|
||||||
|
320: {
|
||||||
|
slidesPerView: 1,
|
||||||
|
spaceBetween: 10
|
||||||
|
},
|
||||||
|
1024: {
|
||||||
|
slidesPerView: 3,
|
||||||
|
spaceBetween: 30
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
// slidesPerView={3}
|
||||||
|
spaceBetween={20}
|
||||||
|
className={"[&_.swiper-button-next]:after:text-primary [&_.swiper-button-next]:after:content-['next'] [&_.swiper-button-prev]:after:text-primary [&_.swiper-button-prev]:after:content-['prev'] min-h-[400px] "}
|
||||||
|
modules={[Navigation, Pagination, Autoplay]}
|
||||||
|
speed={500}
|
||||||
|
|
||||||
|
autoplay={
|
||||||
|
{
|
||||||
|
delay: 5000,
|
||||||
|
disableOnInteraction: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
navigation={
|
||||||
|
{}
|
||||||
|
}
|
||||||
|
pagination={{clickable: true}}
|
||||||
|
|
||||||
|
>
|
||||||
|
{newsData.data.data && newsData.data.data.map((news) => (
|
||||||
|
<SwiperSlide key={news.id}>
|
||||||
|
<NewsCard {...news} />
|
||||||
|
</SwiperSlide>
|
||||||
|
))}
|
||||||
|
|
||||||
|
</Swiper>}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const ArticlesSlider = () => {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Hero/>
|
<Hero/>
|
||||||
<MainInfo/>
|
<MainInfo/>
|
||||||
<Achievements/>
|
<Achievements/>
|
||||||
|
<NewsSlider/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -163,8 +163,8 @@ const MakeOrder = () => {
|
||||||
return (
|
return (
|
||||||
<Wrapper title={"Офромление заказа"}
|
<Wrapper title={"Офромление заказа"}
|
||||||
breadcrumbs={[{name: "Корзина", link: "/cart"}, {name: "Оформление заказа", link: "/order/make"}]}>
|
breadcrumbs={[{name: "Корзина", link: "/cart"}, {name: "Оформление заказа", link: "/order/make"}]}>
|
||||||
<div className="flex w-full pb-16 justify-between">
|
<div className="grid grid-cols-10 gap-5 w-full pb-16 justify-between">
|
||||||
<form id={formId} className={"text-[#151515] [&>div]:mb-5 w-8/12"} onSubmit={handleSubmit(submitForm)}>
|
<form id={formId} className={"text-[#151515] [&>div]:mb-5 col-span-10 xl:col-span-7"} onSubmit={handleSubmit(submitForm)}>
|
||||||
<div className="form__group bg-gray-card p-7 rounded-[30px]">
|
<div className="form__group bg-gray-card p-7 rounded-[30px]">
|
||||||
<h2 className={"mb-5 font-bold text-subtitle-2"}>Покупатель</h2>
|
<h2 className={"mb-5 font-bold text-subtitle-2"}>Покупатель</h2>
|
||||||
|
|
||||||
|
@ -340,7 +340,7 @@ const MakeOrder = () => {
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<div className="flex flex-col w-[30%]">
|
<div className="flex flex-col col-span-10 xl:col-span-3">
|
||||||
<OrderInfo setIsDisabled={setIsDisabled} isDisabled={isDisabled}/>
|
<OrderInfo setIsDisabled={setIsDisabled} isDisabled={isDisabled}/>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<Button type={"submit"} form={formId} color={"primary"}
|
<Button type={"submit"} form={formId} color={"primary"}
|
||||||
|
|
|
@ -1,22 +1,5 @@
|
||||||
import axios, {AxiosInstance} from "axios";
|
import axios, {AxiosInstance} from "axios";
|
||||||
import {ResponseData} from "@/pages/api/v1/catalog";
|
import {CartItem, Filter, Fuser, News, Product, Wrapper} from "@/service/types/local";
|
||||||
|
|
||||||
|
|
||||||
type createFUserType = {
|
|
||||||
fuserId: number
|
|
||||||
}
|
|
||||||
|
|
||||||
type getCartItemsType = {
|
|
||||||
id: number,
|
|
||||||
code: string,
|
|
||||||
name: string,
|
|
||||||
is_active: number,
|
|
||||||
properties: {
|
|
||||||
[key: string]: string
|
|
||||||
}
|
|
||||||
quantity: number,
|
|
||||||
available_quantity: number
|
|
||||||
}
|
|
||||||
|
|
||||||
class LocalAPI {
|
class LocalAPI {
|
||||||
private instance: AxiosInstance;
|
private instance: AxiosInstance;
|
||||||
|
@ -27,37 +10,13 @@ class LocalAPI {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async getCatalogItemsCount(){
|
|
||||||
const {data} = await this.instance.get<{status: number, info: string}>('/api/v1/catalog/count')
|
|
||||||
return +data.info
|
|
||||||
}
|
|
||||||
|
|
||||||
async getCatalogItems(filters: { [key: string]: string[] }, page: number = 1) {
|
async getCatalogItems(filters: { [key: string]: string[] }, page: number = 1) {
|
||||||
const dataFilters: {[key: string]: string} = {}
|
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(filters)) {
|
const {data} = await this.instance.get<Wrapper<Product[]>>('/api/v1/catalog', {
|
||||||
if (value.length === 0) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
dataFilters[key] = value.join(',')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Object.keys(dataFilters).length === 0) {
|
|
||||||
const {data} = await this.instance.get('/api/v1/catalog', {
|
|
||||||
params: {
|
params: {
|
||||||
limit: 10,
|
limit: 10,
|
||||||
page,
|
page,
|
||||||
isFilter: 0,
|
...filters
|
||||||
}
|
|
||||||
})
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
const {data} = await this.instance.get('/api/v1/catalog', {
|
|
||||||
params: {
|
|
||||||
limit: 10,
|
|
||||||
page,
|
|
||||||
isFilter: 1,
|
|
||||||
...dataFilters
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -65,36 +24,34 @@ class LocalAPI {
|
||||||
}
|
}
|
||||||
|
|
||||||
async getFilters() {
|
async getFilters() {
|
||||||
const {data} = await this.instance.get("/api/v1/catalog/filters")
|
const {data} = await this.instance.get<Wrapper<Filter[]>>("/api/v1/catalog/filters")
|
||||||
return data
|
return data.data
|
||||||
}
|
}
|
||||||
|
|
||||||
async getCatalogItemByCode(code: string) {
|
async getCatalogItemByCode(code: string) {
|
||||||
const {data} = await this.instance.get<ResponseData>(`/api/v1/catalog/${code}`)
|
const {data} = await this.instance.get<Wrapper<Product>>(`/api/v1/catalog/${code}`)
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
async getFuserId() {
|
async getFuserId() {
|
||||||
let fuserId: string | number | null = localStorage.getItem('fuserId')
|
let fuserId: string | number | null = localStorage.getItem('fuserId')
|
||||||
|
if (fuserId === null || !Number.isInteger(+fuserId)) {
|
||||||
if (!fuserId) {
|
const data = await this.createFUser()
|
||||||
fuserId = (await this.createFUser()).fuserId
|
fuserId = data.fuserId
|
||||||
localStorage.setItem('fuserId', JSON.stringify(fuserId))
|
localStorage.setItem('fuserId', JSON.stringify(fuserId))
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("current fuserId", fuserId)
|
|
||||||
return +fuserId
|
return +fuserId
|
||||||
}
|
}
|
||||||
|
|
||||||
async createFUser() {
|
async createFUser() {
|
||||||
const {data} = await this.instance.post<createFUserType>('/api/v1/cart')
|
const {data} = await this.instance.post<Wrapper<{fuser: Fuser, fuserId:number}>>('/api/v1/cart')
|
||||||
console.log("Fuser id is", data)
|
console.log("Fuser id is", data)
|
||||||
return data
|
return data.data!!
|
||||||
}
|
}
|
||||||
|
|
||||||
async getCartItems() {
|
async getCartItems() {
|
||||||
|
|
||||||
const {data} = await this.instance.get<getCartItemsType[]>('/api/v1/cart', {
|
const {data} = await this.instance.get<Wrapper<CartItem[]>>('/api/v1/cart', {
|
||||||
params: {
|
params: {
|
||||||
fuserId: await this.getFuserId()}
|
fuserId: await this.getFuserId()}
|
||||||
})
|
})
|
||||||
|
@ -123,7 +80,11 @@ class LocalAPI {
|
||||||
}
|
}
|
||||||
|
|
||||||
async totalProductPrice() {
|
async totalProductPrice() {
|
||||||
const {data} = await this.instance.post('/api/v1/order/total', {fuserId: await this.getFuserId()})
|
const {data} = await this.instance.post<Wrapper<{total: number}>>('/api/v1/order/total', {fuserId: await this.getFuserId()})
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
async fetchNews() {
|
||||||
|
const {data} = await this.instance.get<Wrapper<News[]>>("/api/v1/news")
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,98 @@
|
||||||
|
export interface Wrapper<T> {
|
||||||
|
status: string
|
||||||
|
data: T | null
|
||||||
|
meta: Meta
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CartItem {
|
||||||
|
id: number
|
||||||
|
fuserId: number
|
||||||
|
productId: number
|
||||||
|
priceTypeId: number
|
||||||
|
quantity: number
|
||||||
|
fuser: Fuser
|
||||||
|
product: Product
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Fuser {
|
||||||
|
id: number
|
||||||
|
code: string
|
||||||
|
userId: number
|
||||||
|
dateInserted: string
|
||||||
|
dateUpdated: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Product {
|
||||||
|
id: number
|
||||||
|
code: string
|
||||||
|
name: string
|
||||||
|
isActive: boolean
|
||||||
|
properties: Properties
|
||||||
|
detailText: string
|
||||||
|
price: Price
|
||||||
|
availableQuantity: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Properties {
|
||||||
|
acea?: string;
|
||||||
|
width?: string;
|
||||||
|
height?: string;
|
||||||
|
length?: string;
|
||||||
|
volume?: string;
|
||||||
|
weight?: string;
|
||||||
|
mileage?: string;
|
||||||
|
box_type?: string;
|
||||||
|
category?: string;
|
||||||
|
oil_type?: string;
|
||||||
|
documents?: string[];
|
||||||
|
use_areas?: string;
|
||||||
|
viscosity?: string;
|
||||||
|
acid_index?: string;
|
||||||
|
main_image?: string[];
|
||||||
|
pour_point?: string;
|
||||||
|
flash_point?: string;
|
||||||
|
subcategory?: string;
|
||||||
|
vendor_code?: string;
|
||||||
|
api_standart?: string;
|
||||||
|
requirements?: string;
|
||||||
|
viscosity_index?: string;
|
||||||
|
viscosity_kinematic?: string;
|
||||||
|
tribological_properties?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Price {
|
||||||
|
BASE: number
|
||||||
|
[key: string]: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Filter {
|
||||||
|
id: number
|
||||||
|
code: string
|
||||||
|
name: string
|
||||||
|
values: Value[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Value {
|
||||||
|
id: number
|
||||||
|
value: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Meta {
|
||||||
|
count?: number
|
||||||
|
page?: number
|
||||||
|
limit? : number
|
||||||
|
requestStarted: number
|
||||||
|
requestFinished: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface News {
|
||||||
|
id: number
|
||||||
|
isActive: boolean
|
||||||
|
sort: number
|
||||||
|
name: string
|
||||||
|
content: string
|
||||||
|
code: string
|
||||||
|
picture: string
|
||||||
|
date: string
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue