Commit latest changes
parent
b94292b479
commit
3ef29adde7
|
@ -0,0 +1,6 @@
|
||||||
|
/** @type {import('next-sitemap').IConfig} */
|
||||||
|
module.exports = {
|
||||||
|
siteUrl: 'https://relynolli.ru',
|
||||||
|
generateRobotsTxt: true, //
|
||||||
|
// ...other options
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
output: "standalone",
|
output: "standalone",
|
||||||
reactStrictMode: true,
|
reactStrictMode: false,
|
||||||
images: {
|
images: {
|
||||||
remotePatterns: [
|
remotePatterns: [
|
||||||
{
|
{
|
||||||
|
@ -17,32 +17,32 @@ const nextConfig = {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
webpack(config) {
|
webpack(config) {
|
||||||
const fileLoaderRule = config.module.rules.find((rule) =>
|
const fileLoaderRule = config.module.rules.find((rule) =>
|
||||||
rule.test?.test?.('.svg'),
|
rule.test?.test?.('.svg'),
|
||||||
)
|
)
|
||||||
|
|
||||||
config.module.rules.push(
|
config.module.rules.push(
|
||||||
// Reapply the existing rule, but only for svg imports ending in ?url
|
// Reapply the existing rule, but only for svg imports ending in ?url
|
||||||
{
|
{
|
||||||
...fileLoaderRule,
|
...fileLoaderRule,
|
||||||
test: /\.svg$/i,
|
test: /\.svg$/i,
|
||||||
resourceQuery: /url/, // *.svg?url
|
resourceQuery: /url/, // *.svg?url
|
||||||
},
|
},
|
||||||
// Convert all other *.svg imports to React components
|
// Convert all other *.svg imports to React components
|
||||||
{
|
{
|
||||||
test: /\.svg$/i,
|
test: /\.svg$/i,
|
||||||
issuer: fileLoaderRule.issuer,
|
issuer: fileLoaderRule.issuer,
|
||||||
resourceQuery: { not: [...fileLoaderRule.resourceQuery.not, /url/] }, // exclude if *.svg?url
|
resourceQuery: {not: [...fileLoaderRule.resourceQuery.not, /url/]}, // exclude if *.svg?url
|
||||||
use: ['@svgr/webpack'],
|
use: ['@svgr/webpack'],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
// Modify the file loader rule to ignore *.svg, since we have it handled now.
|
|
||||||
fileLoaderRule.exclude = /\.svg$/i
|
|
||||||
|
|
||||||
return config
|
// Modify the file loader rule to ignore *.svg, since we have it handled now.
|
||||||
}
|
fileLoaderRule.exclude = /\.svg$/
|
||||||
|
return config
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default nextConfig;
|
export default nextConfig;
|
||||||
|
|
12
package.json
12
package.json
|
@ -6,12 +6,18 @@
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint"
|
"lint": "next lint",
|
||||||
|
"postbuild": "next-sitemap"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@cdek-it/widget": "3",
|
||||||
"@nextui-org/react": "^2.2.9",
|
"@nextui-org/react": "^2.2.9",
|
||||||
|
"@pbe/react-yandex-maps": "^1.2.5",
|
||||||
"@tanstack/react-query": "^5.20.5",
|
"@tanstack/react-query": "^5.20.5",
|
||||||
"@tanstack/react-query-devtools": "^5.21.7",
|
"@tanstack/react-query-devtools": "^5.21.7",
|
||||||
|
"@types/lodash": "^4.17.0",
|
||||||
|
"@types/yandex-maps": "^2.1.35",
|
||||||
|
"@uidotdev/usehooks": "^2.4.1",
|
||||||
"axios": "^1.6.7",
|
"axios": "^1.6.7",
|
||||||
"framer-motion": "^11.0.3",
|
"framer-motion": "^11.0.3",
|
||||||
"libphonenumber-js": "^1.10.56",
|
"libphonenumber-js": "^1.10.56",
|
||||||
|
@ -19,11 +25,14 @@
|
||||||
"mysql2": "^3.9.1",
|
"mysql2": "^3.9.1",
|
||||||
"nanoid": "^5.0.5",
|
"nanoid": "^5.0.5",
|
||||||
"next": "14.1.0",
|
"next": "14.1.0",
|
||||||
|
"next-sitemap": "^4.2.3",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
"react-hook-form": "^7.50.1",
|
"react-hook-form": "^7.50.1",
|
||||||
"react-image": "^4.1.0",
|
"react-image": "^4.1.0",
|
||||||
"react-imask": "^7.5.0",
|
"react-imask": "^7.5.0",
|
||||||
|
"react-stately": "^3.30.1",
|
||||||
|
"react-ymaps3": "^0.0.19",
|
||||||
"swiper": "^11.0.7",
|
"swiper": "^11.0.7",
|
||||||
"valtio": "^1.13.0",
|
"valtio": "^1.13.0",
|
||||||
"zod": "^3.22.4"
|
"zod": "^3.22.4"
|
||||||
|
@ -34,6 +43,7 @@
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^18",
|
"@types/react": "^18",
|
||||||
"@types/react-dom": "^18",
|
"@types/react-dom": "^18",
|
||||||
|
"@yandex/ymaps3-types": "^0.0.24",
|
||||||
"autoprefixer": "^10.0.1",
|
"autoprefixer": "^10.0.1",
|
||||||
"eslint": "^8",
|
"eslint": "^8",
|
||||||
"eslint-config-next": "14.1.0",
|
"eslint-config-next": "14.1.0",
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
<svg width="40" height="40" viewBox="0 0 40 40" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect width="40" height="40" rx="8" fill-opacity="0.7"/>
|
||||||
|
<g clip-path="url(#clip0_948_14736)">
|
||||||
|
<path d="M31.4985 14.1546C31.2225 13.1096 30.4092 12.2868 29.3765 12.0075C27.505 11.5 20 11.5 20 11.5C20 11.5 12.4949 11.5 10.6234 12.0075C9.59068 12.2868 8.77741 13.1096 8.50141 14.1546C8 16.0485 8 20 8 20C8 20 8 23.9514 8.50141 25.8454C8.77741 26.8903 9.59068 27.7132 10.6234 27.9926C12.4949 28.5 20 28.5 20 28.5C20 28.5 27.505 28.5 29.3765 27.9926C30.4092 27.7132 31.2225 26.8903 31.4985 25.8454C32 23.9514 32 20 32 20C32 20 32 16.0485 31.4985 14.1546Z" />
|
||||||
|
<path class="gray" d="M17.709 24.0737V16.625L23.629 20.3495L17.709 24.0737Z"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_948_14736">
|
||||||
|
<rect width="24" height="17" fill="white" transform="translate(8 11.5)"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 864 B |
|
@ -0,0 +1,9 @@
|
||||||
|
# *
|
||||||
|
User-agent: *
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
# Host
|
||||||
|
Host: https://relynolli.ru
|
||||||
|
|
||||||
|
# Sitemaps
|
||||||
|
Sitemap: https://relynolli.ru/sitemap.xml
|
|
@ -0,0 +1,36 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:mobile="http://www.google.com/schemas/sitemap-mobile/1.0" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1">
|
||||||
|
<url><loc>https://relynolli.ru</loc><lastmod>2024-04-15T14:24:16.500Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||||
|
<url><loc>https://relynolli.ru/articles</loc><lastmod>2024-04-15T14:24:16.500Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||||
|
<url><loc>https://relynolli.ru/cart</loc><lastmod>2024-04-15T14:24:16.500Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||||
|
<url><loc>https://relynolli.ru/catalog</loc><lastmod>2024-04-15T14:24:16.500Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||||
|
<url><loc>https://relynolli.ru/contact</loc><lastmod>2024-04-15T14:24:16.500Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||||
|
<url><loc>https://relynolli.ru/news</loc><lastmod>2024-04-15T14:24:16.500Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||||
|
<url><loc>https://relynolli.ru/order/make</loc><lastmod>2024-04-15T14:24:16.500Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||||
|
<url><loc>https://relynolli.ru/catalog/maslo-motornoe-relynolli-premium-m1-lh-10w-40-sn-cf-1l-kanistra-acea-a5-b5</loc><lastmod>2024-04-15T14:24:16.500Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||||
|
<url><loc>https://relynolli.ru/catalog/maslo-motornoe-relynolli-premium-m1-lh-10w-40-sn-cf-4l-kanistra-acea-a5-b5</loc><lastmod>2024-04-15T14:24:16.500Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||||
|
<url><loc>https://relynolli.ru/catalog/maslo-motornoe-relynolli-premium-m1-nh-5w-30-sn-cf-1l-kanistra-acea-a5-b5</loc><lastmod>2024-04-15T14:24:16.500Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||||
|
<url><loc>https://relynolli.ru/catalog/maslo-motornoe-relynolli-premium-m1-nh-5w-30-sn-cf-205l-bochka-acea-a5-b5</loc><lastmod>2024-04-15T14:24:16.500Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||||
|
<url><loc>https://relynolli.ru/catalog/maslo-motornoe-relynolli-premium-m1-nh-5w-30-sn-cf-4l-kanistra-acea-a5-b5</loc><lastmod>2024-04-15T14:24:16.500Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||||
|
<url><loc>https://relynolli.ru/catalog/maslo-motornoe-relynolli-premium-m1-nh-5w-40-sn-cf-1l-kanistra-acea-a5-b5</loc><lastmod>2024-04-15T14:24:16.500Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||||
|
<url><loc>https://relynolli.ru/catalog/maslo-motornoe-relynolli-premium-m1-nh-5w-40-sn-cf-205l-bochka-acea-a5-b5</loc><lastmod>2024-04-15T14:24:16.500Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||||
|
<url><loc>https://relynolli.ru/catalog/maslo-motornoe-relynolli-premium-m1-nh-5w-40-sn-cf-4l-kanistra-acea-a5-b5</loc><lastmod>2024-04-15T14:24:16.500Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||||
|
<url><loc>https://relynolli.ru/catalog/maslo-motornoe-relynolli-standart-m1-lh-10w-40-slcf-1l-kanistra-acea-a3b4</loc><lastmod>2024-04-15T14:24:16.500Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||||
|
<url><loc>https://relynolli.ru/catalog/maslo-motornoe-relynolli-standart-m1-lh-10w-40-slcf-4l-kanistra-acea-a3b4</loc><lastmod>2024-04-15T14:24:16.500Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||||
|
<url><loc>https://relynolli.ru/articles/poyasnenie-po-sootvetstviyu-masel-relynolli-klassu-acea-a5-b5</loc><lastmod>2024-04-15T14:24:16.500Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||||
|
<url><loc>https://relynolli.ru/articles/professionalnoe-retsenzirovanie-dissertatsii-ot-ntts-td-tekhnokhim-grupp</loc><lastmod>2024-04-15T14:24:16.500Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||||
|
<url><loc>https://relynolli.ru/articles/tsifrovizatsiya-sklada-otgruzki-bez-oshibok-relynolli</loc><lastmod>2024-04-15T14:24:16.500Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||||
|
<url><loc>https://relynolli.ru/articles/realnye-ispytaniya-masla-ili-fokus-pokus</loc><lastmod>2024-04-15T14:24:16.500Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||||
|
<url><loc>https://relynolli.ru/articles/nauchno-tekhnicheskiy-tsentr-vozmozhnosti-i-perspektivy</loc><lastmod>2024-04-15T14:24:16.500Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||||
|
<url><loc>https://relynolli.ru/articles/skhema-ispytaniy-relynolli-garantiya-nadezhnosti-i-dostovernosti</loc><lastmod>2024-04-15T14:24:16.500Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||||
|
<url><loc>https://relynolli.ru/news/meniaj_besplatno</loc><lastmod>2024-04-15T14:24:16.500Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||||
|
<url><loc>https://relynolli.ru/news/tv-reportazh-relynolli</loc><lastmod>2024-04-15T14:24:16.500Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||||
|
<url><loc>https://relynolli.ru/news/motornoe-maslo-relynolli-v-podarok</loc><lastmod>2024-04-15T14:24:16.500Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||||
|
<url><loc>https://relynolli.ru/news/samarskij-politeh-relynolli</loc><lastmod>2024-04-15T14:24:16.500Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||||
|
<url><loc>https://relynolli.ru/news/start-sotrudnichestva-service-trans-cargo-tehnohim-relynolli</loc><lastmod>2024-04-15T14:24:16.500Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||||
|
<url><loc>https://relynolli.ru/news/tehnohim-yarmarka-vakanciy</loc><lastmod>2024-04-15T14:24:16.500Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||||
|
<url><loc>https://relynolli.ru/news/skidka-10-dlya-podpischikov-kanala-telegram</loc><lastmod>2024-04-15T14:24:16.500Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||||
|
<url><loc>https://relynolli.ru/news/kazautoexpo-2023-relynolli</loc><lastmod>2024-04-15T14:24:16.500Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||||
|
<url><loc>https://relynolli.ru/news/relynolli-magazin-roznica</loc><lastmod>2024-04-15T14:24:16.500Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||||
|
<url><loc>https://relynolli.ru/news/bisness-yaroslavii</loc><lastmod>2024-04-15T14:24:16.500Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||||
|
</urlset>
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||||
|
<sitemap><loc>https://relynolli.ru/sitemap-0.xml</loc></sitemap>
|
||||||
|
</sitemapindex>
|
|
@ -3,18 +3,28 @@ import {Button, Checkbox} from "@nextui-org/react";
|
||||||
import {ChevronRightIcon} from "@nextui-org/shared-icons";
|
import {ChevronRightIcon} from "@nextui-org/shared-icons";
|
||||||
import {useQuery} from "@tanstack/react-query";
|
import {useQuery} from "@tanstack/react-query";
|
||||||
import LocalAPI from "@/service/localAPI";
|
import LocalAPI from "@/service/localAPI";
|
||||||
|
import {useState} from "react";
|
||||||
|
import _ from 'lodash';
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
type OrderInfoProps = {
|
type OrderInfoProps = {
|
||||||
setIsDisabled: (value: boolean) => void
|
setIsDisabled: (value: boolean) => void
|
||||||
isDisabled: boolean
|
isDisabled: boolean
|
||||||
}
|
}
|
||||||
const OrderInfo = (props: OrderInfoProps) => {
|
const OrderInfo = (props: OrderInfoProps) => {
|
||||||
|
|
||||||
|
const [coupon, setCoupon] = useState("");
|
||||||
|
const [couponApplied, setCouponApplied] = useState(false)
|
||||||
|
|
||||||
|
|
||||||
const totalProductPriceQs = useQuery(
|
const totalProductPriceQs = useQuery(
|
||||||
{
|
{
|
||||||
queryKey: ['totalProductPrice'], queryFn: async () => {
|
queryKey: ['totalProductPrice', couponApplied, coupon], queryFn: async () => {
|
||||||
const service = new LocalAPI()
|
const service = new LocalAPI()
|
||||||
return await service.totalProductPrice()
|
if (couponApplied) {
|
||||||
|
return await service.totalProductPrice(coupon)
|
||||||
|
}
|
||||||
|
return await service.totalProductPrice(undefined)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -23,20 +33,19 @@ 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 ? totalProductPriceQs.data.data!.total : 0).replace(/\B(?=(\d{3})+(?!\d))/g, " ")} ₽</span>
|
{/* TODO round value */}
|
||||||
|
{/*<span>{String(totalProductPriceQs.data ? _.sum(totalProductPriceQs.data.data!.items.map(item => item.cart.product.price.BASE * item.cart.quantity)) : 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 isDisabled={totalProductPriceQs.data && couponApplied && _.some(totalProductPriceQs.data.data!.items, "discount")} isInvalid={totalProductPriceQs.data && couponApplied && _.every(totalProductPriceQs.data.data!.items, ["discount", null])} type="text" label="Введите промокод" variant={"bordered"}
|
||||||
className={"border-[#1E1E1E] mr-4"}/>
|
className={"border-[#1E1E1E] mr-4"} onChange={(e) => setCoupon(e.target.value)}/>
|
||||||
<Button className={"flex-auto h-full"}
|
<Button isDisabled={couponApplied} className={"flex-auto h-full"}
|
||||||
color={'primary'}><ChevronRightIcon/></Button>
|
color={'primary'} onClick={() => setCouponApplied(true)}><ChevronRightIcon/></Button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{/*TODO calculate discount*/}
|
|
||||||
|
|
||||||
<div className="flex justify-between mb-2 border-b-1 border-b-black-1 pb-4">
|
<div className="flex justify-between mb-2 border-b-1 border-b-black-1 pb-4">
|
||||||
<span>Скидка: </span>
|
<span>Скидка: </span>
|
||||||
<span>0 ₽</span>
|
{/*<span>{totalProductPriceQs.data ? _.sum(totalProductPriceQs.data.data!.items.map(item => item.cart.product.price.BASE * item.cart.quantity)) - totalProductPriceQs.data.data!.total : 0} ₽</span>*/}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/*TODO calculate discount + shipment*/}
|
{/*TODO calculate discount + shipment*/}
|
||||||
|
@ -55,7 +64,7 @@ const OrderInfo = (props: OrderInfoProps) => {
|
||||||
props.setIsDisabled(!props.isDisabled)
|
props.setIsDisabled(!props.isDisabled)
|
||||||
}}
|
}}
|
||||||
className={"[&_span:last-child]:!text-[#808080] [&_span:last-child]:text-subtitle-5 [&_span:last-child]:leading-normal mb-6"}>Нажимая
|
className={"[&_span:last-child]:!text-[#808080] [&_span:last-child]:text-subtitle-5 [&_span:last-child]:leading-normal mb-6"}>Нажимая
|
||||||
кнопку «Оформить заказ», я даю согласие на обработку моих персональных данных</Checkbox>
|
кнопку «Оформить заказ», я даю согласие на <Link className={"text-primary"} href={"https://tehnohimgrupp.ru/upload/Согласие на ОПД Технохим.pdf"}>обработку моих персональных данных</Link></Checkbox>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
import {Autocomplete, AutocompleteItem, cn} from "@nextui-org/react";
|
||||||
|
import {useDebounce} from "@uidotdev/usehooks";
|
||||||
|
import localAPI from "@/service/localAPI";
|
||||||
|
import {Controller} from "react-hook-form";
|
||||||
|
import {InputPropsType} from "@/components/pages/order/types";
|
||||||
|
import {useQuery} from "@tanstack/react-query";
|
||||||
|
import _ from "lodash";
|
||||||
|
|
||||||
|
const AddressInput = (props: InputPropsType) => {
|
||||||
|
const address = props.watch("address")
|
||||||
|
const debouncedAddress = useDebounce(address, 500)
|
||||||
|
|
||||||
|
const addressInfo = useQuery({
|
||||||
|
queryKey: ["address_info", debouncedAddress],
|
||||||
|
queryFn: async () => {
|
||||||
|
const service = new localAPI()
|
||||||
|
return await service.fetchAddresses(debouncedAddress)
|
||||||
|
},
|
||||||
|
staleTime: 1000
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Controller render={ ({field}) =>
|
||||||
|
<Autocomplete className={"col-span-2"}
|
||||||
|
label={"Адрес"}
|
||||||
|
labelPlacement={"outside"}
|
||||||
|
variant={"bordered"} isRequired
|
||||||
|
inputProps={{
|
||||||
|
classNames: {
|
||||||
|
inputWrapper: cn("h-[65px]"),
|
||||||
|
label: cn("group[data-filled-within=true] group-data-[filled-within=true]:-translate-y-[60px] group-data-[filled-within=true]:text-[#8F8F8F]"),
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
inputValue={field.value}
|
||||||
|
isLoading={addressInfo.isLoading}
|
||||||
|
onInputChange={(value) => {
|
||||||
|
props.setValue("address", value)
|
||||||
|
const obj = _.find(addressInfo.data, (p1) => p1.metaDataProperty.GeocoderMetaData.Address.formatted === value)
|
||||||
|
if (typeof obj !== "undefined") {
|
||||||
|
props.setValue("lat", +(obj.Point.pos.split(' ')[1]))
|
||||||
|
props.setValue("lon", +( obj.Point.pos.split(' ')[0]))
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
items={addressInfo.data || []}
|
||||||
|
>
|
||||||
|
{ item => (
|
||||||
|
<AutocompleteItem key={item.metaDataProperty.GeocoderMetaData.Address.formatted} value={item.metaDataProperty.GeocoderMetaData.Address.formatted} classNames={{
|
||||||
|
title: cn("text-[#8F8F8F] data-[selected=true]:text-[#151515]"),
|
||||||
|
}}>
|
||||||
|
{item.metaDataProperty.GeocoderMetaData.Address.formatted}
|
||||||
|
</AutocompleteItem>
|
||||||
|
)}
|
||||||
|
</Autocomplete>
|
||||||
|
} name={"address"} control={props.control} rules={{required: "Поле обязательно для заполнения"}} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default AddressInput
|
|
@ -0,0 +1,165 @@
|
||||||
|
import {Dispatch, SetStateAction, useEffect, useRef, useState} from "react"
|
||||||
|
import Script from "next/script";
|
||||||
|
import {YMap, type YMapLocationRequest} from 'ymaps3';
|
||||||
|
import {Spinner, Tooltip} from "@nextui-org/react";
|
||||||
|
import {InputPropsType} from "@/components/pages/order/types";
|
||||||
|
import {useQuery} from "@tanstack/react-query";
|
||||||
|
import LocalAPI from "@/service/localAPI";
|
||||||
|
import {SdekPoint} from "@/service/types/local";
|
||||||
|
|
||||||
|
|
||||||
|
type GeoObject = {
|
||||||
|
lat: number,
|
||||||
|
lon: number,
|
||||||
|
name: string,
|
||||||
|
description: string
|
||||||
|
pvzId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type SdekMapProps = {
|
||||||
|
objects: GeoObject[],
|
||||||
|
center: [number, number],
|
||||||
|
zoom?: number,
|
||||||
|
} & Omit<InputPropsType, 'control' | 'errors'>
|
||||||
|
|
||||||
|
const initMap = async (ref: React.RefObject<HTMLDivElement>,
|
||||||
|
setMapIsLoaded: Dispatch<SetStateAction<boolean>>,
|
||||||
|
setMap: Dispatch<SetStateAction<YMap | null>>
|
||||||
|
) => {
|
||||||
|
|
||||||
|
await ymaps3.ready;
|
||||||
|
setMapIsLoaded(true)
|
||||||
|
|
||||||
|
const LOCATION: YMapLocationRequest = {
|
||||||
|
center: [37.623082, 55.75254],
|
||||||
|
zoom: 9
|
||||||
|
};
|
||||||
|
|
||||||
|
const {YMap, YMapDefaultSchemeLayer, YMapMarker} = ymaps3;
|
||||||
|
if (!ref.current) return
|
||||||
|
|
||||||
|
const map = new YMap(ref.current, {location: LOCATION});
|
||||||
|
|
||||||
|
map.addChild(new YMapDefaultSchemeLayer({}))
|
||||||
|
.addChild(new ymaps3.YMapDefaultFeaturesLayer({zIndex: 1800}))
|
||||||
|
setMap(map)
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const SdekElem = ({map, object, watch, setValue}: { map: YMap | null, object: SdekPoint } & Omit<InputPropsType, 'control' | 'errors'>) => {
|
||||||
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
const selectedPvzId = watch("pvzId")
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!map || !ref.current) return
|
||||||
|
const marker = new ymaps3.YMapMarker({
|
||||||
|
coordinates: [object.location.longitude, object.location.latitude],
|
||||||
|
draggable: false,
|
||||||
|
}, ref.current)
|
||||||
|
|
||||||
|
map.addChild(marker)
|
||||||
|
|
||||||
|
}, [map, ref.current]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip placement={"bottom-end"} content={
|
||||||
|
<div className="px-1 py-2 text-black">
|
||||||
|
<div className="text-small font-bold">{object.name}</div>
|
||||||
|
<div className="text-tiny">{object.note}</div>
|
||||||
|
</div>
|
||||||
|
}>
|
||||||
|
<div ref={ref} className={"relative h-[1px] w-[1px]"} onClick={() => setValue("pvzId", object.code)}>
|
||||||
|
<CDEKLogo isSelected={object.code === selectedPvzId}/>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const CDEKLogo = ({isSelected}: { isSelected: boolean }) => (
|
||||||
|
<svg className={"absolute top-[-85px] left-[-30px]"} width="76" height="85" viewBox="0 0 76 85" fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M38.2221 85L12.0489 45.3333L64.3953 45.3333L38.2221 85Z" fill="#D9D9D9"/>
|
||||||
|
<g filter="url(#filter0_d_1020_18429)">
|
||||||
|
<circle cx="38.2222" cy="30.2222" r="30.2222" className={isSelected ? "fill-primary" : "fill-white"}/>
|
||||||
|
</g>
|
||||||
|
<g clip-path="url(#clip0_1020_18429)">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||||
|
d="M22.6584 34.0822H20.7036C17.6541 34.0822 19.8696 27.6053 22.4369 27.6053H25.5255C26.0207 27.6053 26.8939 27.6966 27.3239 26.4715L27.9885 24.5688H23.7531C21.4595 24.5688 19.6741 25.3768 18.3839 26.7322C16.1554 29.0519 15.3996 32.6748 15.9339 34.408C16.4422 36.011 17.8236 37.0666 19.9869 37.0927L21.668 37.1057H23.7401L24.2484 35.5809C24.6263 34.4993 23.7792 34.0822 22.6584 34.0822ZM46.455 31.3976L47.0934 29.3255H40.2778C39.144 29.3255 38.6357 29.6383 38.4403 30.2899L37.8017 32.362H44.6174C45.7512 32.362 46.2595 32.0492 46.455 31.3976ZM36.7852 35.0466L36.1466 37.1187H42.9624C44.0831 37.1187 44.6044 36.8059 44.7999 36.1543L45.4385 34.0822H38.6227C37.5019 34.0822 36.9937 34.395 36.7852 35.0466ZM48.0448 26.654L48.6836 24.5819H41.8677C40.7339 24.5819 40.2256 24.8946 40.0302 25.5463L39.3916 27.6183H46.2074C47.3282 27.6183 47.8363 27.3056 48.0448 26.654ZM37.7496 27.071C37.4628 25.1423 36.4333 24.5819 33.9311 24.5819H29.37L26.7114 32.362H28.3925C29.396 32.362 29.9043 32.375 30.4386 30.9415L31.5463 27.6053H33.2405C34.687 27.6053 34.3612 29.4168 33.6314 31.1761C32.9798 32.7269 31.846 34.0953 30.4777 34.0953H27.6497C26.5159 34.0953 25.9947 34.408 25.7861 35.0597L25.0824 37.1317H27.1545L29.1875 37.1187C30.9859 37.1057 32.4585 36.9753 34.1788 35.4245C36.0033 33.7695 38.1144 29.521 37.7496 27.071ZM60.66 24.5688H56.7113L52.9971 28.5176C52.5669 28.9737 52.1239 29.4298 51.6937 29.9641H51.6546L53.5051 24.5688H50.2865L45.9467 37.1187H49.1657L50.5469 33.17L51.9804 31.958L53.1143 35.5288C53.4661 36.6365 53.8312 37.1187 54.6132 37.1187H57.0762L54.5478 30.1075L60.66 24.5688Z"
|
||||||
|
fill="#1AB248"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<filter id="filter0_d_1020_18429" x="0.444445" y="0" width="75.5554" height="75.5554"
|
||||||
|
filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||||
|
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||||
|
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||||
|
result="hardAlpha"/>
|
||||||
|
<feOffset dy="7.55556"/>
|
||||||
|
<feGaussianBlur stdDeviation="3.77778"/>
|
||||||
|
<feComposite in2="hardAlpha" operator="out"/>
|
||||||
|
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
|
||||||
|
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1020_18429"/>
|
||||||
|
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1020_18429" result="shape"/>
|
||||||
|
</filter>
|
||||||
|
<clipPath id="clip0_1020_18429">
|
||||||
|
<rect width="45.3333" height="12.8184" fill="white" transform="translate(15.5557 24.5557)"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
const CdekMap = (props: SdekMapProps) => {
|
||||||
|
const ref = useRef(null);
|
||||||
|
const [mapIsLoaded, setMapIsLoaded] = useState<boolean>(false)
|
||||||
|
const [mapInstance, setMapInstance] = useState<YMap | null>(null)
|
||||||
|
|
||||||
|
const lat = props.watch("lat")
|
||||||
|
const lon = props.watch("lon")
|
||||||
|
|
||||||
|
const sdekPoints = useQuery({
|
||||||
|
queryKey: ["sdek-points", lat, lon],
|
||||||
|
queryFn: async () => {
|
||||||
|
const service = new LocalAPI()
|
||||||
|
if (!lat || !lon){ return []}
|
||||||
|
|
||||||
|
return await service.fetchSdekPoints(lat, lon)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!mapInstance || !mapIsLoaded) return
|
||||||
|
if (lat && lon) {
|
||||||
|
const LOCATION: YMapLocationRequest = {
|
||||||
|
center: [lon, lat],
|
||||||
|
zoom: 15
|
||||||
|
};
|
||||||
|
mapInstance.setLocation(LOCATION)
|
||||||
|
}
|
||||||
|
|
||||||
|
}, [mapInstance, mapIsLoaded, lat, lon]);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Script src={"https://api-maps.yandex.ru/v3/?apikey=cc184d53-fc5f-4821-aa68-a767beea55d6&lang=ru_RU"}
|
||||||
|
onLoad={() => initMap(ref, setMapIsLoaded, setMapInstance)}/>
|
||||||
|
|
||||||
|
{
|
||||||
|
sdekPoints.isLoading && <Spinner />
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
sdekPoints.data && sdekPoints.data.map((obj, idx) => <SdekElem key={idx} map={mapInstance} object={obj} {...props}/>)
|
||||||
|
}
|
||||||
|
|
||||||
|
<div ref={ref} className={"h-[600px] w-full"}>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CdekMap
|
|
@ -0,0 +1,65 @@
|
||||||
|
import {Radio, RadioProps} from "@nextui-org/radio";
|
||||||
|
import {cn} from "@nextui-org/react";
|
||||||
|
import StorageIcon from "../../../../public/storage.svg";
|
||||||
|
import DeliveryIcon from "../../../../public/delivery.svg";
|
||||||
|
import {Controller} from "react-hook-form";
|
||||||
|
import {InputPropsType} from "@/components/pages/order/types";
|
||||||
|
|
||||||
|
const DeliveryTypeInput = ({deliveryType, ...formProps}: { deliveryType: "take-away" | "delivery" } & RadioProps) => {
|
||||||
|
|
||||||
|
if (deliveryType === 'take-away') return (
|
||||||
|
<div className={"flex-[1_1_calc((100%_/_2)_-_20px)]"}>
|
||||||
|
<Radio className={""}
|
||||||
|
classNames={{
|
||||||
|
label: cn("flex flex-row gap-10"),
|
||||||
|
labelWrapper: cn("w-full"),
|
||||||
|
wrapper: cn("hidden"),
|
||||||
|
base: cn("!max-w-full border-[2px] border-[#8F8F8F] p-5 rounded-[20px] data-[selected=true]:border-primary data-[selected=true]:bg-primary transition-colors")
|
||||||
|
}} {...formProps} value={"take-away"}>
|
||||||
|
<StorageIcon className={"group-data-[selected=true]:fill-[#151515] fill-[#8F8F8F]"}/>
|
||||||
|
<div className="text self-stretch flex justify-between flex-col py-2 w-1/3">
|
||||||
|
<h3 className={"text-subtitle-3 font-bold group-data-[selected=true]:text-[#151515] text-[#8F8F8F]"}>Самовывоз</h3>
|
||||||
|
<p className={"text-subtitle-4 text-[#8F8F8F] group-data-[selected=true]:text-[#151515]"}>
|
||||||
|
г. Домодедово
|
||||||
|
ул. Каширское Шоссе д. 4 к.1
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</Radio>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
else return (
|
||||||
|
<div className={"flex flex-[1_1_calc((100%_/_2)_-_20px)]"}>
|
||||||
|
|
||||||
|
<Radio
|
||||||
|
classNames={{
|
||||||
|
label: cn("flex flex-row gap-10"),
|
||||||
|
labelWrapper: cn("w-full"),
|
||||||
|
wrapper: cn("hidden"),
|
||||||
|
base: cn("!max-w-full border-[2px] border-[#8F8F8F] p-5 rounded-[20px] data-[selected=true]:border-primary data-[selected=true]:bg-primary transition-colors w-full")
|
||||||
|
}} {...formProps} value={"delivery"}>
|
||||||
|
|
||||||
|
<DeliveryIcon className={"group-data-[selected=true]:fill-[#151515] fill-[#8F8F8F]"}/>
|
||||||
|
|
||||||
|
<div className="text self-stretch flex justify-between flex-col py-2 w-1/3">
|
||||||
|
<h3 className={"text-subtitle-3 font-bold group-data-[selected=true]:text-[#151515] text-[#8F8F8F]"}>Доставка</h3>
|
||||||
|
<p className={"text-subtitle-4 text-[#8F8F8F] group-data-[selected=true]:text-[#151515]"}>
|
||||||
|
Доставка с помощью ТК
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</Radio>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const DeliveryInput = ({control, deliveryType}: InputPropsType & { deliveryType: "take-away" | "delivery" }) => {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Controller control={control} name={"receivingMethod"} render={({field}) =>
|
||||||
|
<DeliveryTypeInput deliveryType={deliveryType} {...field} />}/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default DeliveryInput
|
|
@ -0,0 +1,34 @@
|
||||||
|
import {Input} from "@nextui-org/input";
|
||||||
|
import {Controller} from "react-hook-form";
|
||||||
|
import {InputPropsType} from "@/components/pages/order/types";
|
||||||
|
|
||||||
|
const EmailInput = ({control, errors} : InputPropsType) => {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name={"email"}
|
||||||
|
rules={{
|
||||||
|
required: "Поле обязательно для заполнения", pattern: {
|
||||||
|
value: /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/,
|
||||||
|
message: "Неверный формат эл. почты"
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
render={({field}) =>
|
||||||
|
|
||||||
|
<Input
|
||||||
|
classNames={{
|
||||||
|
"inputWrapper": "h-[65px]",
|
||||||
|
"label": "group[data-filled-within=true] group-data-[filled-within=true]:-translate-y-[60px] group-data-[filled-within=true]:text-[#8F8F8F]"
|
||||||
|
}}
|
||||||
|
variant={"bordered"} label={"E-mail"} type={"email"} isRequired
|
||||||
|
labelPlacement={"outside"} {...field}
|
||||||
|
isInvalid={!!errors.email}
|
||||||
|
errorMessage={errors.email && errors.email.message}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EmailInput;
|
|
@ -0,0 +1,25 @@
|
||||||
|
import {Controller} from "react-hook-form";
|
||||||
|
import {Input} from "@nextui-org/input";
|
||||||
|
import {InputPropsType} from "@/components/pages/order/types";
|
||||||
|
|
||||||
|
const FullNameInput = (props: InputPropsType) => {
|
||||||
|
return (
|
||||||
|
<Controller name={"fullName"} control={props.control}
|
||||||
|
rules={{required: "Поле обязательно для заполнения"}}
|
||||||
|
render={({field}) =>
|
||||||
|
<Input className={"col-span-2"}
|
||||||
|
classNames={{
|
||||||
|
"inputWrapper": "h-[65px]",
|
||||||
|
"label": "group[data-filled-within=true] group-data-[filled-within=true]:-translate-y-[60px] group-data-[filled-within=true]:text-[#8F8F8F]"
|
||||||
|
}}
|
||||||
|
variant={"bordered"} label={"ФИО"} type={"text"} isRequired
|
||||||
|
isInvalid={!!props.errors.fullName}
|
||||||
|
errorMessage={props.errors.fullName && props.errors.fullName.message}
|
||||||
|
labelPlacement={"outside"} {...field}/>
|
||||||
|
}
|
||||||
|
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FullNameInput
|
|
@ -0,0 +1,80 @@
|
||||||
|
import {Control, Controller, FieldErrors, UseFormSetValue, UseFormWatch} from "react-hook-form";
|
||||||
|
import {Input} from "@nextui-org/input";
|
||||||
|
import {FormDataValuesType, InputPropsType} from "@/components/pages/order/types";
|
||||||
|
import {useEffect, useState} from "react";
|
||||||
|
|
||||||
|
|
||||||
|
const normalizeInput = (value: string, previousValue: string) => {
|
||||||
|
// return nothing if no value
|
||||||
|
if (!value) return value;
|
||||||
|
|
||||||
|
// only allows 0-9 inputs
|
||||||
|
const currentValue = value.replace(/[^\d]/g, '');
|
||||||
|
const cvLength = currentValue.length;
|
||||||
|
|
||||||
|
if (!previousValue || value.length > previousValue.length) {
|
||||||
|
|
||||||
|
if (cvLength < 2) {
|
||||||
|
if (currentValue === "8") {
|
||||||
|
return `+7`;
|
||||||
|
}
|
||||||
|
return `+${currentValue} `
|
||||||
|
}
|
||||||
|
;
|
||||||
|
// returns: "x", "xx", "xxx" "xxx"
|
||||||
|
if (cvLength < 5) return `+${currentValue.slice(0, 1)} ${currentValue.slice(1)}`;
|
||||||
|
|
||||||
|
// 7 (902) 486 65-00
|
||||||
|
|
||||||
|
// returns: "(xxx)", "(xxx) x", "(xxx) xx", "(xxx) xxx",
|
||||||
|
if (cvLength < 8) return `+${currentValue.slice(0, 1)} (${currentValue.slice(1, 4)}) ${currentValue.slice(4)}`;
|
||||||
|
|
||||||
|
// returns: "(xxx) xxx-", (xxx) xxx-x", "(xxx) xxx-xx", "(xxx) xxx-xxx", "(xxx) xxx-xx"
|
||||||
|
if (cvLength < 10) return `+${currentValue.slice(0, 1)} (${currentValue.slice(1, 4)}) ${currentValue.slice(4, 7)} ${currentValue.slice(7)}`;
|
||||||
|
|
||||||
|
return `+${currentValue.slice(0, 1)} (${currentValue.slice(1, 4)}) ${currentValue.slice(4, 7)} ${currentValue.slice(7, 9)}-${currentValue.slice(9)}`;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
};
|
||||||
|
|
||||||
|
const PhoneInput = ({control, errors, watch, setValue} : InputPropsType) => {
|
||||||
|
|
||||||
|
const [phoneNumberPrev, setPhoneNumberPrev] = useState("")
|
||||||
|
const phoneNumberCur = watch("phoneNumber")
|
||||||
|
|
||||||
|
// const formRef = useRef<HTMLFormElement>(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (phoneNumberCur === phoneNumberPrev) return
|
||||||
|
setValue('phoneNumber', normalizeInput(phoneNumberCur, phoneNumberPrev))
|
||||||
|
setPhoneNumberPrev(phoneNumberCur)
|
||||||
|
}, [phoneNumberCur])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Controller control={control}
|
||||||
|
name={"phoneNumber"}
|
||||||
|
rules={{
|
||||||
|
required: "Поле обязательно для заполнения",
|
||||||
|
pattern: {
|
||||||
|
message: "Неверный формат номера телефона",
|
||||||
|
value: /\+\d \(\d{3}\) \d{3} \d{2}-\d{2}/
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
render={({field}) =>
|
||||||
|
<Input
|
||||||
|
classNames={{
|
||||||
|
"inputWrapper": "h-[65px]",
|
||||||
|
"label": "group[data-filled-within=true] group-data-[filled-within=true]:-translate-y-[60px] group-data-[filled-within=true]:text-[#8F8F8F]"
|
||||||
|
}}
|
||||||
|
variant={"bordered"} label={"Телефон"} type={"tel"} isRequired
|
||||||
|
labelPlacement={"outside"} {...field}
|
||||||
|
isInvalid={!!errors.phoneNumber}
|
||||||
|
errorMessage={errors.phoneNumber && errors.phoneNumber.message}
|
||||||
|
/>
|
||||||
|
}/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PhoneInput;
|
|
@ -0,0 +1,18 @@
|
||||||
|
import {Control, FieldErrors, UseFormSetValue, UseFormWatch} from "react-hook-form";
|
||||||
|
|
||||||
|
export type FormDataValuesType = {
|
||||||
|
fullName: string
|
||||||
|
phoneNumber: string
|
||||||
|
email: string
|
||||||
|
receivingMethod: string
|
||||||
|
address?: string
|
||||||
|
deliveryTypeId?: number
|
||||||
|
paymentTypeId: number
|
||||||
|
comment: string,
|
||||||
|
pvzId?: string
|
||||||
|
lat?: number,
|
||||||
|
lon?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export type InputPropsType = { errors: FieldErrors<FormDataValuesType>, control: Control<FormDataValuesType>, watch: UseFormWatch<FormDataValuesType>, setValue: UseFormSetValue<FormDataValuesType> }
|
|
@ -0,0 +1,23 @@
|
||||||
|
import {Radio, RadioGroup} from "@nextui-org/radio";
|
||||||
|
|
||||||
|
const Step = ({value, title} : {value: string, title: string}) => {
|
||||||
|
return (
|
||||||
|
<Radio value={value}>
|
||||||
|
<span>{title}</span>
|
||||||
|
</Radio>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const CallbackForm = ({steps} : {steps: {title: string, value: string}[]}) => {
|
||||||
|
return (
|
||||||
|
<form>
|
||||||
|
{steps.map((step, index) => (
|
||||||
|
<RadioGroup key={index} name="callback" defaultValue={step.value}>
|
||||||
|
<Step {...step}/>
|
||||||
|
</RadioGroup>
|
||||||
|
))}
|
||||||
|
</form>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CallbackForm
|
|
@ -3,6 +3,7 @@ import Link from "next/link";
|
||||||
import Logo from "../../../public/header_logo.svg";
|
import Logo from "../../../public/header_logo.svg";
|
||||||
import TgIcon from "../../../public/tg_icon.svg"
|
import TgIcon from "../../../public/tg_icon.svg"
|
||||||
import VkIcon from "../../../public/vk_icon.svg"
|
import VkIcon from "../../../public/vk_icon.svg"
|
||||||
|
import YouTubeIcon from "../../../public/YouTubeIcon.svg"
|
||||||
import {Img} from "react-image";
|
import {Img} from "react-image";
|
||||||
|
|
||||||
const Footer = () => {
|
const Footer = () => {
|
||||||
|
@ -18,13 +19,14 @@ const Footer = () => {
|
||||||
<a className={"block"} href={"tel:+74951919720"}>+7(495)191-97-20</a>
|
<a className={"block"} href={"tel:+74951919720"}>+7(495)191-97-20</a>
|
||||||
|
|
||||||
<div className="socials my-4 flex [&_a]:mr-2">
|
<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="https://vk.com/relynolli_vk" 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>
|
<a href="https://t.me/relynolli" className={"group"}><TgIcon className={"fill-gray-3 group-hover:fill-primary transition-colors [&_path]:fill-white"} /></a>
|
||||||
|
<a href="https://t.me/relynolli" className={"group"}><YouTubeIcon className={"fill-gray-3 group-hover:fill-primary transition-colors [&_path]:fill-white last:[&_path]:fill-gray-3"} /></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={"markets my-2"}>
|
{/*<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>
|
{/* <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>
|
</div>
|
||||||
<div className="col-span-12 xl:col-span-3 xl:col-start-5 mt-5">
|
<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>
|
<h2 className={"text-2xl hover:text-primary transition-colors"}>Бренд</h2>
|
||||||
|
@ -32,31 +34,31 @@ const Footer = () => {
|
||||||
<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>
|
||||||
<li className={"hover:text-primary transition-colors"}>Новости</li>
|
<li className={"hover:text-primary transition-colors"}><Link href={"/news"}>Новости</Link></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>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-12 xl:col-span-3 mt-5">
|
<div className="col-span-12 xl:col-span-3 mt-5">
|
||||||
<h2 className={"text-2xl hover:text-primary transition-colors"}>Продукция</h2>
|
<h2 className={"text-2xl hover:text-primary transition-colors"}><Link href={"/catalog"}>Продукция</Link></h2>
|
||||||
<ul className={"text-gray-3 text-sm font-semibold my-2"}>
|
<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"}><Link href={"/catalog"}>Relynolli ® Standart M</Link></li>
|
||||||
<li className={"hover:text-primary transition-colors"}>Relynolli ® Premium M</li>
|
<li className={"hover:text-primary transition-colors"}><Link href={"/catalog"}>Relynolli ® Premium M</Link></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-12 xl:col-span-2 mt-5">
|
<div className="col-span-12 xl:col-span-2 mt-5">
|
||||||
<h2 className={"text-2xl hover:text-primary transition-colors"}>Информация</h2>
|
<h2 className={"text-2xl hover:text-primary transition-colors"}>Информация</h2>
|
||||||
<ul className={"text-gray-3 text-sm font-semibold my-2"}>
|
<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"}><Link href={"/contact"}>Контакты</Link></li>
|
||||||
<li className={"hover:text-primary transition-colors"}>Поддержка и рекламации</li>
|
<li className={"hover:text-primary transition-colors"}>Поддержка и рекламации</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<Divider className={"col-span-12 my-8 h-[1px] w-full bg-gray-3"}/>
|
<Divider className={"col-span-12 my-8 h-[1px] w-full bg-gray-3"}/>
|
||||||
<div className="col-span-12">
|
<div className="col-span-12">
|
||||||
<h2>© ООО "ТД Технохим Групп" 2024</h2>
|
<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 mt-4"}><Link href={"https://tehnohimgrupp.ru/upload/Политика_обработки_ПДн_ТХГ.pdf"}>Политика конфиденциальности</Link></p>
|
||||||
<p className={"text-gray-3 text-sm font-semibold my-2"}>Обработка персональных данных</p>
|
<p className={"text-gray-3 text-sm font-semibold my-2"}><Link href={"https://tehnohimgrupp.ru/upload/Согласие на ОПД Технохим.pdf"}>Обработка персональных данных</Link></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ import {useQuery, useQueryClient} from "@tanstack/react-query";
|
||||||
import LocalAPI from "@/service/localAPI";
|
import LocalAPI from "@/service/localAPI";
|
||||||
import {HTMLProps, useEffect, useState} from "react";
|
import {HTMLProps, useEffect, useState} from "react";
|
||||||
import {tv} from "tailwind-variants"
|
import {tv} from "tailwind-variants"
|
||||||
|
import {Img} from "react-image";
|
||||||
|
|
||||||
const NavbarMenuToggle = ({isOpened, ...props}: { isOpened: boolean} & HTMLProps<HTMLDivElement>) => {
|
const NavbarMenuToggle = ({isOpened, ...props}: { isOpened: boolean} & HTMLProps<HTMLDivElement>) => {
|
||||||
|
|
||||||
|
@ -72,9 +73,9 @@ const NavbarMenuToggle = ({isOpened, ...props}: { isOpened: boolean} & HTMLProps
|
||||||
const Header = () => {
|
const Header = () => {
|
||||||
const menuItems = [
|
const menuItems = [
|
||||||
{title: "Каталог", href: "/catalog"},
|
{title: "Каталог", href: "/catalog"},
|
||||||
{title: "Бренд", href: "#"},
|
{title: "Новости", href: "/news"},
|
||||||
{title: "Статьи", href: "#"},
|
{title: "Статьи", href: "/articles"},
|
||||||
{title: "Контакты", href: "#"},
|
{title: "Контакты", href: "/contact"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -91,8 +92,8 @@ const Header = () => {
|
||||||
return (
|
return (
|
||||||
<header className={"w-full top-0 left-0 bg-black-4 py-[33px] z-50 h-[100px] relative"}>
|
<header className={"w-full top-0 left-0 bg-black-4 py-[33px] z-50 h-[100px] relative"}>
|
||||||
<div className="wrapper flex justify-between items-center ">
|
<div className="wrapper flex justify-between items-center ">
|
||||||
<Link href={"/"} className={"transition-none hover:opacity-100 w-1/4"}>
|
<Link href={"/"} className={"transition-none hover:opacity-100 w-3/4 xl:w-1/4"} onClick={() => setIsOpened(false)}>
|
||||||
<Logo/>
|
<Img src={'/header_logo.svg'} className={"w-full max-w-[271px]"} />
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<nav className={"xl:flex justify-between items-center w-[30%] hidden"}>
|
<nav className={"xl:flex justify-between items-center w-[30%] hidden"}>
|
||||||
|
@ -166,7 +167,7 @@ const Header = () => {
|
||||||
menuItems.map(item => (
|
menuItems.map(item => (
|
||||||
<Link href={item.href}
|
<Link href={item.href}
|
||||||
className={"text-2xl text-white hover:text-green-2 hover:opacity-100 transition-colors"}
|
className={"text-2xl text-white hover:text-green-2 hover:opacity-100 transition-colors"}
|
||||||
key={item.title}>{item.title}</Link>
|
key={item.title} onClick={() => setIsOpened(false)}>{item.title}</Link>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
</nav>
|
</nav>
|
||||||
|
@ -201,6 +202,7 @@ const Header = () => {
|
||||||
<Badge isInvisible={!Boolean(cart.data.data)} color={"primary"} content={cart.data.data && cart.data.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'}
|
||||||
|
onClick={() => setIsOpened(false)}
|
||||||
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]"}>
|
||||||
<CartLogo className={"fill-white group-hover:fill-black-1 transition-colors"}/>
|
<CartLogo className={"fill-white group-hover:fill-black-1 transition-colors"}/>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
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";
|
import Footer from "@/components/reusable/footer";
|
||||||
|
import Script from "next/script";
|
||||||
|
|
||||||
const mulish = Mulish({
|
const mulish = Mulish({
|
||||||
subsets: ["cyrillic", "latin"],
|
subsets: ["cyrillic", "latin"],
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
import React, {useState} from "react";
|
||||||
|
|
||||||
|
export const Box = ({title, isCompleted}: {title: string, isCompleted: boolean}): JSX.Element => {
|
||||||
|
if (isCompleted) {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-[66px] h-[66px]">
|
||||||
|
<div className="w-[68px] h-[66px] top-0 left-0 bg-primary rounded-[100%]">
|
||||||
|
<div className="w-[66px] h-[66px] rounded-[50px] flex items-center justify-center font-bold text-2xl ">
|
||||||
|
{title}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className="w-[66px] h-[66px]">
|
||||||
|
<div className="w-[68px] h-[66px] top-0 left-0 rounded-[100%] text-primary border-2 border-primary">
|
||||||
|
<div className="w-[66px] h-[66px] rounded-[50px] flex items-center justify-center font-bold text-2xl ">
|
||||||
|
{title}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const Stepper = ({steps = []} : {steps: {title: string, isActive?: boolean}[]}) => {
|
||||||
|
const [lastActiveIdx, setLastActiveIdx] = useState(0)
|
||||||
|
|
||||||
|
const getIsCompleted = (idx: number, step: {title: string, isActive?: boolean}) => {
|
||||||
|
if (step.isActive && idx >= lastActiveIdx) {
|
||||||
|
// setLastActiveIdx(idx)
|
||||||
|
}
|
||||||
|
return idx <= lastActiveIdx
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className={"stepper"}>
|
||||||
|
<div className="flex items-center gap-[7px] relative">
|
||||||
|
{steps.map((step, idx) => (
|
||||||
|
<>
|
||||||
|
<Box title={step.title} key={idx} isCompleted={getIsCompleted(idx, step)}/>
|
||||||
|
{idx < steps.length - 1 &&
|
||||||
|
<svg width="47" height="16" viewBox="0 0 47 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M46.7071 8.70711C47.0976 8.31658 47.0976 7.68342 46.7071 7.29289L40.3431 0.928932C39.9526 0.538408 39.3195 0.538408 38.9289 0.928932C38.5384 1.31946 38.5384 1.95262 38.9289 2.34315L44.5858 8L38.9289 13.6569C38.5384 14.0474 38.5384 14.6805 38.9289 15.0711C39.3195 15.4616 39.9526 15.4616 40.3431 15.0711L46.7071 8.70711ZM0 9H46V7H0V9Z" fill="#92E727"/>
|
||||||
|
</svg>
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
export default Stepper;
|
|
@ -31,7 +31,40 @@ const Wrapper = (props: WrapperProps) => {
|
||||||
{
|
{
|
||||||
props.breadcrumbs.map(item =>
|
props.breadcrumbs.map(item =>
|
||||||
<BreadcrumbItem startContent={item.icon} key={item.name}>
|
<BreadcrumbItem startContent={item.icon} key={item.name}>
|
||||||
<Link href={item.link}>{item.name}</Link>
|
<Link className={"!whitespace-break-spaces"} href={item.link}>{item.name}</Link>
|
||||||
|
</BreadcrumbItem>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</Breadcrumbs>
|
||||||
|
}
|
||||||
|
<h1 className={"mt-4 lg:text-7xl text-3xl font-bold italic uppercase pb-16"}>{props.title}</h1>
|
||||||
|
|
||||||
|
{props.children}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const WrapperDark =(props: WrapperProps) => {
|
||||||
|
return <>
|
||||||
|
<section className={"bg-black-2 text-white py-7"}>
|
||||||
|
<div className="wrapper">
|
||||||
|
{
|
||||||
|
props.breadcrumbs &&
|
||||||
|
<Breadcrumbs separator="/"
|
||||||
|
itemClasses={{
|
||||||
|
separator: "px-2 text-gray-3"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<BreadcrumbItem startContent={<HomeIcon/>} className={"text-white"}>
|
||||||
|
<Link className={"text-gray-3"} href={"/"}>Главная</Link>
|
||||||
|
</BreadcrumbItem>
|
||||||
|
{
|
||||||
|
props.breadcrumbs.map(item =>
|
||||||
|
<BreadcrumbItem startContent={item.icon} key={item.name}>
|
||||||
|
<Link className={"text-gray-3"} href={item.link}>{item.name}</Link>
|
||||||
</BreadcrumbItem>
|
</BreadcrumbItem>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,37 @@
|
||||||
import { Html, Head, Main, NextScript } from "next/document";
|
import {Html, Head, Main, NextScript} from "next/document";
|
||||||
|
|
||||||
export default function Document() {
|
export default function Document() {
|
||||||
return (
|
return (
|
||||||
<Html lang="en">
|
<Html lang="en">
|
||||||
<Head />
|
<Head/>
|
||||||
<body>
|
<body>
|
||||||
<Main />
|
<Main/>
|
||||||
<NextScript />
|
<NextScript/>
|
||||||
</body>
|
|
||||||
</Html>
|
<script
|
||||||
);
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: `
|
||||||
|
(function(m,e,t,r,i,k,a){m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)};
|
||||||
|
m[i].l=1*new Date();
|
||||||
|
for (var j = 0; j < document.scripts.length; j++) {if (document.scripts[j].src === r) { return; }}
|
||||||
|
k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)})
|
||||||
|
(window, document, "script", "https://mc.yandex.ru/metrika/tag.js", "ym");
|
||||||
|
|
||||||
|
ym(95565760, "init", {
|
||||||
|
clickmap:true,
|
||||||
|
trackLinks:true,
|
||||||
|
accurateTrackBounce:true,
|
||||||
|
webvisor:true
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
}}/>
|
||||||
|
<noscript>
|
||||||
|
<div>
|
||||||
|
<img src="https://mc.yandex.ru/watch/12345678" style={{position: 'absolute', left: '-9999px'}}
|
||||||
|
alt=""/>
|
||||||
|
</div>
|
||||||
|
</noscript>
|
||||||
|
</body>
|
||||||
|
</Html>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
import LocalAPI from "@/service/localAPI";
|
||||||
|
import {InferGetStaticPropsType} from "next";
|
||||||
|
import {Img} from "react-image";
|
||||||
|
import Wrapper from "@/components/reusable/wrapper";
|
||||||
|
|
||||||
|
const News = (props: InferGetStaticPropsType<typeof getStaticProps>) => {
|
||||||
|
return (
|
||||||
|
<Wrapper title={props.news.name} breadcrumbs={[{name: "Новости", link: "/articles"}, {name: props.news.name, link: "/articles/" + props.news.code}]} >
|
||||||
|
{
|
||||||
|
props.news.picture && <Img className={"max-h-[250px] lg:max-h-[500px] mb-6 mx-auto rounded-[20px]"} src={"https://relynolli.ru/upload/" + props.news.picture} alt={props.news.name} />
|
||||||
|
}
|
||||||
|
<div className="content text-base lg:text-2xl [&>*]:mb-4 [&_img]:max-h-[250px] [&_img]:lg:max-h-[500px] [&_a]:text-primary break-words" dangerouslySetInnerHTML={{__html: props.news.content}}></div>
|
||||||
|
</Wrapper>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default News
|
||||||
|
|
||||||
|
export async function getStaticPaths() {
|
||||||
|
const service = new LocalAPI()
|
||||||
|
const news = await service.fetchArticles()
|
||||||
|
return {
|
||||||
|
paths: news.data!.map(item => ({
|
||||||
|
params: {
|
||||||
|
slug: item.code
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
fallback: "blocking",
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getStaticProps({params: {slug}}: { params: { slug: string } }) {
|
||||||
|
|
||||||
|
const service = new LocalAPI()
|
||||||
|
console.log(slug)
|
||||||
|
try {
|
||||||
|
const news = await service.retrieveArticle(slug)
|
||||||
|
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
news: news.data!
|
||||||
|
},
|
||||||
|
revalidate: 10 * 60
|
||||||
|
}}
|
||||||
|
catch (e) {
|
||||||
|
return {
|
||||||
|
notFound: true,
|
||||||
|
revalidate: 10 * 60
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
import {Img} from "react-image";
|
||||||
|
import {useQuery} from "@tanstack/react-query";
|
||||||
|
import LocalAPI from "@/service/localAPI";
|
||||||
|
import {News} from "@/service/types/local";
|
||||||
|
import {WrapperDark} from "@/components/reusable/wrapper";
|
||||||
|
import {Skeleton} from "@nextui-org/react";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
const NewsCard = (props: News) => {
|
||||||
|
return (
|
||||||
|
<Link href={"/articles/" + props.code}>
|
||||||
|
<div className="w-full max-w-[476px] relative group">
|
||||||
|
<div className="h-64 w-full overflow-hidden relative z-20">
|
||||||
|
<Img className="h-full w-full rounded-[20px] mb-5 object-cover"
|
||||||
|
src={"https://relynolli.ru/upload/" + props.picture}/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-primary w-full opacity-0 group-hover:opacity-100 absolute top-1/2 left-0 z-10 bottom-0 -translate-y-10 group-hover:translate-y-0 transition-all rounded-b-[20px]"></div>
|
||||||
|
|
||||||
|
<div className="z-20 relative p-2">
|
||||||
|
<div className="w-full text-white group-hover:text-black-2 text-xl font-semibold transition-all">{props.name}</div>
|
||||||
|
<div className="text-neutral-400 text-base font-semibold group-hover:text-black-2 transition-all">{new Date(props.date).toLocaleDateString()}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const News = () => {
|
||||||
|
|
||||||
|
const queryNews = useQuery({
|
||||||
|
queryKey: ["articles"], queryFn: async () => {
|
||||||
|
const service = new LocalAPI()
|
||||||
|
return await service.fetchArticles()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<WrapperDark title={"Статьи"} breadcrumbs={[{name: "Статьи", link: "/articles"}]}>
|
||||||
|
|
||||||
|
<div className="news-container grid grid-cols-1 lg:grid-cols-3 gap-5">
|
||||||
|
{
|
||||||
|
queryNews.data && queryNews.data.data!.map(news => <NewsCard key={news.id} {...news}/>)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
queryNews.isFetching && Array(10).fill(0).map((_, index) => <Skeleton key={index} className={"rounded-[20px] !bg-gray-3"}>
|
||||||
|
<div className="w-full h-64 "></div>
|
||||||
|
</Skeleton> )
|
||||||
|
}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</WrapperDark>)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default News
|
|
@ -5,8 +5,6 @@ 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"
|
||||||
import {ChevronRightIcon} from "@nextui-org/shared-icons";
|
import {ChevronRightIcon} from "@nextui-org/shared-icons";
|
||||||
import {toggleCart} from "@/store/cart"
|
|
||||||
import {ResponseData} from "@/pages/api/v1/catalog";
|
|
||||||
import {useState} from "react";
|
import {useState} from "react";
|
||||||
import {useRouter} from "next/navigation";
|
import {useRouter} from "next/navigation";
|
||||||
import Wrapper from "@/components/reusable/wrapper";
|
import Wrapper from "@/components/reusable/wrapper";
|
||||||
|
@ -63,7 +61,7 @@ const CartCard = (item: CartItem) => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button className={"z-20 absolute top-8 right-0"} onClick={() => {
|
<button className={"z-20 absolute top-8 right-0"} onClick={() => {
|
||||||
// TODO DO
|
changeQuantity.mutate({productId: item.product.id!, quantity: 0})
|
||||||
}}>
|
}}>
|
||||||
<CrossIcon className={"hover:fill-red-500 fill-[#8F8F8F] transition-colors"}/>
|
<CrossIcon className={"hover:fill-red-500 fill-[#8F8F8F] transition-colors"}/>
|
||||||
</button>
|
</button>
|
||||||
|
@ -93,7 +91,7 @@ const PlaceHolder = () => {
|
||||||
|
|
||||||
const Cart = () => {
|
const Cart = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [isDisabled, setIsDisabled] = useState(true)
|
const [isDisabled, setIsDisabled] = useState(false)
|
||||||
|
|
||||||
|
|
||||||
const cart = useQuery({
|
const cart = useQuery({
|
||||||
|
@ -129,8 +127,6 @@ const Cart = () => {
|
||||||
|
|
||||||
</>) : <PlaceHolder/>
|
</>) : <PlaceHolder/>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
|
|
||||||
|
|
|
@ -53,7 +53,7 @@ const OilCard = ({product}: InferGetStaticPropsType<typeof getStaticProps>) => {
|
||||||
Каталог
|
Каталог
|
||||||
</Link>
|
</Link>
|
||||||
</BreadcrumbItem>
|
</BreadcrumbItem>
|
||||||
<BreadcrumbItem>{product.name}</BreadcrumbItem>
|
<BreadcrumbItem><Link className={"!whitespace-break-spaces"} href={"/catalog/" + product.name}>{product.name}</Link></BreadcrumbItem>
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
</div>
|
</div>
|
||||||
<div className="wrapper mt-12 grid grid-cols-1 lg:grid-cols-2 gap-4 items-center">
|
<div className="wrapper mt-12 grid grid-cols-1 lg:grid-cols-2 gap-4 items-center">
|
||||||
|
@ -71,8 +71,8 @@ const OilCard = ({product}: InferGetStaticPropsType<typeof getStaticProps>) => {
|
||||||
<span
|
<span
|
||||||
className={"text-base md:text-xl block mb-7 text-[#E0E3E3]"}>Тип: {product.properties.oil_type}</span>
|
className={"text-base md:text-xl block mb-7 text-[#E0E3E3]"}>Тип: {product.properties.oil_type}</span>
|
||||||
</div>
|
</div>
|
||||||
<Button isDisabled color={"warning"} className={"text-black italic md:text-xl text-sm py-8 mb-7 w-full lg:w-auto"}><span
|
{/*<Button isDisabled color={"warning"} className={"text-black italic md:text-xl text-sm py-8 mb-7 w-full lg:w-auto"}><span*/}
|
||||||
className={"font-bold"}>Скидка 10%</span> при покупке более 12 шт</Button>
|
{/* className={"font-bold"}>Скидка 10%</span> при покупке более 12 шт</Button>*/}
|
||||||
<div className="grid items-center grid-cols-2 lg:grid-cols-4 gap-5 pb-5">
|
<div className="grid items-center grid-cols-2 lg:grid-cols-4 gap-5 pb-5">
|
||||||
|
|
||||||
<span className="font-bold text-3xl text-white text-center">
|
<span className="font-bold text-3xl text-white text-center">
|
||||||
|
@ -199,13 +199,13 @@ export const getStaticProps = async ({params: {code}}: { params: { code: string
|
||||||
props: {
|
props: {
|
||||||
product: data.data!
|
product: data.data!
|
||||||
},
|
},
|
||||||
revalidate: 15*60
|
revalidate: 10 * 60
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
return {
|
return {
|
||||||
redirect: {"destination": "/404", "permanent": false},
|
notFound: true,
|
||||||
revalidate: 15*60
|
revalidate: 10 * 60
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,583 @@
|
||||||
|
import Wrapper from "@/components/reusable/wrapper";
|
||||||
|
import Stepper from "@/components/reusable/stepper";
|
||||||
|
import CallbackForm from "@/components/reusable/contacts/callbackForm";
|
||||||
|
|
||||||
|
const Form1 = () => (
|
||||||
|
<>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
|
||||||
|
const Form2 = () => (<></>)
|
||||||
|
|
||||||
|
const Form3 = () => (<></>)
|
||||||
|
|
||||||
|
const Form4= () => (<></>)
|
||||||
|
|
||||||
|
const Form5 = () => (<></>)
|
||||||
|
|
||||||
|
const Form6 = () => (<></>)
|
||||||
|
|
||||||
|
const Form7 = () => (<></>)
|
||||||
|
|
||||||
|
const Form8 = () => (<></>)
|
||||||
|
|
||||||
|
const Form9 = () => (<></>)
|
||||||
|
|
||||||
|
const Form10 = () => (<></>)
|
||||||
|
|
||||||
|
const Form11 = () => (<></>)
|
||||||
|
|
||||||
|
const Form12 = () => (<></>)
|
||||||
|
const Form13 = () => (<></>)
|
||||||
|
|
||||||
|
const Form14 = () => (<></>)
|
||||||
|
|
||||||
|
const Contact = () => {
|
||||||
|
const steps = [
|
||||||
|
{
|
||||||
|
curStep: "1",
|
||||||
|
title: "Покупатель",
|
||||||
|
value: "customer",
|
||||||
|
step: [
|
||||||
|
{
|
||||||
|
curStep: "2",
|
||||||
|
title: "Юридическое лицо",
|
||||||
|
value: "business",
|
||||||
|
step: [
|
||||||
|
{
|
||||||
|
curStep:"3",
|
||||||
|
title: "Помощь подбора масла",
|
||||||
|
value: "oil-select-help",
|
||||||
|
step: [
|
||||||
|
{
|
||||||
|
curStep:"4",
|
||||||
|
component: Form1
|
||||||
|
}
|
||||||
|
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
curStep:"3",
|
||||||
|
title: "Уточнение характеристик товара",
|
||||||
|
value: "product-characteristics",
|
||||||
|
step: [
|
||||||
|
{
|
||||||
|
curStep:"4",
|
||||||
|
component: Form4
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
curStep:"3",
|
||||||
|
title: "Заявка на сотрудничество",
|
||||||
|
value: "cooperation",
|
||||||
|
step: [
|
||||||
|
{
|
||||||
|
curStep:"4",
|
||||||
|
component: Form2
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
curStep:"3",
|
||||||
|
title: "Обращение к техническому специалисту",
|
||||||
|
value: "technical-specialist",
|
||||||
|
step: [
|
||||||
|
{
|
||||||
|
curStep:"4",
|
||||||
|
component: Form1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
curStep:"3",
|
||||||
|
title: "Запрос сертификата",
|
||||||
|
value: "certificate-request",
|
||||||
|
step: [
|
||||||
|
{
|
||||||
|
curStep:"4",
|
||||||
|
component: Form2
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
curStep:"3",
|
||||||
|
title: "Отзыв о качестве продукции",
|
||||||
|
value: "feedback-on-product-quality",
|
||||||
|
step: [
|
||||||
|
{
|
||||||
|
curStep:"4",
|
||||||
|
component: Form3
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
curStep:"3",
|
||||||
|
title: "Жалоба",
|
||||||
|
value: "complaint",
|
||||||
|
step: [
|
||||||
|
{
|
||||||
|
curStep:"4",
|
||||||
|
component: Form3
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
curStep:"3",
|
||||||
|
title: "Иные вопросы",
|
||||||
|
value: "other-questions",
|
||||||
|
step: [
|
||||||
|
{
|
||||||
|
curStep:"4",
|
||||||
|
component: Form3
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
curStep:"3",
|
||||||
|
title: "Оформить подписку",
|
||||||
|
value: "subscribe",
|
||||||
|
step: [
|
||||||
|
{
|
||||||
|
curStep:"4",
|
||||||
|
component: Form5
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
curStep: "2",
|
||||||
|
title: "Физическое лицо",
|
||||||
|
value: "individual",
|
||||||
|
step: [
|
||||||
|
{
|
||||||
|
curStep:"3",
|
||||||
|
title: "Помощь подбора масла",
|
||||||
|
value: "oil-select-help",
|
||||||
|
step: [
|
||||||
|
{
|
||||||
|
curStep:"4",
|
||||||
|
component: Form6
|
||||||
|
}
|
||||||
|
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
curStep:"3",
|
||||||
|
title: "Уточнение характеристик товара",
|
||||||
|
value: "product-characteristics",
|
||||||
|
step: [
|
||||||
|
{
|
||||||
|
curStep:"4",
|
||||||
|
component: Form9
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
curStep:"3",
|
||||||
|
title: "Заявка на сотрудничество",
|
||||||
|
value: "cooperation",
|
||||||
|
step: [
|
||||||
|
{
|
||||||
|
curStep:"4",
|
||||||
|
component: Form7
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
curStep:"3",
|
||||||
|
title: "Обращение к техническому специалисту",
|
||||||
|
value: "technical-specialist",
|
||||||
|
step: [
|
||||||
|
{
|
||||||
|
curStep:"4",
|
||||||
|
component: Form6
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
curStep:"3",
|
||||||
|
title: "Запрос сертификата",
|
||||||
|
value: "certificate-request",
|
||||||
|
step: [
|
||||||
|
{
|
||||||
|
curStep:"4",
|
||||||
|
component: Form7
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
curStep:"3",
|
||||||
|
title: "Отзыв о качестве продукции",
|
||||||
|
value: "feedback-on-product-quality",
|
||||||
|
step: [
|
||||||
|
{
|
||||||
|
curStep:"4",
|
||||||
|
component: Form8
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
curStep:"3",
|
||||||
|
title: "Жалоба",
|
||||||
|
value: "complaint",
|
||||||
|
step: [
|
||||||
|
{
|
||||||
|
curStep:"4",
|
||||||
|
component: Form8
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
curStep:"3",
|
||||||
|
title: "Иные вопросы",
|
||||||
|
value: "other-questions",
|
||||||
|
step: [
|
||||||
|
{
|
||||||
|
curStep:"4",
|
||||||
|
component: Form8
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
curStep:"3",
|
||||||
|
title: "Оформить подписку",
|
||||||
|
value: "subscribe",
|
||||||
|
step: [
|
||||||
|
{
|
||||||
|
curStep:"4",
|
||||||
|
component: Form10
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
curStep: "1",
|
||||||
|
title: "Поставщик",
|
||||||
|
value : "supplier",
|
||||||
|
step: [
|
||||||
|
{
|
||||||
|
curStep: "2",
|
||||||
|
title: "Юридическое лицо",
|
||||||
|
value: "business",
|
||||||
|
step: [
|
||||||
|
{
|
||||||
|
curStep:"3",
|
||||||
|
title: "Помощь подбора масла",
|
||||||
|
value: "oil-select-help",
|
||||||
|
step: [
|
||||||
|
{
|
||||||
|
curStep:"4",
|
||||||
|
component: Form1
|
||||||
|
}
|
||||||
|
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
curStep:"3",
|
||||||
|
title: "Уточнение характеристик товара",
|
||||||
|
value: "product-characteristics",
|
||||||
|
step: [
|
||||||
|
{
|
||||||
|
curStep:"4",
|
||||||
|
component: Form4
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
curStep:"3",
|
||||||
|
title: "Заявка на сотрудничество",
|
||||||
|
value: "cooperation",
|
||||||
|
step: [
|
||||||
|
{
|
||||||
|
curStep:"4",
|
||||||
|
component: Form2
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
curStep:"3",
|
||||||
|
title: "Обращение к техническому специалисту",
|
||||||
|
value: "technical-specialist",
|
||||||
|
step: [
|
||||||
|
{
|
||||||
|
curStep:"4",
|
||||||
|
component: Form1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
curStep:"3",
|
||||||
|
title: "Запрос сертификата",
|
||||||
|
value: "certificate-request",
|
||||||
|
step: [
|
||||||
|
{
|
||||||
|
curStep:"4",
|
||||||
|
component: Form2
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
curStep:"3",
|
||||||
|
title: "Отзыв о качестве продукции",
|
||||||
|
value: "feedback-on-product-quality",
|
||||||
|
step: [
|
||||||
|
{
|
||||||
|
curStep:"4",
|
||||||
|
component: Form3
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
curStep:"3",
|
||||||
|
title: "Жалоба",
|
||||||
|
value: "complaint",
|
||||||
|
step: [
|
||||||
|
{
|
||||||
|
curStep:"4",
|
||||||
|
component: Form3
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
curStep:"3",
|
||||||
|
title: "Иные вопросы",
|
||||||
|
value: "other-questions",
|
||||||
|
step: [
|
||||||
|
{
|
||||||
|
curStep:"4",
|
||||||
|
component: Form3
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
curStep:"3",
|
||||||
|
title: "Оформить подписку",
|
||||||
|
value: "subscribe",
|
||||||
|
step: [
|
||||||
|
{
|
||||||
|
curStep:"4",
|
||||||
|
component: Form5
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
curStep: "2",
|
||||||
|
title: "Физическое лицо",
|
||||||
|
value: "individual",
|
||||||
|
step: [
|
||||||
|
{
|
||||||
|
curStep:"3",
|
||||||
|
title: "Помощь подбора масла",
|
||||||
|
value: "oil-select-help",
|
||||||
|
step: [
|
||||||
|
{
|
||||||
|
curStep:"4",
|
||||||
|
component: Form6
|
||||||
|
}
|
||||||
|
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
curStep:"3",
|
||||||
|
title: "Уточнение характеристик товара",
|
||||||
|
value: "product-characteristics",
|
||||||
|
step: [
|
||||||
|
{
|
||||||
|
curStep:"4",
|
||||||
|
component: Form9
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
curStep:"3",
|
||||||
|
title: "Заявка на сотрудничество",
|
||||||
|
value: "cooperation",
|
||||||
|
step: [
|
||||||
|
{
|
||||||
|
curStep:"4",
|
||||||
|
component: Form7
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
curStep:"3",
|
||||||
|
title: "Обращение к техническому специалисту",
|
||||||
|
value: "technical-specialist",
|
||||||
|
step: [
|
||||||
|
{
|
||||||
|
curStep:"4",
|
||||||
|
component: Form6
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
curStep:"3",
|
||||||
|
title: "Запрос сертификата",
|
||||||
|
value: "certificate-request",
|
||||||
|
step: [
|
||||||
|
{
|
||||||
|
curStep:"4",
|
||||||
|
component: Form7
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
curStep:"3",
|
||||||
|
title: "Отзыв о качестве продукции",
|
||||||
|
value: "feedback-on-product-quality",
|
||||||
|
step: [
|
||||||
|
{
|
||||||
|
curStep:"4",
|
||||||
|
component: Form8
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
curStep:"3",
|
||||||
|
title: "Жалоба",
|
||||||
|
value: "complaint",
|
||||||
|
step: [
|
||||||
|
{
|
||||||
|
curStep:"4",
|
||||||
|
component: Form8
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
curStep:"3",
|
||||||
|
title: "Иные вопросы",
|
||||||
|
value: "other-questions",
|
||||||
|
step: [
|
||||||
|
{
|
||||||
|
curStep:"4",
|
||||||
|
component: Form8
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
curStep:"3",
|
||||||
|
title: "Оформить подписку",
|
||||||
|
value: "subscribe",
|
||||||
|
step: [
|
||||||
|
{
|
||||||
|
curStep:"4",
|
||||||
|
component: Form10
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
curStep: "1",
|
||||||
|
title: "Государственное предприятие",
|
||||||
|
value : "state-enterprise",
|
||||||
|
step: [
|
||||||
|
{
|
||||||
|
curStep: "2",
|
||||||
|
title: "Закупщик",
|
||||||
|
value: "buyer",
|
||||||
|
step: [
|
||||||
|
{
|
||||||
|
curStep:"4",
|
||||||
|
component: Form11,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
curStep: "2",
|
||||||
|
title: "Учебное или научное заведение",
|
||||||
|
value: "university",
|
||||||
|
step: [
|
||||||
|
{
|
||||||
|
curStep: "4",
|
||||||
|
component: Form12
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
curStep: "1",
|
||||||
|
title: "Работник компании",
|
||||||
|
value : "worker",
|
||||||
|
step: [
|
||||||
|
{
|
||||||
|
curStep: "2",
|
||||||
|
title: "Соискатель",
|
||||||
|
value: "founder",
|
||||||
|
step: [
|
||||||
|
{
|
||||||
|
curStep: "4",
|
||||||
|
component: Form13
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
curStep: "2",
|
||||||
|
title: "Штатный сотрудник",
|
||||||
|
value: "local-worker",
|
||||||
|
step: [
|
||||||
|
{
|
||||||
|
curStep: "4",
|
||||||
|
component:Form14
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Wrapper title={"Контакты"} breadcrumbs={[{name: "Контакты", link: "/contacts"}]}>
|
||||||
|
<iframe className={"rounded-[20px] mb-24"} src="https://yandex.ru/map-widget/v1/?um=constructor%3Aa9c71bba18ffe78a2028d1851bd205ecebc04063c76af8dedab6bceff5c73b24&source=constructor" width="100%" height="500"></iframe>
|
||||||
|
|
||||||
|
{/*<div className={"block mb-24"}>*/}
|
||||||
|
{/* <Stepper steps={[*/}
|
||||||
|
{/* {title: "1", isActive: true},*/}
|
||||||
|
{/* {title: "2", isActive: false},*/}
|
||||||
|
{/* {title: "3", isActive: false},*/}
|
||||||
|
{/* {title: "4", isActive: false},*/}
|
||||||
|
{/* ]}></Stepper>*/}
|
||||||
|
|
||||||
|
{/* <CallbackForm steps={steps.map(step => ({title: step.title, value: step.value}))}>*/}
|
||||||
|
|
||||||
|
{/* </CallbackForm>*/}
|
||||||
|
{/*</div>*/}
|
||||||
|
|
||||||
|
|
||||||
|
<div className={"grid gap-5 grid-cols-1 lg:grid-cols-2 text-center mb-24"}>
|
||||||
|
<div className={"bg-[#E2E2E5] p-10 rounded-[20px]"}>
|
||||||
|
<span className={"block font-bold text-xl mb-4 max-w-[520px] mx-auto"}>Московская область, г. Домодедово, Каширское ш, 4, к.1, оф.330</span>
|
||||||
|
<span className={"block text-[#52525C] opacity-60"}>Адрес</span>
|
||||||
|
</div>
|
||||||
|
<div className={"bg-[#E2E2E5] p-10 rounded-[20px]"}>
|
||||||
|
<span className={"block font-bold text-xl mb-4 max-w-[520px] mx-auto"}>142000, Московская обл,
|
||||||
|
г. Домодедово, а/а 80</span>
|
||||||
|
<span className={"block text-[#52525C] opacity-60"}>Адрес для писем</span>
|
||||||
|
</div>
|
||||||
|
<div className={"bg-[#E2E2E5] p-10 rounded-[20px]"}>
|
||||||
|
<span className={"block font-bold text-xl mb-4 max-w-[520px] mx-auto"}>+7 495 191-97-20</span>
|
||||||
|
<span className={"block text-[#52525C] opacity-60"}>Телефон</span>
|
||||||
|
</div>
|
||||||
|
<div className={"bg-[#E2E2E5] p-10 rounded-[20px]"}>
|
||||||
|
<span className={"block font-bold text-xl mb-4 max-w-[520px] mx-auto"}>9.00-19.00</span>
|
||||||
|
<span className={"block text-[#52525C] opacity-60"}>Часы работы</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</Wrapper>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default Contact;
|
|
@ -44,9 +44,9 @@ const Hero = () => {
|
||||||
<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
|
<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]">
|
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"}>Моторные
|
<h2 className={"text-xl uppercase leading-[100%] xl:text-title-4 2xl:text-title-2 mb-4 xl:mb-8"}>Моторные
|
||||||
масла для <span className={"text-primary"}>дизельных</span> двигателей</h2>
|
масла для <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-[1.14rem] xl:text-subtitle-1 leading-inherit mb-4 xl:mb-6"}>Грузового транспорта и спецтехники</span>
|
||||||
|
|
||||||
<Link href={"/catalog"}
|
<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'}>
|
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'}>
|
||||||
|
@ -66,11 +66,11 @@ const Hero = () => {
|
||||||
<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
|
<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]">
|
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-3 mb-4 xl:mb-8"}>
|
<h2 className={"text-[1.14rem] uppercase leading-[100%] xl:text-title-4 2xl:text-title-3 mb-4 xl:mb-8"}>
|
||||||
<span className={"2xl:text-title-3 text-primary"}>Начать бизнес</span> по продажам
|
<span className={"2xl:text-title-3 text-primary"}>Начать бизнес</span> по продажам
|
||||||
качественного моторного масла </h2>
|
качественного моторного масла </h2>
|
||||||
<span
|
<span
|
||||||
className={"text-sm xl:text-title-2 leading-inherit text-subtitle-1 mb-4 xl:mb-6 text-primary"}>-ЛЕГКО</span>
|
className={"text-xl uppercase 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/"}
|
<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'}>
|
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>
|
||||||
|
@ -88,9 +88,9 @@ const Hero = () => {
|
||||||
<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
|
<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]">
|
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"}>Приглашаем
|
<h2 className={"text-xl leading-[100%] xl:text-title-4 2xl:text-title-2 mb-4 xl:mb-8"}>Приглашаем
|
||||||
к сотрудничеству СТО и автосервисы</h2>
|
к сотрудничеству СТО и автосервисы</h2>
|
||||||
<span className={"text-sm xl:text-subtitle-1 leading-inherit text-subtitle-1 mb-4 xl:mb-6"}>Поставляем масло <span
|
<span className={"text-sm italic leading-[100%] xl:text-subtitle-1 mb-4 xl:mb-6"}>Поставляем масло <span
|
||||||
className={"text-primary"}>RELYNOLLI ®</span> в партнерские СТО на льготных условиях</span>
|
className={"text-primary"}>RELYNOLLI ®</span> в партнерские СТО на льготных условиях</span>
|
||||||
|
|
||||||
<Link href={"https://forms.yandex.ru/u/65e4c1e9eb6146024f8e3234/"}
|
<Link href={"https://forms.yandex.ru/u/65e4c1e9eb6146024f8e3234/"}
|
||||||
|
@ -299,7 +299,7 @@ const NewsSlider = () => {
|
||||||
<h2 className={"text-2xl md:text-3xl leading-[35px] text-white m-0 mr-4 "}>Новости</h2>
|
<h2 className={"text-2xl md:text-3xl leading-[35px] text-white m-0 mr-4 "}>Новости</h2>
|
||||||
<Link
|
<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"}
|
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>
|
href={"/news"}>Больше новостей</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{newsData.data &&
|
{newsData.data &&
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
import LocalAPI from "@/service/localAPI";
|
||||||
|
import {InferGetStaticPropsType} from "next";
|
||||||
|
import {Img} from "react-image";
|
||||||
|
import Wrapper from "@/components/reusable/wrapper";
|
||||||
|
|
||||||
|
const News = (props: InferGetStaticPropsType<typeof getStaticProps>) => {
|
||||||
|
return (
|
||||||
|
<Wrapper title={props.news.name} breadcrumbs={[{name: "Новости", link: "/news"}, {name: props.news.name, link: "/news/" + props.news.code}]} >
|
||||||
|
{
|
||||||
|
props.news.picture && <Img className={"max-h-[250px] lg:max-h-[500px] mb-6 mx-auto rounded-[20px]"} src={"https://relynolli.ru/upload/" + props.news.picture} alt={props.news.name} />
|
||||||
|
}
|
||||||
|
<div className="content text-base lg:text-2xl [&>*]:mb-4 [&_img]:max-h-[250px] [&_img]:lg:max-h-[500px] [&_a]:text-primary break-words" dangerouslySetInnerHTML={{__html: props.news.content}}></div>
|
||||||
|
</Wrapper>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default News
|
||||||
|
|
||||||
|
export async function getStaticPaths() {
|
||||||
|
const service = new LocalAPI()
|
||||||
|
const news = await service.fetchNews()
|
||||||
|
return {
|
||||||
|
paths: news.data!.map(item => ({
|
||||||
|
params: {
|
||||||
|
slug: item.code
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
fallback: "blocking",
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getStaticProps({params: {slug}}: { params: { slug: string } }) {
|
||||||
|
|
||||||
|
const service = new LocalAPI()
|
||||||
|
try {
|
||||||
|
const news = await service.retrieveNews(slug)
|
||||||
|
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
news: news.data!
|
||||||
|
},
|
||||||
|
revalidate: 10 * 60
|
||||||
|
}}
|
||||||
|
catch (e) {
|
||||||
|
return {
|
||||||
|
notFound: true,
|
||||||
|
revalidate: 10 * 60
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
import {Img} from "react-image";
|
||||||
|
import {useQuery} from "@tanstack/react-query";
|
||||||
|
import LocalAPI from "@/service/localAPI";
|
||||||
|
import {News} from "@/service/types/local";
|
||||||
|
import {WrapperDark} from "@/components/reusable/wrapper";
|
||||||
|
import {Skeleton} from "@nextui-org/react";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
const NewsCard = (props: News) => {
|
||||||
|
return (
|
||||||
|
<Link href={"/news/" + props.code}>
|
||||||
|
<div className="w-full max-w-[476px] relative group">
|
||||||
|
<div className="h-64 w-full overflow-hidden relative z-20">
|
||||||
|
<Img className="h-full w-full rounded-[20px] mb-5 object-cover"
|
||||||
|
src={"https://relynolli.ru/upload/" + props.picture}/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-primary w-full opacity-0 group-hover:opacity-100 absolute top-1/2 left-0 z-10 bottom-0 -translate-y-10 group-hover:translate-y-0 transition-all rounded-b-[20px]"></div>
|
||||||
|
|
||||||
|
<div className="z-20 relative p-2">
|
||||||
|
<div className="w-full text-white group-hover:text-black-2 text-xl font-semibold transition-all">{props.name}</div>
|
||||||
|
<div className="text-neutral-400 text-base font-semibold group-hover:text-black-2 transition-all">{new Date(props.date).toLocaleDateString()}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const News = () => {
|
||||||
|
|
||||||
|
const queryNews = useQuery({
|
||||||
|
queryKey: ["news"], queryFn: async () => {
|
||||||
|
const service = new LocalAPI()
|
||||||
|
return await service.fetchNews()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<WrapperDark title={"Новости"} breadcrumbs={[{name: "Новости", link: "/news"}]}>
|
||||||
|
|
||||||
|
<div className="news-container grid grid-cols-1 lg:grid-cols-3 gap-5">
|
||||||
|
{
|
||||||
|
queryNews.data && queryNews.data.data!.map(news => <NewsCard key={news.id} {...news}/>)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
queryNews.isFetching && Array(10).fill(0).map((_, index) => <Skeleton key={index} className={"rounded-[20px] !bg-gray-3"}>
|
||||||
|
<div className="w-full h-64 "></div>
|
||||||
|
</Skeleton> )
|
||||||
|
}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</WrapperDark>)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default News
|
|
@ -1,138 +1,50 @@
|
||||||
import Wrapper from "@/components/reusable/wrapper";
|
import Wrapper from "@/components/reusable/wrapper";
|
||||||
import {Input} from "@nextui-org/input";
|
import {Radio, RadioGroup} from "@nextui-org/radio";
|
||||||
import {Radio, RadioGroup, RadioProps} from "@nextui-org/radio";
|
|
||||||
import {Autocomplete, AutocompleteItem, Button, CircularProgress, cn, Tooltip} from "@nextui-org/react";
|
import {Autocomplete, AutocompleteItem, Button, CircularProgress, cn, Tooltip} from "@nextui-org/react";
|
||||||
import DeliveryIcon from "@/../public/delivery.svg"
|
|
||||||
import StorageIcon from "@/../public/storage.svg"
|
|
||||||
import axios from "axios";
|
|
||||||
import {useQuery, useQueryClient} from "@tanstack/react-query";
|
import {useQuery, useQueryClient} from "@tanstack/react-query";
|
||||||
import {HTMLProps, useEffect, useRef, useState} from "react";
|
import {useState} from "react";
|
||||||
import {useForm, Controller, SubmitHandler} from "react-hook-form";
|
import {SubmitHandler, useForm} from "react-hook-form";
|
||||||
import {cmp} from "semver";
|
|
||||||
import OrderInfo from "@/components/pages/cart/orderInfo";
|
import OrderInfo from "@/components/pages/cart/orderInfo";
|
||||||
import {useRouter} from "next/navigation";
|
import {useRouter} from "next/navigation";
|
||||||
import {Modal, ModalBody, ModalContent, ModalHeader} from "@nextui-org/modal";
|
import {Modal, ModalBody, ModalContent} from "@nextui-org/modal";
|
||||||
import {For} from "@babel/types";
|
|
||||||
import {getUserId} from "@/store/cart";
|
|
||||||
import {nanoid} from "nanoid";
|
import {nanoid} from "nanoid";
|
||||||
|
import localAPI from "@/service/localAPI";
|
||||||
|
import {useDebounce} from "@uidotdev/usehooks";
|
||||||
|
import CdekMap from "../../components/pages/order/cdekMap";
|
||||||
|
import {FormDataValuesType} from "@/components/pages/order/types";
|
||||||
|
import FullNameInput from "@/components/pages/order/fullNameInput";
|
||||||
|
import EmailInput from "@/components/pages/order/emailInput";
|
||||||
|
import PhoneInput from "@/components/pages/order/phoneInput";
|
||||||
|
import DeliveryInput from "@/components/pages/order/deliveryTypeInput";
|
||||||
|
import AddressInput from "@/components/pages/order/addressInput";
|
||||||
|
|
||||||
const getTakeAwayInfo = async () => {
|
const getTakeAwayInfo = async () => {
|
||||||
return (await axios.get("/api/v1/delivery/3")).data
|
return {
|
||||||
}
|
"id": 3,
|
||||||
|
"code": "3",
|
||||||
const normalizeInput = (value: string, previousValue: string) => {
|
"name": "Самовывоз, склад в г. Домодедово",
|
||||||
// return nothing if no value
|
"description": "Вы можете самостоятельно забрать заказ со склада.<div><br><div>Адрес: Московская обл, г. Домодедово, Каширское ш, 4, к.1, грузовая зона (заезд с ул. Пионерская).</div><div><br></div><div>График работы: пн-птн. 10.00-17.30</div><div><br></div><div><font color=\"#00a650\">Для юридических лиц необходима доверенность на получение груза.</font></div></div><br>",
|
||||||
if (!value) return value;
|
"active": "Y",
|
||||||
|
"image": 0
|
||||||
// only allows 0-9 inputs
|
|
||||||
const currentValue = value.replace(/[^\d]/g, '');
|
|
||||||
const cvLength = currentValue.length;
|
|
||||||
|
|
||||||
if (!previousValue || value.length > previousValue.length) {
|
|
||||||
|
|
||||||
if (cvLength < 2) return `+${currentValue} `;
|
|
||||||
// returns: "x", "xx", "xxx" "xxx"
|
|
||||||
if (cvLength < 5) return `+${currentValue.slice(0, 1)} ${currentValue.slice(1)}`;
|
|
||||||
|
|
||||||
// 7 (902) 486 65-00
|
|
||||||
|
|
||||||
// returns: "(xxx)", "(xxx) x", "(xxx) xx", "(xxx) xxx",
|
|
||||||
if (cvLength < 8) return `+${currentValue.slice(0, 1)} (${currentValue.slice(1, 4)}) ${currentValue.slice(4)}`;
|
|
||||||
|
|
||||||
// returns: "(xxx) xxx-", (xxx) xxx-x", "(xxx) xxx-xx", "(xxx) xxx-xxx", "(xxx) xxx-xx"
|
|
||||||
if (cvLength < 10) return `+${currentValue.slice(0, 1)} (${currentValue.slice(1, 4)}) ${currentValue.slice(4, 7)} ${currentValue.slice(7)}`;
|
|
||||||
|
|
||||||
return `+${currentValue.slice(0, 1)} (${currentValue.slice(1, 4)}) ${currentValue.slice(4, 7)} ${currentValue.slice(7, 9)}-${currentValue.slice(9)}`;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ""
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
type FormDataValuesType = {
|
|
||||||
fullName: string
|
|
||||||
phoneNumber: string
|
|
||||||
email: string
|
|
||||||
receivingMethod: string
|
|
||||||
address?: string
|
|
||||||
deliveryTypeId?: number
|
|
||||||
paymentTypeId: number
|
|
||||||
comment: string
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const DeliveryTypeInput = ({deliveryType, ...formProps}: {deliveryType: "take-away" | "delivery"} & RadioProps) => {
|
|
||||||
|
|
||||||
if (deliveryType === 'take-away') return (
|
|
||||||
<div className={"flex-[1_1_calc((100%_/_2)_-_20px)]"}>
|
|
||||||
<Radio className={""}
|
|
||||||
classNames={{
|
|
||||||
label: cn("flex flex-row gap-10"),
|
|
||||||
labelWrapper: cn("w-full"),
|
|
||||||
wrapper: cn("hidden"),
|
|
||||||
base: cn("!max-w-full border-[2px] border-[#8F8F8F] p-5 rounded-[20px] data-[selected=true]:border-primary data-[selected=true]:bg-primary transition-colors")
|
|
||||||
}} {...formProps} value={"take-away"}>
|
|
||||||
<StorageIcon className={"group-data-[selected=true]:fill-[#151515] fill-[#8F8F8F]"}/>
|
|
||||||
<div className="text self-stretch flex justify-between flex-col py-2 w-1/3">
|
|
||||||
<h3 className={"text-subtitle-3 font-bold group-data-[selected=true]:text-[#151515] text-[#8F8F8F]"}>Самовывоз</h3>
|
|
||||||
<p className={"text-subtitle-4 text-[#8F8F8F] group-data-[selected=true]:text-[#151515]"}>
|
|
||||||
г. Домодедово
|
|
||||||
ул. Каширское Шоссе д. 4 к.1
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</Radio>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
|
|
||||||
else return (
|
|
||||||
<Tooltip content={"В разработке"} className={"text-black-2"}>
|
|
||||||
|
|
||||||
<div className={"flex flex-[1_1_calc((100%_/_2)_-_20px)]"}>
|
|
||||||
|
|
||||||
<Radio
|
|
||||||
classNames={{
|
|
||||||
label: cn("flex flex-row gap-10"),
|
|
||||||
labelWrapper: cn("w-full"),
|
|
||||||
wrapper: cn("hidden"),
|
|
||||||
base: cn("!max-w-full border-[2px] border-[#8F8F8F] p-5 rounded-[20px] data-[selected=true]:border-primary data-[selected=true]:bg-primary transition-colors w-full")
|
|
||||||
}} isDisabled={true} {...formProps} value={"delivery"}>
|
|
||||||
|
|
||||||
<DeliveryIcon className={"group-data-[selected=true]:fill-[#151515] fill-[#8F8F8F]"}/>
|
|
||||||
|
|
||||||
<div className="text self-stretch flex justify-between flex-col py-2 w-1/3">
|
|
||||||
<h3 className={"text-subtitle-3 font-bold group-data-[selected=true]:text-[#151515] text-[#8F8F8F]"}>Доставка</h3>
|
|
||||||
<p className={"text-subtitle-4 text-[#8F8F8F] group-data-[selected=true]:text-[#151515]"}>
|
|
||||||
Доставка с помощью ТК
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</Radio>
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const MakeOrder = () => {
|
const MakeOrder = () => {
|
||||||
|
|
||||||
const qc = useQueryClient();
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [modalVisible, setModalVisible] = useState(false);
|
const [modalVisible, setModalVisible] = useState(false);
|
||||||
|
|
||||||
const submitForm: SubmitHandler<FormDataValuesType> = async (data) => {
|
const submitForm: SubmitHandler<FormDataValuesType> = async (data) => {
|
||||||
setModalVisible(true)
|
setModalVisible(true)
|
||||||
const {data: responseData} = await axios.post('/api/v1/order/make', {
|
const service = new localAPI()
|
||||||
fuserId: await getUserId(),
|
const responseData = await service.makeOrder({
|
||||||
|
fuserId: await service.getFuserId(),
|
||||||
fullName: data.fullName,
|
fullName: data.fullName,
|
||||||
email: data.email,
|
phoneNumber: data.phoneNumber.replace(/\D/g, ""),
|
||||||
phoneNumber: data.phoneNumber
|
email: data.email
|
||||||
})
|
})
|
||||||
router.push(responseData.confirmation.confirmation_url)
|
|
||||||
|
router.push(responseData.data!.confirmation.confirmation_url)
|
||||||
}
|
}
|
||||||
|
|
||||||
const takeawayInfo = useQuery({
|
const takeawayInfo = useQuery({
|
||||||
|
@ -140,98 +52,33 @@ const MakeOrder = () => {
|
||||||
queryFn: getTakeAwayInfo
|
queryFn: getTakeAwayInfo
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
const {control, handleSubmit, formState: {errors}, watch, setValue} = useForm<FormDataValuesType>({
|
const {control, handleSubmit, formState: {errors}, watch, setValue} = useForm<FormDataValuesType>({
|
||||||
mode: "all",
|
mode: "all",
|
||||||
defaultValues: {}
|
defaultValues: {}
|
||||||
})
|
})
|
||||||
|
|
||||||
const [phoneNumberPrev, setPhoneNumberPrev] = useState("")
|
|
||||||
const phoneNumberCur = watch("phoneNumber")
|
|
||||||
const receivingMethodCur = watch("receivingMethod")
|
|
||||||
// const formRef = useRef<HTMLFormElement>(null)
|
|
||||||
|
|
||||||
useEffect(() => {
|
const receivingMethodCur = watch("receivingMethod")
|
||||||
if (phoneNumberCur === phoneNumberPrev) return
|
|
||||||
setValue('phoneNumber', normalizeInput(phoneNumberCur, phoneNumberPrev))
|
|
||||||
setPhoneNumberPrev(phoneNumberCur)
|
|
||||||
}, [phoneNumberCur])
|
|
||||||
|
|
||||||
const [isDisabled, setIsDisabled] = useState(false)
|
const [isDisabled, setIsDisabled] = useState(false)
|
||||||
|
|
||||||
const formId = nanoid(5)
|
const formId = nanoid(5)
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
return (
|
return (
|
||||||
<Wrapper title={"Офромление заказа"}
|
<Wrapper title={"Оформление заказа"}
|
||||||
breadcrumbs={[{name: "Корзина", link: "/cart"}, {name: "Оформление заказа", link: "/order/make"}]}>
|
breadcrumbs={[{name: "Корзина", link: "/cart"}, {name: "Оформление заказа", link: "/order/make"}]}>
|
||||||
<div className="grid grid-cols-10 gap-5 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 col-span-10 xl:col-span-7"} 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>
|
||||||
|
|
||||||
<div className="form__fields grid grid-cols-2 gap-5">
|
<div className="form__fields grid grid-cols-2 gap-5">
|
||||||
<Controller name={"fullName"} control={control}
|
<FullNameInput errors={errors} control={control} watch={watch} setValue={setValue}/>
|
||||||
rules={{required: "Поле обязательно для заполнения"}}
|
<EmailInput errors={errors} control={control} watch={watch} setValue={setValue}/>
|
||||||
render={({field}) =>
|
<PhoneInput errors={errors} control={control} watch={watch} setValue={setValue}/>
|
||||||
<Input className={"col-span-2"}
|
|
||||||
classNames={{
|
|
||||||
"inputWrapper": "h-[65px]",
|
|
||||||
"label": "group[data-filled-within=true] group-data-[filled-within=true]:-translate-y-[60px] group-data-[filled-within=true]:text-[#8F8F8F]"
|
|
||||||
}}
|
|
||||||
variant={"bordered"} label={"ФИО"} type={"text"} isRequired
|
|
||||||
isInvalid={!!errors.fullName}
|
|
||||||
errorMessage={errors.fullName && errors.fullName.message}
|
|
||||||
labelPlacement={"outside"} {...field}/>
|
|
||||||
}
|
|
||||||
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Controller control={control}
|
|
||||||
name={"phoneNumber"}
|
|
||||||
rules={{
|
|
||||||
required: "Поле обязательно для заполнения",
|
|
||||||
pattern: {
|
|
||||||
message: "Неверный формат номера телефона",
|
|
||||||
value: /\+\d \(\d{3}\) \d{3} \d{2}-\d{2}/
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
render={({field}) =>
|
|
||||||
<Input
|
|
||||||
classNames={{
|
|
||||||
"inputWrapper": "h-[65px]",
|
|
||||||
"label": "group[data-filled-within=true] group-data-[filled-within=true]:-translate-y-[60px] group-data-[filled-within=true]:text-[#8F8F8F]"
|
|
||||||
}}
|
|
||||||
variant={"bordered"} label={"Телефон"} type={"tel"} isRequired
|
|
||||||
labelPlacement={"outside"} {...field}
|
|
||||||
isInvalid={!!errors.phoneNumber}
|
|
||||||
errorMessage={errors.phoneNumber && errors.phoneNumber.message}
|
|
||||||
/>
|
|
||||||
}/>
|
|
||||||
|
|
||||||
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name={"email"}
|
|
||||||
rules={{
|
|
||||||
required: "Поле обязательно для заполнения", pattern: {
|
|
||||||
value: /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/,
|
|
||||||
message: "Неверный формат эл. почты"
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
render={({field}) =>
|
|
||||||
|
|
||||||
<Input
|
|
||||||
classNames={{
|
|
||||||
"inputWrapper": "h-[65px]",
|
|
||||||
"label": "group[data-filled-within=true] group-data-[filled-within=true]:-translate-y-[60px] group-data-[filled-within=true]:text-[#8F8F8F]"
|
|
||||||
}}
|
|
||||||
variant={"bordered"} label={"E-mail"} type={"email"} isRequired
|
|
||||||
labelPlacement={"outside"} {...field}
|
|
||||||
isInvalid={!!errors.email}
|
|
||||||
errorMessage={errors.email && errors.email.message}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -240,21 +87,11 @@ const MakeOrder = () => {
|
||||||
className={"text-red-500"}>*</span></h2>
|
className={"text-red-500"}>*</span></h2>
|
||||||
|
|
||||||
<RadioGroup classNames={{"wrapper": cn("flex flex-row justify-center gap-10")}}
|
<RadioGroup classNames={{"wrapper": cn("flex flex-row justify-center gap-10")}}
|
||||||
// onValueChange={(value) => {
|
|
||||||
// setValue("receivingMethod", value);
|
|
||||||
|
|
||||||
// value === "take-away" ? setValue("deliveryTypeId", 3) : setValue("deliveryTypeId", undefined)
|
|
||||||
// }}
|
|
||||||
>
|
>
|
||||||
|
<DeliveryInput errors={errors} control={control} watch={watch} setValue={setValue}
|
||||||
<Controller control={control} name={"receivingMethod"} render={({field}) =>
|
deliveryType={"take-away"}/>
|
||||||
<DeliveryTypeInput deliveryType={"take-away"} {...field} />} />
|
<DeliveryInput errors={errors} control={control} watch={watch} setValue={setValue}
|
||||||
<Controller
|
deliveryType={"delivery"}/>
|
||||||
control={control}
|
|
||||||
name={"receivingMethod"}
|
|
||||||
render={({field}) =>
|
|
||||||
<DeliveryTypeInput deliveryType={"delivery"} {...field} />} />
|
|
||||||
|
|
||||||
|
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
</div>
|
</div>
|
||||||
|
@ -278,30 +115,37 @@ const MakeOrder = () => {
|
||||||
<h2 className={"mb-5 font-bold text-subtitle-2"}>Адрес доставки</h2>
|
<h2 className={"mb-5 font-bold text-subtitle-2"}>Адрес доставки</h2>
|
||||||
|
|
||||||
<div className="form__fields grid grid-cols-2 gap-5">
|
<div className="form__fields grid grid-cols-2 gap-5">
|
||||||
<Autocomplete className={"col-span-2"} label={"Адрес"} labelPlacement={"outside"}
|
<AddressInput errors={errors} control={control} watch={watch} setValue={setValue}/>
|
||||||
variant={"bordered"} isRequired
|
|
||||||
inputProps={{
|
|
||||||
classNames: {
|
|
||||||
inputWrapper: cn("h-[65px]"),
|
|
||||||
label: cn("group[data-filled-within=true] group-data-[filled-within=true]:-translate-y-[60px] group-data-[filled-within=true]:text-[#8F8F8F]"),
|
|
||||||
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<AutocompleteItem classNames={{
|
|
||||||
title: cn("text-[#8F8F8F] data-[selected=true]:text-[#151515]"),
|
|
||||||
}} key={'moscow'} value={"moscow"}>Москва</AutocompleteItem>
|
|
||||||
</Autocomplete>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<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>
|
||||||
<div className="form__fields">
|
{
|
||||||
|
<RadioGroup>
|
||||||
|
<Radio value={"cdek-pickup"}>
|
||||||
|
<span>Самовывоз из пункта выдачи СДЕК</span></Radio>
|
||||||
|
<Radio value={"cdek-delivery"}>Доставка курьером СДЕК</Radio>
|
||||||
|
</RadioGroup>
|
||||||
|
}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="form__group bg-gray-card p-7 rounded-[30px]">
|
||||||
|
<h2 className={"mb-5 font-bold text-subtitle-2"}>Самовывоз из пункта выдачи СДЕК</h2>
|
||||||
|
|
||||||
|
{
|
||||||
|
<CdekMap zoom={9} center={[55.784369, 37.711060]}
|
||||||
|
objects={[{name: "Дом", lat: 55.784369, lon: 37.711060, description: "Тестирование адреса пункта ПВЗ", pvzId: '1'}]}
|
||||||
|
watch={watch} setValue={setValue}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form__group bg-gray-card p-7 rounded-[30px]">
|
||||||
|
<h2 className={"mb-5 font-bold text-subtitle-2"}>Доставка курьером СДЕК</h2>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
@ -314,7 +158,6 @@ const MakeOrder = () => {
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
className={"flex flex-col gap-5"}
|
className={"flex flex-col gap-5"}
|
||||||
>
|
>
|
||||||
{/*<Radio value={"cash"} className={"flex-[1_1_calc((100%_/_2)_-_20px)]"} >Оплата наличными</Radio>*/}
|
|
||||||
<Radio value={"online"} className={"flex-[1_1_calc((100%_/_2)_-_20px)]"}>Оплата картами,
|
<Radio value={"online"} className={"flex-[1_1_calc((100%_/_2)_-_20px)]"}>Оплата картами,
|
||||||
SberPay, другие системы</Radio>
|
SberPay, другие системы</Radio>
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
|
@ -333,7 +176,6 @@ const MakeOrder = () => {
|
||||||
Вас скоро перенаправит на страницу оплаты
|
Вас скоро перенаправит на страницу оплаты
|
||||||
</span>
|
</span>
|
||||||
<CircularProgress size={'lg'}/>
|
<CircularProgress size={'lg'}/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
|
|
|
@ -1,23 +1,33 @@
|
||||||
import axios, {AxiosInstance} from "axios";
|
import axios, {AxiosInstance} from "axios";
|
||||||
import {CartItem, Filter, Fuser, News, Product, Wrapper} from "@/service/types/local";
|
import {
|
||||||
|
CartItem,
|
||||||
|
Filter,
|
||||||
|
Fuser, GeocoderMetaData,
|
||||||
|
GinGeoResponse,
|
||||||
|
Invoice,
|
||||||
|
MakeOrderRequest,
|
||||||
|
News,
|
||||||
|
Product, SdekPoint,
|
||||||
|
Wrapper
|
||||||
|
} from "@/service/types/local";
|
||||||
|
|
||||||
class LocalAPI {
|
class LocalAPI {
|
||||||
private instance: AxiosInstance;
|
private instance: AxiosInstance;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.instance = axios.create({
|
this.instance = axios.create({
|
||||||
baseURL: process.env.NODE_ENV === "development" ? "http://localhost:8000": "https://relynolli.ru"
|
baseURL: process.env.NODE_ENV === "development" ? "http://localhost:8000" : "https://relynolli.ru"
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async getCatalogItems(filters: { [key: string]: string[] }, page: number = 1) {
|
async getCatalogItems(filters: { [key: string]: string[] }, page: number = 1) {
|
||||||
|
|
||||||
const {data} = await this.instance.get<Wrapper<Product[]>>('/api/v1/catalog', {
|
const {data} = await this.instance.get<Wrapper<Product[]>>('/api/v1/catalog', {
|
||||||
params: {
|
params: {
|
||||||
limit: 10,
|
limit: 10,
|
||||||
page,
|
page,
|
||||||
...filters
|
...filters
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
@ -44,7 +54,7 @@ class LocalAPI {
|
||||||
}
|
}
|
||||||
|
|
||||||
async createFUser() {
|
async createFUser() {
|
||||||
const {data} = await this.instance.post<Wrapper<{fuser: Fuser, fuserId:number}>>('/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.data!!
|
return data.data!!
|
||||||
}
|
}
|
||||||
|
@ -53,7 +63,8 @@ class LocalAPI {
|
||||||
|
|
||||||
const {data} = await this.instance.get<Wrapper<CartItem[]>>('/api/v1/cart', {
|
const {data} = await this.instance.get<Wrapper<CartItem[]>>('/api/v1/cart', {
|
||||||
params: {
|
params: {
|
||||||
fuserId: await this.getFuserId()}
|
fuserId: await this.getFuserId()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
@ -67,8 +78,7 @@ class LocalAPI {
|
||||||
try {
|
try {
|
||||||
const {data} = await this.instance.patch('/api/v1/cart/item', {productId, quantity, fuserId: await this.getFuserId()})
|
const {data} = await this.instance.patch('/api/v1/cart/item', {productId, quantity, fuserId: await this.getFuserId()})
|
||||||
return data
|
return data
|
||||||
}
|
} catch (e) {
|
||||||
catch (e) {
|
|
||||||
console.log(e)
|
console.log(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,15 +89,57 @@ class LocalAPI {
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
async totalProductPrice() {
|
async totalProductPrice(coupon: string | undefined) {
|
||||||
const {data} = await this.instance.post<Wrapper<{total: number}>>('/api/v1/order/total', {fuserId: await this.getFuserId()})
|
if (coupon) {
|
||||||
|
const {data} = await this.instance.post<Wrapper<{ total: number, items: { cart: CartItem, discount: { name: string, value: number } }[] }>>('/api/v1/order/total', {fuserId: await this.getFuserId(), coupon})
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
const {data} = await this.instance.post<Wrapper<{ total: number, items: { cart: CartItem, discount: { name: string, value: number } }[] }>>('/api/v1/order/total', {fuserId: await this.getFuserId()})
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
async fetchNews() {
|
|
||||||
|
async fetchNews(limit: number = 10, page: number = 1) {
|
||||||
const {data} = await this.instance.get<Wrapper<News[]>>("/api/v1/news")
|
const {data} = await this.instance.get<Wrapper<News[]>>("/api/v1/news")
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fetchArticles(limit: number = 10, page: number = 1) {
|
||||||
|
const {data} = await this.instance.get<Wrapper<News[]>>("/api/v1/articles")
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchAddresses(q: string | undefined) {
|
||||||
|
if (!q) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
const {data} = await this.instance.get<Wrapper<GinGeoResponse[]>>("/api/v1/address/search", {
|
||||||
|
params: {q}
|
||||||
|
})
|
||||||
|
return data.data ? data.data : []
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchSdekPoints(lat: number, lon: number) {
|
||||||
|
const {data} = await this.instance.get<Wrapper<SdekPoint[]>>("/api/v1/cdek/points", {
|
||||||
|
params: {lat, lon}
|
||||||
|
})
|
||||||
|
return data.data? data.data: []
|
||||||
|
}
|
||||||
|
|
||||||
|
async retrieveNews(slug: string) {
|
||||||
|
const {data} = await this.instance.get<Wrapper<News>>("/api/v1/news/" + slug)
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
async retrieveArticle(slug: string) {
|
||||||
|
const {data} = await this.instance.get<Wrapper<News>>("/api/v1/articles/" + slug)
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
async makeOrder(req: MakeOrderRequest) {
|
||||||
|
const {data} = await this.instance.post<Wrapper<Invoice>>('/api/v1/order/make', req)
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default LocalAPI;
|
export default LocalAPI;
|
|
@ -96,3 +96,194 @@ export interface News {
|
||||||
date: string
|
date: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MakeOrderRequest {
|
||||||
|
fuserId: number
|
||||||
|
phoneNumber: string
|
||||||
|
fullName: string
|
||||||
|
email: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Invoice {
|
||||||
|
id: string
|
||||||
|
status: string
|
||||||
|
amount: Amount
|
||||||
|
description: string
|
||||||
|
recipient: Recipient
|
||||||
|
created_at: string
|
||||||
|
confirmation: Confirmation
|
||||||
|
paid: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Amount {
|
||||||
|
value: string
|
||||||
|
currency: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Recipient {
|
||||||
|
account_id: string
|
||||||
|
gateway_id: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Confirmation {
|
||||||
|
type: string
|
||||||
|
confirmation_url: string
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// GEOCODER API
|
||||||
|
export interface GinGeoResponse {
|
||||||
|
metaDataProperty: MetaDataProperty
|
||||||
|
name: string
|
||||||
|
description: string
|
||||||
|
boundedBy: BoundedBy
|
||||||
|
uri: string
|
||||||
|
Point: Point
|
||||||
|
}
|
||||||
|
export interface MetaDataProperty {
|
||||||
|
GeocoderMetaData: GeocoderMetaData
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GeocoderMetaData {
|
||||||
|
precision: string
|
||||||
|
text: string
|
||||||
|
kind: string
|
||||||
|
Address: Address
|
||||||
|
AddressDetails: AddressDetails
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Address {
|
||||||
|
country_code: string
|
||||||
|
formatted: string
|
||||||
|
postal_code: string
|
||||||
|
Components: Component[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Component {
|
||||||
|
kind: string
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AddressDetails {
|
||||||
|
Country: Country
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Country {
|
||||||
|
AddressLine: string
|
||||||
|
CountryNameCode: string
|
||||||
|
CountryName: string
|
||||||
|
AdministrativeArea: AdministrativeArea
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdministrativeArea {
|
||||||
|
AdministrativeAreaName: string
|
||||||
|
SubAdministrativeArea: SubAdministrativeArea
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SubAdministrativeArea {
|
||||||
|
SubAdministrativeAreaName: string
|
||||||
|
Locality: Locality
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Locality {
|
||||||
|
LocalityName: string
|
||||||
|
Thoroughfare: Thoroughfare
|
||||||
|
DependentLocality: DependentLocality
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Thoroughfare {
|
||||||
|
ThoroughfareName: string
|
||||||
|
Premise: Premise
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Premise {
|
||||||
|
PremiseNumber: string
|
||||||
|
PostalCode: PostalCode
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PostalCode {
|
||||||
|
PostalCodeNumber: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DependentLocality {
|
||||||
|
DependentLocalityName: string
|
||||||
|
Thoroughfare: Thoroughfare2
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Thoroughfare2 {
|
||||||
|
ThoroughfareName: string
|
||||||
|
Premise: Premise2
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Premise2 {
|
||||||
|
PremiseNumber: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BoundedBy {
|
||||||
|
Envelope: Envelope
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Envelope {
|
||||||
|
lowerCorner: string
|
||||||
|
upperCorner: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Point {
|
||||||
|
pos: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// SDEK
|
||||||
|
|
||||||
|
export interface SdekPoint {
|
||||||
|
code: string
|
||||||
|
name: string
|
||||||
|
address_comment: string
|
||||||
|
work_time: string
|
||||||
|
phones: Phone[]
|
||||||
|
email: string
|
||||||
|
note: string
|
||||||
|
type: string
|
||||||
|
owner_code: string
|
||||||
|
take_only: boolean
|
||||||
|
is_handout: boolean
|
||||||
|
is_reception: boolean
|
||||||
|
is_dressing_room: boolean
|
||||||
|
have_cashless: boolean
|
||||||
|
have_cash: boolean
|
||||||
|
allowed_cod: boolean
|
||||||
|
work_time_list: WorkTimeList[]
|
||||||
|
weight_max: number
|
||||||
|
location: Location
|
||||||
|
fulfillment: boolean
|
||||||
|
nearest_station: string
|
||||||
|
nearest_metro_station: string
|
||||||
|
office_image_list: OfficeImageList[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Phone {
|
||||||
|
number: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WorkTimeList {
|
||||||
|
day: number
|
||||||
|
time: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Location {
|
||||||
|
country_code: string
|
||||||
|
region_code: number
|
||||||
|
region: string
|
||||||
|
city_code: number
|
||||||
|
city: string
|
||||||
|
fias_guid: string
|
||||||
|
postal_code: string
|
||||||
|
longitude: number
|
||||||
|
latitude: number
|
||||||
|
address: string
|
||||||
|
address_full: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OfficeImageList {
|
||||||
|
url: string
|
||||||
|
}
|
||||||
|
|
|
@ -12,8 +12,16 @@
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"jsx": "preserve",
|
"jsx": "preserve",
|
||||||
"incremental": true,
|
"incremental": true,
|
||||||
|
"typeRoots": [
|
||||||
|
"./types",
|
||||||
|
"./node_modules/@types",
|
||||||
|
"./node_modules/@yandex/ymaps3-types"
|
||||||
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"]
|
"@/*": ["./src/*"],
|
||||||
|
"ymaps3": [
|
||||||
|
"./node_modules/@yandex/ymaps3-types"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
import { YMap } from 'ymaps3';
|
||||||
|
|
||||||
|
declare let map: YMap;
|
Loading…
Reference in New Issue