Sure! Pl
|
|
@ -0,0 +1,35 @@
|
|||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
.yarn/install-state.gz
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
|
||||
|
||||
## Getting Started
|
||||
|
||||
First, run the development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
# or
|
||||
pnpm dev
|
||||
# or
|
||||
bun dev
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
|
||||
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
||||
|
||||
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about Next.js, take a look at the following resources:
|
||||
|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||
|
||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
|
||||
|
||||
## Deploy on Vercel
|
||||
|
||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||
|
||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
|
||||
|
||||
===========================================================
|
||||
|
||||
## Sending to backend
|
||||
|
||||
git commit
|
||||
|
||||
```bash
|
||||
|
||||
npm run build
|
||||
|
||||
npm run out
|
||||
|
||||
```
|
||||
|
||||
check build version on http://localhost:3000
|
||||
|
||||
git commit build
|
||||
|
||||
git push
|
||||
|
||||
=======================================
|
||||
|
||||
## Serve on ubuntu
|
||||
|
||||
```bash
|
||||
|
||||
npm install serve@14.2.1
|
||||
|
||||
npm run out
|
||||
|
||||
```
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
import Loader from "@/components/ui/Loader";
|
||||
|
||||
export default function Loading() {
|
||||
return <Loader />;
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
import { LayoutWithSidebar } from "@/components/page/layout-with-sidebar";
|
||||
import { getAbout } from "@/services/about";
|
||||
import { cookies } from "next/headers";
|
||||
|
||||
export default async function AboutPage() {
|
||||
const lang = cookies().get("lang")?.value ?? "ru";
|
||||
|
||||
const data = await getAbout(lang);
|
||||
|
||||
const aboutText = lang === "en" ? "About us" : "Коротко о нас";
|
||||
|
||||
return (
|
||||
<LayoutWithSidebar second={aboutText} title={aboutText}>
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: data,
|
||||
}}
|
||||
className="text-[16px] aboutus text-p flex flex-col items-start gap-6 leading-[150%] pb-10"
|
||||
/>
|
||||
</LayoutWithSidebar>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
import { Sidebar } from "@/components/ui/Sidebar";
|
||||
import React from "react";
|
||||
|
||||
const CompanyLayout = ({ children }: { children: React.ReactNode }) => {
|
||||
return (
|
||||
<div className="lg:bg-blueBg bg-transparent">
|
||||
<div className="container">
|
||||
<div className="flex w-full">
|
||||
<aside className="w-[25%] hidden tab:block">
|
||||
<Sidebar />
|
||||
</aside>
|
||||
<main className="mt-12 rounded-[4px] lg:ml-10 md:pl-6 pl-0 lg:w-[75%] w-full relative">
|
||||
{children}
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CompanyLayout;
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import Image from "next/image";
|
||||
|
||||
import { GreenBtn } from "@/components/ui/Buttons";
|
||||
|
||||
import { useAppDispatch, useAppSelector } from "@/redux/hooks";
|
||||
import { selectHeader, setShowInput } from "@/redux/slices/headerSlice";
|
||||
import { baseAPI } from "@/lib/API";
|
||||
import { NewsPageType } from "@/lib/types/NewsPage.type";
|
||||
import Link from "next/link";
|
||||
import { BreadCrumbs } from "@/components/ui/bread-crumbs";
|
||||
import { Title } from "@/components/ui/title";
|
||||
import Loader from "@/components/ui/Loader";
|
||||
|
||||
export default function SingleNewsPage({ params }: { params: { id: string } }) {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const { activeLang } = useAppSelector(selectHeader);
|
||||
const [newsItemData, setNewsItemData] = React.useState<NewsPageType>();
|
||||
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const response = await fetch(`${baseAPI}news/${params.id}`, {
|
||||
headers: {
|
||||
"Accept-Language": activeLang.localization,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error("error");
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
setNewsItemData(data);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
fetchData();
|
||||
dispatch(setShowInput(false));
|
||||
}, [activeLang.localization]);
|
||||
|
||||
return (
|
||||
<div className="section-mb w-full">
|
||||
<BreadCrumbs second="Новости" path="/news" third="Статья" />
|
||||
{newsItemData ? (
|
||||
<div className="mb-5">
|
||||
<Title text={newsItemData.data.title} />
|
||||
</div>
|
||||
) : (
|
||||
<Loader />
|
||||
)}
|
||||
|
||||
<div className="flex items-center justify-between mb-[44px] md:mb-8">
|
||||
<p className="text-[#919599]">{newsItemData?.data.published_at}</p>
|
||||
</div>
|
||||
|
||||
{newsItemData?.data?.featured_images?.[0]?.path && (
|
||||
<Image
|
||||
height={480}
|
||||
width={833}
|
||||
src={newsItemData?.data.featured_images[0]?.path || ""}
|
||||
alt="картинка"
|
||||
className="mb-6 max-h-[480px] object-cover w-full"
|
||||
/>
|
||||
)}
|
||||
<div className="mb-[50px]">
|
||||
{newsItemData && (
|
||||
<>
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: newsItemData.data.content_html,
|
||||
}}
|
||||
className="text-[16px] flex flex-col gap-6 leading-[150%] seperate-news-html"
|
||||
/>
|
||||
<br />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Link href="/news" className="flex justify-center">
|
||||
<GreenBtn text="Все новости" px />
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
"use client";
|
||||
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
import { useLang } from "@/utils/useLang";
|
||||
import { useAppSelector } from "@/redux/hooks";
|
||||
import { selectHeader } from "@/redux/slices/headerSlice";
|
||||
import { NewsData } from "@/lib/types/NewsData.type";
|
||||
import { baseAPI } from "@/lib/API";
|
||||
import Loader from "@/components/ui/Loader";
|
||||
import clsx from "clsx";
|
||||
import { Card } from "@/components/shared/Card";
|
||||
import { BorderBtn } from "@/components/ui/Buttons";
|
||||
import { Pagination } from "@/components/ui/Pagination";
|
||||
import { LayoutWithSidebar } from "@/components/page/layout-with-sidebar";
|
||||
|
||||
const News = () => {
|
||||
const { activeLang } = useAppSelector(selectHeader);
|
||||
|
||||
const menu = ["Новости", "СМИ о нас"];
|
||||
const [current, setCurrent] = React.useState<number>(1);
|
||||
const [perPage, setPerPage] = React.useState<number>(6);
|
||||
const [totalNews, setTotalNews] = React.useState<number>(0);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
window.scrollTo({ behavior: "smooth", top: 0 });
|
||||
}, [current]);
|
||||
|
||||
const [newsData, setNewsData] = React.useState<NewsData>();
|
||||
|
||||
const fetchNews = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await fetch(
|
||||
`${baseAPI}news?page=${current}&per_page=${perPage}`,
|
||||
{
|
||||
headers: {
|
||||
"Accept-Language": activeLang.localization,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Fetch failed with status ${response.status}`);
|
||||
}
|
||||
|
||||
const data: NewsData = await response.json();
|
||||
setNewsData(data);
|
||||
setTotalNews(data.meta.total);
|
||||
} catch (error) {
|
||||
console.error("Fetch error:", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
fetchNews();
|
||||
}, [current, perPage, activeLang.localization]);
|
||||
|
||||
const handleOnClickButton = () => {
|
||||
setPerPage((prev) => prev + 6);
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return <Loader className="h-[500px] mx-auto w-full " />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<LayoutWithSidebar
|
||||
title={useLang("News", "Новости", activeLang.localization)}
|
||||
second={useLang("News", "Новости", activeLang.localization)}
|
||||
cursor={false}
|
||||
>
|
||||
<div>
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-col">
|
||||
<div className="hidden sm:flex justify-between items-center pb-[5px] ">
|
||||
<div className="pointer-events-none opacity-0 flex items-center gap-5">
|
||||
{menu.map((item, i) => (
|
||||
<p key={i} className="cursor-pointer leading-[130%]">
|
||||
{item}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={clsx(
|
||||
"mb-[48px] lg:mb-[108px]",
|
||||
"grid grid-cols-1 sm:grid-cols-2 gap-5 sm:gap-8 lg:gap-y-10"
|
||||
)}
|
||||
>
|
||||
{newsData &&
|
||||
newsData.data.map((item, i) => (
|
||||
<Card
|
||||
key={i}
|
||||
id={item.id}
|
||||
title={item.title}
|
||||
date={item.published_at}
|
||||
img={
|
||||
item.featured_images.length > 0
|
||||
? item.featured_images[0].path
|
||||
: newsData.data[0].featured_images[0].path
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="hidden sm:flex flex-col gap-6 w-full max-w-[180px] mx-auto justify-center items-center">
|
||||
{newsData && totalNews > perPage && perPage >= totalNews && (
|
||||
<div onClick={handleOnClickButton}>
|
||||
<BorderBtn px text={"Показать ещё"} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{newsData?.meta && (
|
||||
<Pagination
|
||||
current={current}
|
||||
setCurrent={setCurrent}
|
||||
totalPage={newsData?.meta.total}
|
||||
lastPage={newsData?.meta.last_page}
|
||||
currentPage={newsData?.meta.current_page}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</LayoutWithSidebar>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default News;
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import { LayoutWithSidebar } from "@/components/page/layout-with-sidebar";
|
||||
import { getServices } from "@/services/services";
|
||||
|
||||
export default async function AdvertisingPage() {
|
||||
const { data } = await getServices();
|
||||
|
||||
return (
|
||||
<LayoutWithSidebar
|
||||
title={data ? data[8].title : ""}
|
||||
third={data ? data[8].title : ""}
|
||||
>
|
||||
<div
|
||||
className="select-inner"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: data ? data[8].content : "",
|
||||
}}
|
||||
/>
|
||||
</LayoutWithSidebar>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import { LayoutWithSidebar } from "@/components/page/layout-with-sidebar";
|
||||
import { getServices } from "@/services/services";
|
||||
|
||||
export default async function AdvertisingPage() {
|
||||
const data = await getServices();
|
||||
|
||||
return (
|
||||
<LayoutWithSidebar
|
||||
title={data?.data ? data.data[1].title : ""}
|
||||
third={data?.data ? data.data[1].title : ""}
|
||||
>
|
||||
<div
|
||||
className="select-inner"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: data ? data.data[1].content : "",
|
||||
}}
|
||||
/>
|
||||
</LayoutWithSidebar>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import { LayoutWithSidebar } from "@/components/page/layout-with-sidebar";
|
||||
import { getServices } from "@/services/services";
|
||||
|
||||
export default async function BusinessTours() {
|
||||
const { data } = await getServices();
|
||||
|
||||
return (
|
||||
<LayoutWithSidebar
|
||||
title={data ? data[7].title : ""}
|
||||
third={data ? data[7].title : ""}
|
||||
>
|
||||
<div
|
||||
className="select-inner"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: data ? data[7].content : "",
|
||||
}}
|
||||
/>
|
||||
</LayoutWithSidebar>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import { LayoutWithSidebar } from "@/components/page/layout-with-sidebar";
|
||||
import { getServices } from "@/services/services";
|
||||
|
||||
export default async function CertificationPage() {
|
||||
const data = await getServices();
|
||||
|
||||
return (
|
||||
<LayoutWithSidebar
|
||||
title={data?.data ? data.data[3].title : ""}
|
||||
third={data?.data ? data.data[3].title : ""}
|
||||
>
|
||||
<div
|
||||
className="select-inner"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: data ? data.data[3].content : "",
|
||||
}}
|
||||
/>
|
||||
</LayoutWithSidebar>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import { LayoutWithSidebar } from "@/components/page/layout-with-sidebar";
|
||||
import { getServices } from "@/services/services";
|
||||
|
||||
export default async function ForwardingPage() {
|
||||
const data = await getServices();
|
||||
|
||||
return (
|
||||
<LayoutWithSidebar
|
||||
title={data?.data ? data.data[4].title : ""}
|
||||
third={data?.data ? data.data[4].title : ""}
|
||||
>
|
||||
<div
|
||||
className="select-inner"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: data ? data.data[4].content : "",
|
||||
}}
|
||||
/>
|
||||
</LayoutWithSidebar>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import { LayoutWithSidebar } from "@/components/page/layout-with-sidebar";
|
||||
import { getServices } from "@/services/services";
|
||||
|
||||
export default async function HybridsPage() {
|
||||
const data = await getServices();
|
||||
|
||||
return (
|
||||
<LayoutWithSidebar
|
||||
title={data?.data ? data.data[2].title : ""}
|
||||
third={data?.data ? data.data[2].title : ""}
|
||||
>
|
||||
<div
|
||||
className="select-inner"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: data ? data.data[2].content : "",
|
||||
}}
|
||||
/>
|
||||
</LayoutWithSidebar>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
import Loader from "@/components/ui/Loader";
|
||||
|
||||
export default function Loading() {
|
||||
return <Loader />;
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import { LayoutWithSidebar } from "@/components/page/layout-with-sidebar";
|
||||
import { getServices } from "@/services/services";
|
||||
|
||||
export default async function MeetingsPage() {
|
||||
const data = await getServices();
|
||||
|
||||
return (
|
||||
<LayoutWithSidebar
|
||||
title={data?.data ? data.data[9].title : ""}
|
||||
third={data?.data ? data.data[9].title : ""}
|
||||
>
|
||||
<div
|
||||
className="select-inner"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: data ? data.data[9].content : "",
|
||||
}}
|
||||
/>
|
||||
</LayoutWithSidebar>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import { LayoutWithSidebar } from "@/components/page/layout-with-sidebar";
|
||||
import { getServices } from "@/services/services";
|
||||
|
||||
export default async function MissionsPage() {
|
||||
const data = await getServices();
|
||||
|
||||
return (
|
||||
<LayoutWithSidebar
|
||||
title={data?.data ? data.data[10].title : ""}
|
||||
third={data?.data ? data.data[10].title : ""}
|
||||
>
|
||||
<div
|
||||
className="select-inner"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: data ? data.data[10].content : "",
|
||||
}}
|
||||
/>
|
||||
</LayoutWithSidebar>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import { LayoutWithSidebar } from "@/components/page/layout-with-sidebar";
|
||||
import { getServices } from "@/services/services";
|
||||
|
||||
export default async function OrganizationPage() {
|
||||
const data = await getServices();
|
||||
|
||||
return (
|
||||
<LayoutWithSidebar
|
||||
title={data?.data ? data.data[0].title : ""}
|
||||
third={data?.data ? data.data[0].title : ""}
|
||||
>
|
||||
<div
|
||||
className="select-inner"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: data?.data ? data.data[0].content : "",
|
||||
}}
|
||||
/>
|
||||
</LayoutWithSidebar>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import { LayoutWithSidebar } from "@/components/page/layout-with-sidebar";
|
||||
import { getServices } from "@/services/services";
|
||||
|
||||
export default async function OutsourcingPage() {
|
||||
const data = await getServices();
|
||||
|
||||
return (
|
||||
<LayoutWithSidebar
|
||||
title={data?.data ? data.data[5].title : ""}
|
||||
third={data?.data ? data.data[5].title : ""}
|
||||
>
|
||||
<div
|
||||
className="select-inner"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: data ? data.data[5].content : "",
|
||||
}}
|
||||
/>
|
||||
</LayoutWithSidebar>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import { LayoutWithSidebar } from "@/components/page/layout-with-sidebar";
|
||||
import { getServices } from "@/services/services";
|
||||
|
||||
export default async function PersonalTrainingPage() {
|
||||
const data = await getServices();
|
||||
|
||||
return (
|
||||
<LayoutWithSidebar
|
||||
title={data?.data ? data.data[6].title : ""}
|
||||
third={data?.data ? data.data[6].title : ""}
|
||||
>
|
||||
<div
|
||||
className="select-inner"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: data ? data.data[6].content : "",
|
||||
}}
|
||||
/>
|
||||
</LayoutWithSidebar>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
"use client";
|
||||
|
||||
import { LayoutWithSidebar } from "@/components/page/layout-with-sidebar";
|
||||
import { baseAPI } from "@/lib/API";
|
||||
import { useAppSelector } from "@/redux/hooks";
|
||||
import { selectHeader } from "@/redux/slices/headerSlice";
|
||||
import { useLang } from "@/utils/useLang";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
const Visitors = () => {
|
||||
const [visitorsData, setVisitorsData] = useState<string>();
|
||||
const { activeLang } = useAppSelector(selectHeader);
|
||||
|
||||
const fetchVisitors = async () => {
|
||||
try {
|
||||
const res = await fetch(`${baseAPI}settings/visitors_page`, {
|
||||
headers: {
|
||||
"Accept-Language": activeLang.localization,
|
||||
},
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error("Error");
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
setVisitorsData(data);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchVisitors();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<LayoutWithSidebar
|
||||
title={useLang(
|
||||
"Information for visitors",
|
||||
"Информация для посетителей",
|
||||
activeLang.localization
|
||||
)}
|
||||
second={useLang("For visitors", "Посетителям", activeLang.localization)}
|
||||
>
|
||||
<div
|
||||
dangerouslySetInnerHTML={{ __html: visitorsData ? visitorsData : "" }}
|
||||
className="flex flex-col gap-1 select-inner"
|
||||
/>
|
||||
</LayoutWithSidebar>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Visitors;
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
"use client";
|
||||
|
||||
import { LayoutWithSidebar } from "@/components/page/layout-with-sidebar";
|
||||
import { baseAPI } from "@/lib/API";
|
||||
import { useAppSelector } from "@/redux/hooks";
|
||||
import { selectHeader } from "@/redux/slices/headerSlice";
|
||||
import { useLang } from "@/utils/useLang";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
const RulesForVisitors = () => {
|
||||
const [visitorsData, setVisitorsData] = useState<string>();
|
||||
const { activeLang } = useAppSelector(selectHeader);
|
||||
|
||||
const fetchVisitors = async () => {
|
||||
try {
|
||||
const res = await fetch(`${baseAPI}settings/visiting_rules`, {
|
||||
headers: {
|
||||
"Accept-Language": activeLang.localization,
|
||||
},
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error("Error");
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
setVisitorsData(data);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchVisitors();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<LayoutWithSidebar
|
||||
title={useLang(
|
||||
"Entrance rules",
|
||||
"Порядок регистрации посетителей",
|
||||
activeLang.localization
|
||||
)}
|
||||
second={useLang("Visitors", "Посетителям", activeLang.localization)}
|
||||
path="/visitors"
|
||||
third={useLang(
|
||||
"Entrance rules",
|
||||
"Порядок регистрации посетителей",
|
||||
activeLang.localization
|
||||
)}
|
||||
>
|
||||
<div
|
||||
dangerouslySetInnerHTML={{ __html: visitorsData ? visitorsData : "" }}
|
||||
className="flex flex-col gap-1 select-inner"
|
||||
/>
|
||||
</LayoutWithSidebar>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RulesForVisitors;
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
import { EventPageButtons } from "@/components/shared/event-page-buttons";
|
||||
import { getEventPage } from "@/services/calendar";
|
||||
import { cookies } from "next/headers";
|
||||
import Image from "next/image";
|
||||
|
||||
export default async function EventPage({
|
||||
params,
|
||||
}: {
|
||||
params: { id: string };
|
||||
}) {
|
||||
const { id } = params;
|
||||
|
||||
const lang = cookies().get("lang")?.value ?? "ru";
|
||||
|
||||
const data = await getEventPage(id, lang);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col container gap-20 pt-16 section-mb">
|
||||
<h1 className="md:text-[48px] text-[28px] text-ON_SURFACE leading-[115%] font-medium">
|
||||
{data?.title ? data?.title : null}
|
||||
</h1>
|
||||
|
||||
<div className="flex flex-col lg:flex-row justify-between gap-6">
|
||||
<div className="flex flex-col gap-20 w-full">
|
||||
<div className="flex flex-col gap-8 event-block ">
|
||||
<div className="flex justify-between items-center">
|
||||
<h4>{lang === "en" ? "Date" : "Дата"}</h4>
|
||||
<h5>{data?.date}</h5>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
{data?.location && (
|
||||
<>
|
||||
<div className="flex justify-between items-center">
|
||||
<h4>{lang === "en" ? "Venue" : "Место"}</h4>
|
||||
<h5>{data?.location}</h5>
|
||||
</div>
|
||||
<hr />
|
||||
</>
|
||||
)}
|
||||
|
||||
{data?.organizers.length > 0 && (
|
||||
<>
|
||||
<div className="flex justify-between items-center">
|
||||
<h4>{lang === "en" ? "Organiser" : "Организатор"}</h4>
|
||||
<h5>{data?.organizers[0]?.name}</h5>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{data?.coorganizers.length > 0 && (
|
||||
<>
|
||||
<hr />
|
||||
|
||||
<div className="flex justify-between items-center">
|
||||
<h4>{lang === "en" ? "Co-organiser" : "Со-организатор"}</h4>
|
||||
|
||||
<h5>{data?.coorganizers[0]?.name}</h5>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<EventPageButtons
|
||||
once={Boolean(data?.our)}
|
||||
details={data?.url_detailed}
|
||||
register={data?.url_registration}
|
||||
visit={data?.url_web}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Image
|
||||
src={data?.images?.[0]?.path || ""}
|
||||
width={392}
|
||||
height={392}
|
||||
alt="event image"
|
||||
className="size-[392px] object-cover"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-8">
|
||||
<div className="flex flex-col lg:flex-row gap-6">
|
||||
<h4 className="text_24 lg:flex-[0_0_392px]">
|
||||
{lang === "en" ? "Description" : "Описание"}
|
||||
</h4>
|
||||
<p className="flex-1 text_16">{data?.description}</p>
|
||||
</div>
|
||||
|
||||
{data?.event_topic && (
|
||||
<>
|
||||
<hr className="border-OUTLINE_VAR" />
|
||||
|
||||
<div className="flex flex-col lg:flex-row gap-6">
|
||||
<h4 className="text_24 lg:flex-[0_0_392px]">
|
||||
{lang === "en" ? "Theme of event" : "Тематика мероприятия"}
|
||||
</h4>
|
||||
<div
|
||||
className="flex-1 text_16"
|
||||
dangerouslySetInnerHTML={{ __html: data?.event_topic }}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
import { EventCard } from "@/components/shared/event-card";
|
||||
import { BreadCrumbs } from "@/components/ui/bread-crumbs";
|
||||
import { Title } from "@/components/ui/title";
|
||||
import { getCalendar } from "@/services/calendar";
|
||||
import { Metadata } from "next";
|
||||
import { cookies } from "next/headers";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "TurkmenExpo | Calendar",
|
||||
};
|
||||
|
||||
export default async function CalendarPage() {
|
||||
const lang = cookies().get("lang")?.value ?? "ru";
|
||||
|
||||
const data = await getCalendar(lang);
|
||||
|
||||
const title = lang === "en" ? "Calendar of events" : "Календарь мероприятий";
|
||||
return (
|
||||
<div className="section-mb">
|
||||
<div className="container flex flex-col items-start pt-5 gap-10 md:gap-12">
|
||||
<div>
|
||||
<div className="mb-[24px]">
|
||||
<BreadCrumbs second={title} />
|
||||
</div>
|
||||
<Title text={title} />
|
||||
</div>
|
||||
<div className="flex flex-col gap-6 w-full">
|
||||
{data.data.map((item, i) => (
|
||||
<EventCard dark={true} key={i} {...item} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
import React from "react";
|
||||
|
||||
import { BreadCrumbs } from "@/components/ui/bread-crumbs";
|
||||
import { ContactsForm } from "@/components/shared/contacts-form";
|
||||
import { cookies } from "next/headers";
|
||||
|
||||
export default async function ContactsPage() {
|
||||
const lang = cookies().get("lang")?.value;
|
||||
|
||||
return (
|
||||
<main className="bg-blueBg h-full w-full">
|
||||
<div className="container flex flex-col items-start">
|
||||
<div className="mt-5">
|
||||
<BreadCrumbs second={lang === "ru" ? "Контакты" : "Contacts"} />
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 w-full">
|
||||
<ContactsForm />
|
||||
|
||||
<div className="p-6 bg-bg_surface_container rounded-[8px]">
|
||||
<h2 className="h2 mb-10 xl:mb-8 text-3xl font-normal">Контакты</h2>
|
||||
|
||||
<div className="flex flex-col gap-20">
|
||||
<div className="flex items-center gap-6">
|
||||
<img src="/assets/icons/contacts/address.svg" alt="address" />
|
||||
|
||||
<div>
|
||||
<h3 className="text-xl mb-2">Адрес:</h3>
|
||||
<address className="text-base normal not-italic">
|
||||
744000, г. Ашхабад, просп. Битарап Туркменистан, 183
|
||||
</address>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-6">
|
||||
<img src="/assets/icons/contacts/phone.svg" alt="phone" />
|
||||
|
||||
<div>
|
||||
<h3 className="text-xl mb-2"></h3>
|
||||
<h4 className="text-base normal">
|
||||
+99371871814; 99363719588
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-6">
|
||||
<img src="/assets/icons/contacts/mail.svg" alt="email" />
|
||||
|
||||
<div>
|
||||
<h3 className="text-xl mb-2">Email:</h3>
|
||||
<h4 className="text-base normal">contact@turkmenexpo.com</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative w-full h-[728px] mt-12 google-map">
|
||||
<iframe
|
||||
src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3148.67827952586!2d58.29659607507902!3d37.8912058554459!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x3f7003944259cb1d%3A0xafc893357e4b0d2!2z0KLQvtGA0LPQvtCy0L4t0L_RgNC-0LzRi9GI0LvQtdC90L3QsNGPINC_0LDQu9Cw0YLQsCDQotGD0YDQutC80LXQvdC40YHRgtCw0L3QsA!5e0!3m2!1sru!2s!4v1713164734635!5m2!1sru!2s"
|
||||
className="absolute inset-0 w-full h-full"
|
||||
style={{ border: 0 }}
|
||||
allowFullScreen
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
import { Footer } from "@/components/shared/Footer";
|
||||
import { Header } from "@/components/shared/Header";
|
||||
import { PropsWithChildren } from "react";
|
||||
|
||||
const layout = ({ children }: PropsWithChildren) => {
|
||||
return (
|
||||
<div className="flex flex-col h-full wrapper w-full overflow-x-hidden relative">
|
||||
<Header />
|
||||
<main className="flex-auto bg-blueBg">{children}</main>
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default layout;
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
import { Events } from "@/components/shared/Events";
|
||||
import { News } from "@/components/shared/News";
|
||||
import { Partners } from "@/components/shared/Partners";
|
||||
import { Slider } from "@/components/shared/Slider";
|
||||
import { Video } from "@/components/shared/Video";
|
||||
import Loader from "@/components/ui/Loader";
|
||||
import { cookies } from "next/headers";
|
||||
import { Suspense } from "react";
|
||||
|
||||
export default async function HomePage() {
|
||||
const lang = cookies().get("lang")?.value ?? "ru";
|
||||
|
||||
return (
|
||||
<div className="bg-blueBg flex flex-col gap-[60px] md:gap-20 pb-20">
|
||||
<Slider lang={lang} />
|
||||
|
||||
<Suspense fallback={<Loader />}>
|
||||
<Events lang={lang} />
|
||||
</Suspense>
|
||||
<News />
|
||||
{/* <Video /> */}
|
||||
<Partners />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
"use client";
|
||||
import { useRef } from "react";
|
||||
import { Provider } from "react-redux";
|
||||
import { makeStore, AppStore } from "../redux/store";
|
||||
|
||||
export default function StoreProvider({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const storeRef = useRef<AppStore>();
|
||||
if (!storeRef.current) {
|
||||
storeRef.current = makeStore();
|
||||
}
|
||||
|
||||
return <Provider store={storeRef.current}>{children}</Provider>;
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
import { NextResponse, NextRequest } from 'next/server';
|
||||
import { getSlider } from '@/services/home';
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
const { searchParams } = new URL(req.nextUrl);
|
||||
const bannerType = searchParams.get('bannerType') || 'main-surat';
|
||||
|
||||
try {
|
||||
const data = await getSlider(bannerType);
|
||||
return NextResponse.json(data);
|
||||
} catch (error) {
|
||||
return NextResponse.json({ error: 'Ошибка загрузки данных баннера' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,422 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
:root {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 100%;
|
||||
color: #393f3e;
|
||||
height: 100vh;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
box-sizing: border-box;
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
body {
|
||||
height: 100%;
|
||||
color: #171c1b;
|
||||
}
|
||||
|
||||
.main-abs-bg::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 100%;
|
||||
width: 100vw;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
background-color: rgb(52 67 80 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.custom-shadow {
|
||||
box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.greenBtnShadow {
|
||||
box-shadow: 0 3px 4px 0 rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.service-shadow {
|
||||
box-shadow: 0 3px 4px 0 rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.input-check {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0 0 0 0);
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.span-check {
|
||||
position: relative;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 1px solid #738799;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.input-check:checked + .span-check::before {
|
||||
content: "";
|
||||
background-image: url("../public/assets/icons/check.svg");
|
||||
background-size: 8px 8px;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 8px;
|
||||
height: 7px;
|
||||
}
|
||||
|
||||
.radio-input {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0 0 0 0);
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.radio-span {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 1px solid #5d6a77;
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
.radio-input:checked + .radio-span::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: #059784;
|
||||
border-radius: 100%;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.news-text {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.event-text {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
p img {
|
||||
height: 480px;
|
||||
width: 100%;
|
||||
object-fit: cover;
|
||||
padding: 24px 0;
|
||||
}
|
||||
|
||||
.google-map {
|
||||
aspect-ratio: 12/12;
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.google-map {
|
||||
aspect-ratio: 16/9;
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.container {
|
||||
@apply w-full mx-auto max-w-[1290px] lg:px-10 md:px-6 px-4;
|
||||
}
|
||||
|
||||
.label {
|
||||
@apply text-lg text-[#CDEAE6] mb-2;
|
||||
}
|
||||
|
||||
.input {
|
||||
@apply h-14 rounded-[2px] p-4 bg-[#E3E8E5] border border-[#758A87] text-[#303E3C];
|
||||
}
|
||||
|
||||
.error {
|
||||
@apply text-rose-200 text-sm absolute -bottom-6;
|
||||
}
|
||||
|
||||
.text_24 {
|
||||
@apply text-[24px] leading-[130%] font-medium;
|
||||
}
|
||||
|
||||
.text_16 {
|
||||
@apply text-[16px] leading-[140%] text-ON_SURFACE_VAR;
|
||||
}
|
||||
|
||||
.page-container {
|
||||
@apply w-full mr-auto ml-5 max-w-[853px] pr-5;
|
||||
}
|
||||
|
||||
.main-container {
|
||||
@apply w-full ml-[auto] mr-[120px] max-w-[195px] pl-5;
|
||||
}
|
||||
|
||||
.btn {
|
||||
@apply font-medium text-bgWhite rounded-[2px] px-[43px] py-[17px] text-sm;
|
||||
}
|
||||
|
||||
.border-btn {
|
||||
@apply font-medium text-bgWhite rounded-[2px] text-sm border-[1px] border-OUTLINE_VAR hover:border-bgWhite hover:bg-bgWhite hover:text-blueBg transition-all;
|
||||
}
|
||||
|
||||
:disabled {
|
||||
@apply opacity-50;
|
||||
}
|
||||
|
||||
.section-mb {
|
||||
@apply mb-[60px] lg:mb-[160px] md:mb-[100px];
|
||||
}
|
||||
|
||||
.text-p {
|
||||
@apply text-[16px] leading-[1.5];
|
||||
}
|
||||
|
||||
.faq-list {
|
||||
@apply leading-[1.5] before:content-['•'] before:rounded-full before:pr-[5px];
|
||||
}
|
||||
|
||||
.bid-input {
|
||||
@apply focus:outline-none bg-transparent border-[1px] border-OUTLINE rounded-[2px] py-[12px] pl-[15px] pr-10;
|
||||
}
|
||||
|
||||
.bid-drop {
|
||||
@apply focus:outline-none bg-transparent border-[1px] border-OUTLINE rounded-[2px] py-[12px] px-[15px];
|
||||
}
|
||||
|
||||
.radio {
|
||||
@apply w-[16px] h-[16px] p-[3px] border-[1px] border-OUTLINE_VAR rounded-full;
|
||||
}
|
||||
|
||||
.flex-centers {
|
||||
@apply flex items-center;
|
||||
}
|
||||
|
||||
.flex-cols {
|
||||
@apply flex flex-col;
|
||||
}
|
||||
|
||||
.link-border-bottom {
|
||||
@apply after:content-[''] after:absolute after:top-5 after:left-0 after:h-[2px] after:bg-green after:w-full hover:text-bgWhite;
|
||||
}
|
||||
|
||||
.radio-btn:hover + .radio-hover {
|
||||
@apply text-black;
|
||||
}
|
||||
|
||||
.event-block hr {
|
||||
@apply border-OUTLINE_VAR;
|
||||
}
|
||||
|
||||
.event-block h4 {
|
||||
@apply text_24 flex-[0_0_50%];
|
||||
}
|
||||
|
||||
.event-block h5 {
|
||||
@apply text_16 flex-[0_0_50%];
|
||||
}
|
||||
|
||||
.full-btn {
|
||||
@apply h-12 w-full flex items-center font-medium justify-center text-[14px] leading-[130%] rounded-[4px];
|
||||
}
|
||||
|
||||
.list-decor {
|
||||
@apply before:content-['•'] before:text-green absolute top-[7px] left-0;
|
||||
}
|
||||
|
||||
.text-21 {
|
||||
@apply text-[21px] font-semibold leading-[150%];
|
||||
}
|
||||
|
||||
:disabled {
|
||||
@apply pointer-events-none;
|
||||
}
|
||||
|
||||
.swiper-horizontal > .swiper-pagination-bullets .swiper-pagination-bullet,
|
||||
.swiper-pagination-horizontal.swiper-pagination-bullets
|
||||
.swiper-pagination-bullet {
|
||||
@apply w-[6px] h-[6px] bg-gray;
|
||||
}
|
||||
|
||||
.swiper-horizontal
|
||||
> .swiper-pagination-bullets
|
||||
.swiper-pagination-bullet-active,
|
||||
.swiper-pagination-horizontal.swiper-pagination-bullets
|
||||
.swiper-pagination-bullet-active {
|
||||
@apply h-3 w-3 bg-green translate-y-[1.7px];
|
||||
}
|
||||
|
||||
.aboutus p {
|
||||
@apply w-full;
|
||||
}
|
||||
|
||||
.aboutus p img {
|
||||
@apply w-[1000px] p-0 mb-6 h-full !important;
|
||||
}
|
||||
|
||||
.select-inner h3:not(:first-child) {
|
||||
@apply text-[18px] mb-2 mt-5 font-semibold leading-[150%];
|
||||
}
|
||||
|
||||
.select-inner h3:first-child {
|
||||
@apply text-[18px] font-semibold mb-2 leading-[150%];
|
||||
}
|
||||
|
||||
.select-inner p {
|
||||
@apply leading-[150%] mb-6;
|
||||
}
|
||||
|
||||
.select-inner ul {
|
||||
@apply ml-8 leading-[150%];
|
||||
}
|
||||
|
||||
.select-inner li {
|
||||
@apply relative list-disc mb-1;
|
||||
}
|
||||
|
||||
.select-inner ul li em {
|
||||
@apply my-10;
|
||||
}
|
||||
|
||||
.select-inner ol {
|
||||
@apply ml-8;
|
||||
}
|
||||
|
||||
.select-inner ol li {
|
||||
@apply my-3 leading-[150%];
|
||||
}
|
||||
|
||||
.select-inner strong {
|
||||
@apply text-[16px] leading-[130%];
|
||||
}
|
||||
|
||||
.select-inner u {
|
||||
@apply block mb-3 mt-5;
|
||||
}
|
||||
|
||||
.select-inner a {
|
||||
@apply underline cursor-pointer font-bold;
|
||||
}
|
||||
|
||||
.event-topic p {
|
||||
@apply before:content-["•"] before:px-2 before:text-green;
|
||||
}
|
||||
|
||||
.news-seperate-html {
|
||||
@apply w-full h-full !important;
|
||||
}
|
||||
|
||||
.hover-shadow {
|
||||
box-shadow: 0 4px 40px 0 rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
|
||||
.search-text b {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.loader {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
border: 2px solid #fff;
|
||||
border-bottom-color: transparent;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
animation: rotation 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes rotation {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.real-checkbox {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.fake-checkbox {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 2px;
|
||||
border: 1px #5d6a77 solid;
|
||||
}
|
||||
|
||||
.fake-checkbox::before {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%) scale(0);
|
||||
transition: all 0.1s ease-in;
|
||||
background-image: url("/assets/icons/check.svg");
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
.real-checkbox:checked + .fake-checkbox::before {
|
||||
transform: translate(-50%, -50%) scale(1);
|
||||
}
|
||||
|
||||
.autoplay-progress {
|
||||
z-index: 10;
|
||||
width: 63px;
|
||||
height: 1px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: bold;
|
||||
color: var(--swiper-theme-color);
|
||||
}
|
||||
|
||||
.autoplay-progress svg {
|
||||
--progress: 0;
|
||||
border-radius: 30px;
|
||||
/* position: absolute; */
|
||||
left: 0;
|
||||
top: 0px;
|
||||
z-index: 10;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
stroke-width: 10px;
|
||||
stroke: #059784;
|
||||
fill: none;
|
||||
stroke-dashoffset: calc(65 * (1 - var(--progress)));
|
||||
stroke-dasharray: 65;
|
||||
}
|
||||
|
||||
.body-scroll-lock {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.input {
|
||||
@apply flex h-14 rounded-[2px] p-4 focus:ring-2 border-0 focus:outline-none focus:ring-white transition-all hover:ring-white ring-slate-300 ring-[1px] bg-[#E3E8E5] text-base file:border-0 file:bg-SECONDARY_CONTAINER file:outline-none file:text-sm file:w-fit file:text-ON_SECONDARY_CONTAINER file:font-medium focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
import type { Metadata } from "next";
|
||||
import { Roboto } from "next/font/google";
|
||||
import StoreProvider from "./StoreProvider";
|
||||
|
||||
import "./globals.css";
|
||||
import clsx from "clsx";
|
||||
|
||||
const roboto = Roboto({
|
||||
subsets: ["latin"],
|
||||
weight: ["300", "400", "500", "700"],
|
||||
variable: "--font-roboto",
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "TurkmenExpo",
|
||||
description: "",
|
||||
icons: {
|
||||
icon: [
|
||||
{
|
||||
url: "/assets/icons/logo.svg",
|
||||
href: "/assets/icons/logo.svg",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html>
|
||||
<StoreProvider>
|
||||
<body className={clsx("antialiased font-roboto", roboto.variable)}>
|
||||
{children}
|
||||
</body>
|
||||
</StoreProvider>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
// middleware.ts
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
|
||||
export function middleware(request: NextRequest) {
|
||||
// 1. Проверяем существующую куку
|
||||
let lang = request.cookies.get("lang")?.value;
|
||||
|
||||
// 2. Если куки нет, определяем язык из браузера
|
||||
if (!lang) {
|
||||
lang =
|
||||
request.headers
|
||||
.get("Accept-Language")
|
||||
?.split(",")[0] // Берём первый язык из списка
|
||||
.split("-")[0] // Убираем регион (en-US → en)
|
||||
.toLowerCase() || "en"; // Fallback на английский
|
||||
}
|
||||
|
||||
// 3. Сохраняем язык в куки
|
||||
const response = NextResponse.next();
|
||||
if (!request.cookies.has("lang")) {
|
||||
response.cookies.set("lang", lang, {
|
||||
maxAge: 60 * 60 * 24 * 365, // 1 год
|
||||
httpOnly: true,
|
||||
});
|
||||
}
|
||||
|
||||
// 4. Добавляем язык в заголовки ВСЕХ исходящих запросов
|
||||
response.headers.set("Accept-Language", lang);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
.swiper-horizontal > .swiper-pagination-bullets,
|
||||
.swiper-pagination-bullets.swiper-pagination-horizontal {
|
||||
bottom: var(--swiper-pagination-bottom, 40px);
|
||||
top: var(--swiper-pagination-top, auto);
|
||||
left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import Image from 'next/image';
|
||||
|
||||
import navBtn from '@/public/assets/icons/home/nav-btn.svg';
|
||||
|
||||
import 'swiper/css';
|
||||
import 'swiper/css/navigation';
|
||||
import 'swiper/css/pagination';
|
||||
import 'swiper/css/scrollbar';
|
||||
import clsx from 'clsx';
|
||||
|
||||
export const NavBtn = ({
|
||||
left = false,
|
||||
onNext,
|
||||
onPrev,
|
||||
}: {
|
||||
left?: boolean;
|
||||
onNext?: () => void;
|
||||
onPrev?: () => void;
|
||||
}) => {
|
||||
const btnRef = React.useRef<HTMLButtonElement>(null);
|
||||
|
||||
return (
|
||||
<button
|
||||
ref={btnRef}
|
||||
className={`${!left ? 'next-btn' : 'prev-btn'} `}
|
||||
onClick={!left ? onNext : onPrev}>
|
||||
<Image
|
||||
src={navBtn}
|
||||
alt="arrow"
|
||||
className={clsx('img-auto', {
|
||||
'rotate-180': left,
|
||||
})}
|
||||
/>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import Link from "next/link";
|
||||
|
||||
import { topMenu } from "@/lib/database/pathnames";
|
||||
|
||||
export const Pathnames = ({ sort }: { sort: string }) => {
|
||||
return (
|
||||
<div className="text-[12px] text-gray4 flex items-center mob:mb-6 mb-5">
|
||||
<Link href={"/"}>{"Главная "}</Link>
|
||||
{topMenu
|
||||
.filter((item) => item.path === sort)
|
||||
.map((obj, i) =>
|
||||
obj.links.map((item) => <p key={i}>{item.default}</p>)
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { BreadCrumbs } from "../ui/bread-crumbs";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { useAppSelector } from "@/redux/hooks";
|
||||
import { Title } from "../ui/title";
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
second?: string;
|
||||
third?: string;
|
||||
path?: string;
|
||||
path2?: string;
|
||||
cursor?: boolean;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const LayoutWithSidebar = ({
|
||||
title,
|
||||
second,
|
||||
path,
|
||||
path2,
|
||||
third,
|
||||
children,
|
||||
cursor = false,
|
||||
}: Props) => {
|
||||
const pathname = usePathname();
|
||||
const lang = useAppSelector(
|
||||
(state) => state.headerSlice.activeLang.localization
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex bg-white px-6 py-4 rounded-lg flex-col md:gap-6 gap-10 section-mb w-full">
|
||||
<div>
|
||||
<BreadCrumbs
|
||||
second={
|
||||
pathname.includes("/services")
|
||||
? lang === "en"
|
||||
? "Services"
|
||||
: "Сервисы"
|
||||
: second ?? ""
|
||||
}
|
||||
path={path}
|
||||
path2={path2}
|
||||
third={third ? third : ""}
|
||||
/>
|
||||
<Title text={title} />
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
import React from "react";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
|
||||
interface Props {
|
||||
img: string;
|
||||
date: string;
|
||||
id: number;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export const Card = ({ img, date, id, title }: Props) => {
|
||||
return (
|
||||
<div className="border border-OUTLINE/20">
|
||||
<Image
|
||||
width={430}
|
||||
height={210}
|
||||
src={img}
|
||||
alt="событие"
|
||||
className="h-[210px] w-full object-cover"
|
||||
/>
|
||||
<Link href={`/news/${id}`} className="cursor-pointer">
|
||||
<div className="px-[16px] py-[25px] sm:p-[25px]">
|
||||
<p className="text-extraSm text-gray4 mb-[10px]">{date}</p>
|
||||
<p className="font-bold text-[16px] leading-[125%] w-full max-w-[355px]">
|
||||
{title}
|
||||
</p>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
import { getEvents } from "@/services/home";
|
||||
import { EventsMobile } from "./events-mobile";
|
||||
|
||||
const events = [
|
||||
{
|
||||
video:
|
||||
"https://editor.turkmenexpo.com/storage/app/media/video/KidsExpo%202024_%20Turkmenistan.mp4",
|
||||
logo: "https://kids.turkmenexpo.com/logo.svg",
|
||||
},
|
||||
{
|
||||
video:
|
||||
"https://turkmentextile.turkmenexpo.com/app/storage/app/media/video/Textile2025.mp4",
|
||||
logo: "/assets/icons/turkmen-textile.png",
|
||||
},
|
||||
{
|
||||
video:
|
||||
"https://itse.turkmenexpo.com/app/storage/app/media/video/itse2025.mp4",
|
||||
logo: "https://itse.turkmenexpo.com/logo.svg",
|
||||
},
|
||||
];
|
||||
|
||||
export const Events = async ({ lang }: { lang: string }) => {
|
||||
const data = await getEvents(lang);
|
||||
|
||||
const btnText =
|
||||
lang === "en" ? "Show more" : lang === "ru" ? "Показать еще" : "Show more";
|
||||
|
||||
const title = lang === "en" ? "Exhibitions" : "Выставки";
|
||||
|
||||
const linkText = lang === "en" ? "Go to website" : "Перейти на сайт";
|
||||
|
||||
return (
|
||||
<section>
|
||||
<div className="container md:block">
|
||||
<div className="sm:mb-10">
|
||||
<h2 className="text-center font-semibold text-3xl mb-8">
|
||||
{title} <span className="text-PRIMARY">Turkmen</span>
|
||||
<span className="text-red">Expo</span>
|
||||
</h2>
|
||||
{/* <Title
|
||||
text={
|
||||
lang === "en"
|
||||
? "Upcoming exhibitions and events"
|
||||
: lang === "ru"
|
||||
? "Ближайшие выставки и мероприятия"
|
||||
: "Upcoming exhibitions and events"
|
||||
}
|
||||
/> */}
|
||||
</div>
|
||||
{/* <div className="w-full flex flex-col items-center gap-8"> */}
|
||||
<div className="w-full flex flex-col items-center gap-8 lg:flex-row">
|
||||
{/* {data.data.slice(0, 2).map((item, i) => (
|
||||
<EventCard
|
||||
coorganizers={item.coorganizers}
|
||||
dark
|
||||
key={i}
|
||||
{...item}
|
||||
/>
|
||||
))}
|
||||
<LinkButton href="/calendar">{btnText}</LinkButton> */}
|
||||
{events.map((item, i) => (
|
||||
<article
|
||||
key={i}
|
||||
className="w-full h-auto bg-white drop-shadow-sm rounded-sm"
|
||||
>
|
||||
<div className="w-full h-full">
|
||||
<video
|
||||
src={item.video}
|
||||
className="size-full object-cover"
|
||||
autoPlay
|
||||
muted
|
||||
loop
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center justify-between p-4">
|
||||
<img
|
||||
height={80}
|
||||
width={100}
|
||||
src={item.logo}
|
||||
alt="logo"
|
||||
className="h-10 w-auto object-cover"
|
||||
/>
|
||||
<a
|
||||
href={events[0].logo.slice(0, -8)}
|
||||
target="_blank"
|
||||
className="text-PRIMARY underline"
|
||||
>
|
||||
{linkText}
|
||||
</a>
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* <EventsMobile data={data.data.slice(0, 5)} /> */}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import Link from "next/link";
|
||||
|
||||
import { footerInfo, headerMenu2 } from "@/lib/database/pathnames";
|
||||
import { useAppSelector } from "@/redux/hooks";
|
||||
import clsx from "clsx";
|
||||
|
||||
export const socials = [
|
||||
{ href: "https://www.linkedin.com/company/turkmen-expo", icon: "linkedin" },
|
||||
{
|
||||
href: "https://www.facebook.com/profile.php?id=61567254728028",
|
||||
icon: "facebook",
|
||||
},
|
||||
{
|
||||
href: "https://www.instagram.com/turkmenexpo_tm?igsh=bnhkOWpmNWcwcHBq",
|
||||
icon: "instagram",
|
||||
},
|
||||
{
|
||||
href: "https://x.com/turkmenexpo?t=D-XSa8d0AC8GAv5peAzteA&s=09",
|
||||
icon: "x",
|
||||
},
|
||||
];
|
||||
|
||||
export const Footer = () => {
|
||||
const localization = useAppSelector(
|
||||
(state) => state.headerSlice.activeLang.localization
|
||||
);
|
||||
|
||||
return (
|
||||
<footer className="bg-PRIMARY text-white pt-6 pb-5 mob:py-[40px]">
|
||||
<div className="container">
|
||||
<div className="">
|
||||
<div className="flex lg:flex-row flex-col w-full justify-between mb-12">
|
||||
<img className="h-12" src="/assets/icons/white-logo.svg" />
|
||||
|
||||
<div className="flex flex-col mt-8 lg:flex-row items-center font-medium gap-12">
|
||||
{headerMenu2
|
||||
.filter((item) => (localization === "en" ? item.en : !item.en))
|
||||
.map((item, i) => (
|
||||
<Link key={i} href={item.link} className="cursor-pointer">
|
||||
{item.title}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col lg:flex-row lg:justify-between justify-center text-center lg:text-left lg:items-end items-center gap-6">
|
||||
<div className="flex flex-col justify-end w-full">
|
||||
{localization === "ru" ? (
|
||||
<div className="flex flex-col md:gap-y-[10px] gap-2">
|
||||
{footerInfo.slice(0, 4).map((item, i) => (
|
||||
<p
|
||||
className={clsx("text-[12px] leading-[130%]", {
|
||||
"ml-8": i === 2,
|
||||
})}
|
||||
key={i}
|
||||
>
|
||||
{item}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
) : localization === "en" ? (
|
||||
<div className="flex flex-col md:gap-y-[10px] gap-0">
|
||||
{footerInfo.slice(3, 7).map((item, i) => (
|
||||
<p className="text-[12px] leading-[130%]" key={i}>
|
||||
{item}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-6">
|
||||
{socials.map((item, i) => (
|
||||
<a
|
||||
key={i}
|
||||
href={item.href}
|
||||
target="_blank"
|
||||
className="size-8 p-1"
|
||||
>
|
||||
<img
|
||||
src={`/assets/icons/${item.icon}.svg`}
|
||||
alt="social"
|
||||
className="size-full object-contain"
|
||||
/>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,167 @@
|
|||
"use client";
|
||||
|
||||
import React, { Suspense, useEffect, useState } from "react";
|
||||
import clsx from "clsx";
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { AnimatePresence } from "framer-motion";
|
||||
|
||||
import logo from "@/public/assets/icons/logo.svg";
|
||||
import search from "@/public/assets/icons/header/search.svg";
|
||||
import searchMob from "@/public/assets/icons/header/mob-search.svg";
|
||||
|
||||
import { LangMenu } from "../ui/lang-menu";
|
||||
import { SearchInput } from "./search-input";
|
||||
import { headerMenu2 } from "@/lib/database/pathnames";
|
||||
import { BurgerMenu } from "../ui/burger-menu";
|
||||
import { useAppDispatch, useAppSelector } from "@/redux/hooks";
|
||||
import { selectHeader, setShowInput } from "@/redux/slices/headerSlice";
|
||||
import { selectBurger, setBurgerOpen } from "@/redux/slices/burgerSlice";
|
||||
import { useStorage } from "@/hooks/useStorage";
|
||||
import { usePathname, useRouter } from "next/navigation";
|
||||
|
||||
export const Header = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { showInput } = useAppSelector(selectHeader);
|
||||
const { burgerOpen } = useAppSelector(selectBurger);
|
||||
const { activeLang } = useAppSelector(selectHeader);
|
||||
const [activeLink, setActiveLink] = useState("");
|
||||
const pathname = usePathname();
|
||||
|
||||
const toggleMenu = () => {
|
||||
dispatch(setBurgerOpen(!burgerOpen));
|
||||
dispatch(setShowInput(false));
|
||||
};
|
||||
|
||||
const onSearch = () => {
|
||||
dispatch(setShowInput(!showInput));
|
||||
dispatch(setBurgerOpen(false));
|
||||
};
|
||||
|
||||
const { setItem } = useStorage("language");
|
||||
|
||||
useEffect(() => {
|
||||
setItem(activeLang);
|
||||
}, [activeLang]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<AnimatePresence>
|
||||
{showInput && (
|
||||
<div className="absolute w-full top-0 left-0">
|
||||
<SearchInput />
|
||||
</div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
{/* Mobile */}
|
||||
|
||||
<header
|
||||
className={clsx(
|
||||
"bg-[#EEF1F0] border-b border-[#DFE2E1] tab:hidden relative z-[50] flex items-center justify-between px-4 py-6",
|
||||
{
|
||||
// 'fixed w-full top-0': burgerOpen,
|
||||
}
|
||||
)}
|
||||
>
|
||||
<Image
|
||||
src={searchMob}
|
||||
height={32}
|
||||
width={32}
|
||||
alt="поиск"
|
||||
className="cursor-pointer"
|
||||
onClick={onSearch}
|
||||
/>
|
||||
|
||||
<Link
|
||||
onClick={() => {
|
||||
dispatch(setBurgerOpen(false));
|
||||
dispatch(setShowInput(false));
|
||||
}}
|
||||
href={"/"}
|
||||
>
|
||||
<Image
|
||||
src={logo}
|
||||
height={24}
|
||||
width={160}
|
||||
alt="лого"
|
||||
className="cursor-pointer"
|
||||
/>
|
||||
</Link>
|
||||
|
||||
<div
|
||||
onClick={toggleMenu}
|
||||
className="cursor-pointer h-8 w-8 flex flex-col p-1 justify-between items-center"
|
||||
>
|
||||
<span
|
||||
className={clsx(
|
||||
"block transition-all rounded-full bg-PRIMARY w-6 h-[2px]",
|
||||
{
|
||||
"rotate-[45deg] translate-y-[9px]": burgerOpen,
|
||||
}
|
||||
)}
|
||||
/>
|
||||
<span
|
||||
className={clsx(
|
||||
"block transition-all rounded-full bg-PRIMARY w-6 h-[2px]",
|
||||
{
|
||||
"opacity-0 hidden": burgerOpen,
|
||||
}
|
||||
)}
|
||||
/>
|
||||
<span
|
||||
className={clsx(
|
||||
"block transition-all duration-300 rounded-full bg-PRIMARY w-6 h-[2px]",
|
||||
{
|
||||
"rotate-[-45deg] translate-y-[-10px]": burgerOpen,
|
||||
}
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<AnimatePresence>{burgerOpen && <BurgerMenu />}</AnimatePresence>
|
||||
</header>
|
||||
|
||||
{/* Desktop */}
|
||||
|
||||
<header className="hidden border-b border-[#DFE2E1] relative z-[3000] tab:flex flex-col">
|
||||
<div className="bg-[#EEF1F0] text-black">
|
||||
<div className="container py-[17px] flex items-center justify-between">
|
||||
<div className="flex items-center">
|
||||
<Link href="/">
|
||||
<Image src={logo} alt="logo" height={38} width={235} />
|
||||
</Link>
|
||||
<div className="flex gap-[10px]">
|
||||
<Suspense>
|
||||
<LangMenu />
|
||||
</Suspense>
|
||||
<Image
|
||||
src={search}
|
||||
alt="поиск"
|
||||
onClick={() => dispatch(setShowInput(true))}
|
||||
className="cursor-pointer"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav className="flex items-center gap-x-5 font-medium">
|
||||
{headerMenu2
|
||||
.filter((item) =>
|
||||
activeLang.localization === "en" ? item.en : !item.en
|
||||
)
|
||||
.map((item, i) => (
|
||||
<Link
|
||||
key={i}
|
||||
href={item.link}
|
||||
onClick={() => setActiveLink(item.link)}
|
||||
>
|
||||
{item.title}
|
||||
</Link>
|
||||
))}
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { Swiper, SwiperSlide } from "swiper/react";
|
||||
import { Navigation } from "swiper/modules";
|
||||
|
||||
import { NewsCard } from "./news-card";
|
||||
import { NavBtn } from "../home/ui/NavBtn";
|
||||
|
||||
import { useAppSelector } from "@/redux/hooks";
|
||||
import { selectHeader } from "@/redux/slices/headerSlice";
|
||||
import { NewsData } from "@/lib/types/NewsData.type";
|
||||
|
||||
import "swiper/css/bundle";
|
||||
|
||||
import "swiper/css";
|
||||
import "swiper/css/navigation";
|
||||
import "swiper/css/pagination";
|
||||
import "swiper/css/scrollbar";
|
||||
import Link from "next/link";
|
||||
import { useLang } from "@/utils/useLang";
|
||||
import { GreenBtn } from "../ui/Buttons";
|
||||
import { Title } from "../ui/title";
|
||||
import Loader from "@/components/ui/Loader";
|
||||
|
||||
export const News = () => {
|
||||
const [newsData, setNewsData] = useState<NewsData>();
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const { activeLang } = useAppSelector(selectHeader);
|
||||
|
||||
const fetchNews = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await fetch(`https://turkmenexpo.com/app/api/v1/news`, {
|
||||
next: { revalidate: 1800 },
|
||||
headers: {
|
||||
"Accept-Language": activeLang.localization,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Fetch failed with status ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
setNewsData(data);
|
||||
} catch (error) {
|
||||
console.error("Fetch error:", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchNews();
|
||||
}, [activeLang.localization]);
|
||||
|
||||
if (loading) {
|
||||
<Loader className="h-[300px] w-full mx-auto" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<section>
|
||||
<div className="container w-full">
|
||||
<header className="flex items-center mb-5 sm:mb-[43px] justify-between">
|
||||
<Title text={useLang("News", "Новости", activeLang.localization)} />
|
||||
<div className="hidden sm:flex items-center gap-5">
|
||||
<NavBtn left />
|
||||
<NavBtn />
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div className="sm:mb-[35px]">
|
||||
<Swiper
|
||||
modules={[Navigation]}
|
||||
spaceBetween={20}
|
||||
slidesPerView={1}
|
||||
breakpoints={{
|
||||
1024: { slidesPerView: 3 },
|
||||
768: { slidesPerView: 2.5 },
|
||||
640: { slidesPerView: 2 },
|
||||
440: { slidesPerView: 1.5 },
|
||||
}}
|
||||
navigation={{
|
||||
prevEl: ".prev-btn",
|
||||
nextEl: ".next-btn",
|
||||
}}
|
||||
>
|
||||
{loading ? (
|
||||
<Loader className="h-[300px]" />
|
||||
) : (
|
||||
newsData?.data.map((item, i) => (
|
||||
<SwiperSlide key={i}>
|
||||
<NewsCard
|
||||
id={item.id}
|
||||
title={item.title}
|
||||
date={item.published_at}
|
||||
img={item.featured_images[0].path}
|
||||
/>
|
||||
</SwiperSlide>
|
||||
))
|
||||
)}
|
||||
</Swiper>
|
||||
</div>
|
||||
|
||||
<Link href={"/news"} className="hidden sm:flex justify-center">
|
||||
<GreenBtn
|
||||
text={useLang("All news", "Все новости", activeLang.localization)}
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
"use client";
|
||||
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Image from "next/image";
|
||||
import { Swiper, SwiperSlide } from "swiper/react";
|
||||
|
||||
import { Autoplay, Pagination } from "swiper/modules";
|
||||
import { useAppSelector } from "@/redux/hooks";
|
||||
import { selectHeader } from "@/redux/slices/headerSlice";
|
||||
import { PartnersType } from "@/lib/types/PartnersData.type";
|
||||
import { baseAPI } from "@/lib/API";
|
||||
import { useLang } from "@/utils/useLang";
|
||||
import { Title } from "../ui/title";
|
||||
|
||||
export const Partners = () => {
|
||||
const { activeLang } = useAppSelector(selectHeader);
|
||||
const [partnersData, setPartnersData] = useState<PartnersType>();
|
||||
|
||||
const fetchPartners = async () => {
|
||||
try {
|
||||
const res = await fetch(`${baseAPI}partners`, {
|
||||
headers: {
|
||||
"Accept-Language": activeLang.localization,
|
||||
},
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
setPartnersData(data);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchPartners();
|
||||
}, [activeLang.localization]);
|
||||
|
||||
return (
|
||||
<section className="container">
|
||||
<div className="mb-[40px]">
|
||||
<Title
|
||||
text={useLang("Partners", "Партнёры", activeLang.localization)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center">
|
||||
<Swiper
|
||||
modules={[Pagination, Autoplay]}
|
||||
loop
|
||||
slidesPerView={5}
|
||||
autoplay={{ delay: 0 }}
|
||||
spaceBetween={30}
|
||||
speed={7000}
|
||||
breakpoints={{
|
||||
1024: { slidesPerView: 5 },
|
||||
768: { slidesPerView: 4.5 },
|
||||
630: { slidesPerView: 3.5 },
|
||||
300: { slidesPerView: 2 },
|
||||
}}
|
||||
>
|
||||
{partnersData &&
|
||||
partnersData.data.map((logo, i) => (
|
||||
<SwiperSlide key={i} className="h-[63px] w-fit overflow-hidden">
|
||||
<a href={logo.link} target="_blank">
|
||||
<Image
|
||||
height={200}
|
||||
width={200}
|
||||
src={logo.images[0].path}
|
||||
alt="logo"
|
||||
className="h-full w-full object-contain"
|
||||
/>
|
||||
</a>
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
"use client";
|
||||
|
||||
import "swiper/css";
|
||||
import "swiper/css/navigation";
|
||||
import "swiper/css/pagination";
|
||||
import "swiper/css/scrollbar";
|
||||
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import Image from "next/image";
|
||||
import { Swiper, SwiperSlide } from "swiper/react";
|
||||
import { SliderType } from "@/lib/types/SliderData.type";
|
||||
import { useSliderBanner } from "@/hooks/use-slider";
|
||||
import { useMediaQuery } from "usehooks-ts";
|
||||
import { Autoplay, Pagination } from "swiper/modules";
|
||||
import Loader from "../ui/Loader";
|
||||
import { baseAPI } from "@/lib/API";
|
||||
import Link from "next/link";
|
||||
import clsx from "clsx";
|
||||
|
||||
export const Slider = ({ lang }: { lang: string }) => {
|
||||
const isTab = useMediaQuery("(min-width: 1024px)");
|
||||
const isMd = useMediaQuery("(min-width: 700px)");
|
||||
|
||||
const [sliderData, setData] = useState<SliderType>();
|
||||
const [loading, setLoading] = useState(true);
|
||||
const size = useSliderBanner(isTab, isMd);
|
||||
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const res = await fetch(`${baseAPI}banners/${size}`, {
|
||||
headers: {
|
||||
"Accept-Language": lang,
|
||||
},
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error("Ошибка при загрузке данных");
|
||||
}
|
||||
|
||||
const data: SliderType = await res.json();
|
||||
setData(data);
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
const imgRef = useRef<HTMLImageElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, [size, lang]);
|
||||
|
||||
if (loading) return <Loader className="bg-white" />;
|
||||
|
||||
return (
|
||||
<section className={clsx("h-full bg-white")}>
|
||||
<Swiper
|
||||
modules={[Autoplay, Pagination]}
|
||||
loop
|
||||
autoplay={{ delay: 3000 }}
|
||||
speed={5000}
|
||||
slidesPerView={1}
|
||||
slidesPerGroup={1}
|
||||
className="size-full"
|
||||
>
|
||||
{sliderData &&
|
||||
sliderData?.data.banner_items.map((item, i) => (
|
||||
<SwiperSlide key={i} className="size-full cursor-pointer">
|
||||
<Link
|
||||
href={item.link ? item.link : ""}
|
||||
target="_blank"
|
||||
className="size-full"
|
||||
>
|
||||
<Image
|
||||
ref={imgRef}
|
||||
height={600}
|
||||
width={1920}
|
||||
src={item?.image}
|
||||
alt={item?.title}
|
||||
className="object-contain size-full"
|
||||
/>
|
||||
</Link>
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import clsx from "clsx";
|
||||
|
||||
export const Video = () => {
|
||||
return (
|
||||
<section className="relative">
|
||||
<video
|
||||
className={clsx(
|
||||
"w-full opacity-100 lg:h-[565px] sm:h-[375px] h-[200px] object-cover mx-auto"
|
||||
)}
|
||||
id="vid"
|
||||
autoPlay
|
||||
typeof="video/mp4"
|
||||
loop
|
||||
muted
|
||||
src="https://turkmenexpo.com/app/storage/app/media/video/video.mp4"
|
||||
/>
|
||||
<div
|
||||
id="spinner"
|
||||
className="hidden border-[8px] border-solid border-l-red h-[60px] w-[60px] rounded-full z-[100] absolute top-[100px] left-[100px] "
|
||||
></div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
"use client";
|
||||
|
||||
import { FC, useState } from "react";
|
||||
import { Form, useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { z } from "zod";
|
||||
import clsx from "clsx";
|
||||
import { postContacts } from "@/services/contacts";
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const formSchema = z.object({
|
||||
name: z.string().min(2, "Имя необходимо"),
|
||||
email: z.string().email("Email необходим"),
|
||||
phone: z.string().min(8, "Номер телефона необходим"),
|
||||
company: z.string().optional(),
|
||||
msg: z.string().min(5, "Сообщение необходимо"),
|
||||
});
|
||||
|
||||
export type FormType = z.infer<typeof formSchema>;
|
||||
|
||||
export const ContactsForm: FC<Props> = ({ className }) => {
|
||||
const [status, setStatus] = useState(false);
|
||||
|
||||
const { register, handleSubmit, formState, reset } = useForm({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
name: "",
|
||||
email: "",
|
||||
phone: "",
|
||||
company: "",
|
||||
msg: "",
|
||||
},
|
||||
});
|
||||
|
||||
async function onSubmit(data: FormType) {
|
||||
try {
|
||||
const res = await postContacts(data);
|
||||
|
||||
reset();
|
||||
setStatus(res);
|
||||
} catch (error) {
|
||||
console.error("POST contact", error);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={clsx("bg-PRIMARY rounded-[8px] py-8 px-6", className)}>
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<h2 className="h2 !text-white text-3xl font-medium lg:mb-8 mb-6">
|
||||
Связаться с нами
|
||||
</h2>
|
||||
|
||||
<div className="flex flex-col gap-8">
|
||||
<div className="flex flex-col relative">
|
||||
<label htmlFor="name" className="label">
|
||||
Имя
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="name"
|
||||
className="input"
|
||||
{...register("name")}
|
||||
/>
|
||||
<span className="error">{formState.errors.name?.message}</span>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 items-center">
|
||||
<div className="flex flex-col relative">
|
||||
<label htmlFor="email" className="label">
|
||||
E-mail
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="email"
|
||||
className="input"
|
||||
{...register("email")}
|
||||
/>
|
||||
<span className="error">{formState.errors.email?.message}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col relative">
|
||||
<label htmlFor="phone" className="label">
|
||||
Номер
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="phone"
|
||||
className="input"
|
||||
{...register("phone")}
|
||||
/>
|
||||
<span className="error">{formState.errors.phone?.message}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col relative">
|
||||
<label htmlFor="company" className="label">
|
||||
Название компании
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="company"
|
||||
className="input"
|
||||
{...register("company")}
|
||||
/>
|
||||
<span className="error">{formState.errors.company?.message}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col relative">
|
||||
<label htmlFor="msg" className="label">
|
||||
Сообщение
|
||||
</label>
|
||||
<textarea
|
||||
rows={3}
|
||||
id="msg"
|
||||
className="input !h-28 resize-none"
|
||||
{...register("msg")}
|
||||
/>
|
||||
<span className="error">{formState.errors.msg?.message}</span>
|
||||
</div>
|
||||
<button
|
||||
disabled={formState.isSubmitting || status}
|
||||
className="bg-[#A4FFF3] text-sm text-ON_PRIMARY_CONTAINER h-10 rounded-[2px]"
|
||||
>
|
||||
{!status ? "Отправить" : "Форма отправлена"}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import Image from "next/image";
|
||||
import clsx from "clsx";
|
||||
|
||||
import {
|
||||
CalendarImage,
|
||||
Coorganizers,
|
||||
Organizer,
|
||||
Timing,
|
||||
} from "@/lib/types/Calendar.type";
|
||||
import { useAppSelector } from "@/redux/hooks";
|
||||
import { useLang } from "@/utils/useLang";
|
||||
import Link from "next/link";
|
||||
|
||||
interface Props {
|
||||
id: number;
|
||||
description: string;
|
||||
starts_at: string;
|
||||
ends_at: string;
|
||||
date: string;
|
||||
title: string;
|
||||
web_site: string | null;
|
||||
location: string | null;
|
||||
timing: Timing[];
|
||||
event_topic: string | null;
|
||||
organizers: Organizer[];
|
||||
category: string;
|
||||
images: CalendarImage[];
|
||||
dark?: boolean;
|
||||
coorganizers?: Coorganizers[];
|
||||
}
|
||||
|
||||
export const EventCard = ({
|
||||
id,
|
||||
coorganizers,
|
||||
description,
|
||||
starts_at,
|
||||
ends_at,
|
||||
title,
|
||||
date,
|
||||
organizers,
|
||||
category,
|
||||
images,
|
||||
dark = false,
|
||||
}: Props) => {
|
||||
const formatDate = (dateString: string) => {
|
||||
const dateParts = dateString?.split(" ");
|
||||
const date = dateParts?.[0];
|
||||
const parts = date?.split("-");
|
||||
const formattedDate = `${parts?.[2]}.${parts?.[1]}.${parts?.[0]}`;
|
||||
|
||||
return formattedDate;
|
||||
};
|
||||
|
||||
const lang = useAppSelector(
|
||||
(state) => state.headerSlice.activeLang.localization
|
||||
);
|
||||
|
||||
return (
|
||||
<Link className="w-full cursor-default" href={`/calendar/${id}`}>
|
||||
<div
|
||||
className={clsx(
|
||||
"cursor-pointer overflow-hidden transition-all w-full lg:h-[228px] lg:p-0 p-4 rounded-[4px]",
|
||||
{
|
||||
"bg-white": dark,
|
||||
}
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
"md:hidden flex flex-col gap-8 border-b-[1px] border-opacity-10 mb-5 pb-5 md:gap-y-5 min-w-[200px]",
|
||||
{
|
||||
"border-gray3": dark,
|
||||
"border-lightCyan": !dark,
|
||||
}
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
"text-[16px] flex leading-[150%] md:text-[18px] font-semibold",
|
||||
{}
|
||||
)}
|
||||
>
|
||||
<p className="mr-5">{formatDate(starts_at)}</p>{" "}
|
||||
<p>{formatDate(ends_at)}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex md:flex-nowrap flex-wrap items-start lg:gap-x-[33px] gap-[10px]">
|
||||
<Image
|
||||
src={images?.[0]?.path || ""}
|
||||
width={228}
|
||||
height={228}
|
||||
alt="Event Image"
|
||||
className="lg:size-[228px] size-[100px] object-cover rounded-l-[4px]"
|
||||
/>
|
||||
|
||||
{/* CENTER */}
|
||||
<div className="flex flex-col gap-[10px] md:gap-4 max-w-[683px] py-6 w-full">
|
||||
<p className="text-[12px] text-[#6B7674] font-normal leading-none">
|
||||
{category}
|
||||
</p>
|
||||
<div className="flex flex-col gap-[10px] md:gap-[15px]">
|
||||
<h3
|
||||
className={clsx(
|
||||
"text-5 font-medium line leading-[115%] lg:leading-8 md:leading-[100%]"
|
||||
)}
|
||||
>
|
||||
{title}
|
||||
</h3>
|
||||
{/* <p className={clsx('text-base line-clamp-2 leading-[130%] md:leading-[150%]', {})}>
|
||||
{description}
|
||||
</p> */}
|
||||
</div>
|
||||
<div className="flex flex-col gap-[10px]">
|
||||
<p className="text-[#6B7674] uppercase font-normal leading-none text-[10px]">
|
||||
{useLang("Organiser:", "Организатор:", lang)}
|
||||
</p>
|
||||
<p className="text-[#6B7674] font-normal text-[13px] leading-[125%]">
|
||||
{organizers ? organizers[0]?.name : null}
|
||||
</p>
|
||||
</div>
|
||||
{coorganizers && coorganizers?.length > 0 && (
|
||||
<div className="flex flex-col gap-[10px]">
|
||||
<p className="text-[#6B7674] uppercase font-normal leading-none text-[10px]">
|
||||
{useLang("Co-organiser:", "Со-организатор:", lang)}
|
||||
</p>
|
||||
<p className="text-[#6B7674] font-normal text-[13px] leading-[125%]">
|
||||
{coorganizers ? coorganizers[0]?.name : null}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* CENTER */}
|
||||
|
||||
<div className="hidden md:flex text-2xl flex-col text-white leading- none h-[228px] min-w-[245px] items-center justify-center bg-[#31A898] px-12 text-center">
|
||||
<p className="">{formatDate(starts_at)}</p>{" "}
|
||||
<p>{formatDate(ends_at)}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
"use client";
|
||||
|
||||
import { useAppSelector } from "@/redux/hooks";
|
||||
import Link from "next/link";
|
||||
import { FC } from "react";
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
once?: boolean;
|
||||
register?: string;
|
||||
visit?: string;
|
||||
details?: string;
|
||||
}
|
||||
|
||||
export const EventPageButtons: FC<Props> = ({
|
||||
once = false,
|
||||
register,
|
||||
visit,
|
||||
details,
|
||||
}) => {
|
||||
const lang = useAppSelector(
|
||||
(state) => state.headerSlice.activeLang.localization
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-6">
|
||||
{once ? (
|
||||
<>
|
||||
<Link href={register || ""} className="w-full" target="_blank">
|
||||
<button className="full-btn bg-PRIMARY py-2.5 text-white">
|
||||
{lang === "en" ? "Registration" : "Зарегистрироваться"}
|
||||
</button>
|
||||
</Link>
|
||||
<Link href={visit || ""} className="w-full" target="_blank">
|
||||
<button className="full-btn bg-SECONDARY_CONTAINER py-2.5 text-ON_SECONDARY_CONTAINER">
|
||||
{lang === "en" ? "Visit site" : "Посетить сайт"}
|
||||
</button>
|
||||
</Link>
|
||||
</>
|
||||
) : (
|
||||
<Link href={details || ""} className="w-full" target="_blank">
|
||||
<button className="full-btn w-full bg-PRIMARY py-2.5 text-white">
|
||||
{lang === "en" ? "More" : "Подробнее"}
|
||||
</button>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
"use client";
|
||||
|
||||
import "swiper/css";
|
||||
import "swiper/css/pagination";
|
||||
// import "./styles/events.css";
|
||||
|
||||
import { useLang } from "@/utils/useLang";
|
||||
import { FC } from "react";
|
||||
import { Swiper, SwiperSlide } from "swiper/react";
|
||||
import { EventCard } from "./event-card";
|
||||
import { CalendarType } from "@/lib/types/Calendar.type";
|
||||
import { useAppSelector } from "@/redux/hooks";
|
||||
|
||||
interface Props {
|
||||
data: CalendarType["data"];
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const EventsMobile: FC<Props> = ({ data }) => {
|
||||
const lang = useAppSelector(
|
||||
(state) => state.headerSlice.activeLang.localization
|
||||
);
|
||||
return (
|
||||
<div className="md:hidden container">
|
||||
<h2 className="text-[26px] mb-5 sm:mb-10 font-semibold leading-[115%]">
|
||||
{useLang(
|
||||
"Upcoming exhibitions and events",
|
||||
"Ближайшие выставки и мероприятия",
|
||||
lang
|
||||
)}
|
||||
</h2>
|
||||
<div className="flex flex-col">
|
||||
<div className="flex items-center gap-y-[10px]">
|
||||
<Swiper slidesPerView={1} spaceBetween={20}>
|
||||
{data &&
|
||||
data.map((item, i) => (
|
||||
<SwiperSlide key={i} className="md:mb-[72px]">
|
||||
<EventCard dark {...item} />
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
import React from 'react';
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
|
||||
interface Props {
|
||||
img: string;
|
||||
date: string;
|
||||
id: number;
|
||||
title: string;
|
||||
mobile?: boolean;
|
||||
}
|
||||
|
||||
export const NewsCard = ({ img, title, date, id, mobile = false }: Props) => {
|
||||
return (
|
||||
<div className="bg-white rounded-[4px] mx-auto w-full transition-all h-[415px]">
|
||||
{/* Aspect ration 1.8:1 */}
|
||||
<Image
|
||||
src={img}
|
||||
width={290}
|
||||
height={221}
|
||||
alt="photo"
|
||||
className="mob:h-[221px] h-[160px] w-full object-cover rounded-t-[4px]"
|
||||
/>
|
||||
<Link href={`/news/${id}`} className="h-full">
|
||||
<div className="p-[25px] h-[160px] sm:h-[140px]">
|
||||
<p className="text-extraSm leading-[125%] text-[#787878] mb-[10px]">{date}</p>
|
||||
<p className="font-medium leading-[135%] sm:text-[22px] text-[21px] line-clamp-4">
|
||||
{title}
|
||||
</p>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
import { FC } from 'react';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const NewsInner: FC<Props> = ({ className }) => {
|
||||
return <div className={className}></div>;
|
||||
};
|
||||
|
|
@ -0,0 +1,223 @@
|
|||
"use client";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Image from "next/image";
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
import { useAppDispatch, useAppSelector } from "@/redux/hooks";
|
||||
import close from "@/public/assets/icons/home/close-input.svg";
|
||||
|
||||
import { selectInput, setInputStatus } from "@/redux/slices/inputSlice";
|
||||
import { v4 } from "uuid";
|
||||
import { selectHeader, setShowInput } from "@/redux/slices/headerSlice";
|
||||
import clsx from "clsx";
|
||||
import { selectBurger } from "@/redux/slices/burgerSlice";
|
||||
import { baseAPI } from "@/lib/API";
|
||||
import Link from "next/link";
|
||||
import { SearchTypes } from "@/lib/types/Search.type";
|
||||
import { useMediaQuery } from "usehooks-ts";
|
||||
|
||||
export const inputRadio = [
|
||||
{ name: "Везде", id: "all" },
|
||||
{ name: "В событиях", id: "events" },
|
||||
{ name: "В новостях", id: "news" },
|
||||
];
|
||||
|
||||
export const SearchInput = ({ mob = false }: { mob?: boolean }) => {
|
||||
const localization = useAppSelector(
|
||||
(state) => state.headerSlice.activeLang.localization
|
||||
);
|
||||
const wrapper = document.querySelector(".wrapper");
|
||||
|
||||
const tab = useMediaQuery("(min-width: 980px)");
|
||||
|
||||
const [value, setValue] = useState("");
|
||||
const [searchValue, setSearchValue] = useState("");
|
||||
const [searchData, setSearchData] = useState<SearchTypes>();
|
||||
const [isSearching, setIsSearching] = useState(false);
|
||||
const [searchIsError, setSearchIsError] = useState(false);
|
||||
|
||||
const { showInput } = useAppSelector(selectHeader);
|
||||
|
||||
useEffect(() => {
|
||||
wrapper?.classList.remove("overflow-hidden");
|
||||
wrapper?.classList.add("overflow-hidden");
|
||||
|
||||
setStatus(inputRadio[0].id);
|
||||
inputRadio;
|
||||
|
||||
return () => {
|
||||
wrapper?.classList.remove("overflow-hidden");
|
||||
};
|
||||
}, [showInput]);
|
||||
|
||||
const fetchSearchData = async () => {
|
||||
setIsSearching(true);
|
||||
setSearchValue(value);
|
||||
try {
|
||||
const res = await fetch(`${baseAPI}search?search=${value}`, {
|
||||
headers: {
|
||||
"Accept-Language": localization,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
return console.error(res.status);
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
setSearchData(data);
|
||||
|
||||
setIsSearching(false);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
setSearchIsError(true);
|
||||
}
|
||||
};
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
const { inputStatus } = useAppSelector(selectInput);
|
||||
const { burgerOpen } = useAppSelector(selectBurger);
|
||||
|
||||
const setStatus = (name: string) => {
|
||||
dispatch(setInputStatus(name));
|
||||
};
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
initial={{
|
||||
y: "-100%",
|
||||
opacity: 0,
|
||||
}}
|
||||
animate={{
|
||||
y: 0,
|
||||
opacity: 1,
|
||||
}}
|
||||
exit={{
|
||||
y: "-100%",
|
||||
opacity: 0,
|
||||
}}
|
||||
transition={{
|
||||
duration: 0.5,
|
||||
ease: [0.55, 0, 0.1, 1],
|
||||
}}
|
||||
className={clsx("left-0 w-full h-screen overflow-auto z-20 bg-blueBg", {
|
||||
"fixed top-[74px] bottom-0": !tab,
|
||||
"absolute bottom-0 top-0 z-[9999]": tab,
|
||||
hidden: burgerOpen,
|
||||
})}
|
||||
>
|
||||
<div className="container section-mb">
|
||||
<div className="w-full flex justify-end mt-[40px]">
|
||||
<svg
|
||||
className="cursor-pointer"
|
||||
onClick={() => dispatch(setShowInput(false))}
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 13.4L7.09999 18.3C6.91665 18.4834 6.68332 18.575 6.39999 18.575C6.11665 18.575 5.88332 18.4834 5.69999 18.3C5.51665 18.1167 5.42499 17.8834 5.42499 17.6C5.42499 17.3167 5.51665 17.0834 5.69999 16.9L10.6 12L5.69999 7.10005C5.51665 6.91672 5.42499 6.68338 5.42499 6.40005C5.42499 6.11672 5.51665 5.88338 5.69999 5.70005C5.88332 5.51672 6.11665 5.42505 6.39999 5.42505C6.68332 5.42505 6.91665 5.51672 7.09999 5.70005L12 10.6L16.9 5.70005C17.0833 5.51672 17.3167 5.42505 17.6 5.42505C17.8833 5.42505 18.1167 5.51672 18.3 5.70005C18.4833 5.88338 18.575 6.11672 18.575 6.40005C18.575 6.68338 18.4833 6.91672 18.3 7.10005L13.4 12L18.3 16.9C18.4833 17.0834 18.575 17.3167 18.575 17.6C18.575 17.8834 18.4833 18.1167 18.3 18.3C18.1167 18.4834 17.8833 18.575 17.6 18.575C17.3167 18.575 17.0833 18.4834 16.9 18.3L12 13.4Z"
|
||||
fill="#0f0000"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
className={`flex flex-col mt-[10vw] items-center w-full mb-6 max-w-[566px] mx-auto`}
|
||||
>
|
||||
<div className="w-full mb-[24px]">
|
||||
<input
|
||||
maxLength={30}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setValue(e.target.value)
|
||||
}
|
||||
value={value}
|
||||
type="text"
|
||||
placeholder="Что найти?"
|
||||
className="p-3 w-full leading-[150%] placeholder:leading-[150%] placeholder:text-gray focus:outline-none rounded-sm bg-transparent border-[1px] border-[#BCC4CC]"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center text-[12px] sm:text-[14px] gap-[40px] sm:gap-[48px] mb-5 sm:mb-10">
|
||||
{inputRadio.map((item) => (
|
||||
<div
|
||||
onClick={() => setStatus(item.id)}
|
||||
className="flex cursor-pointer items-center gap-[10px]"
|
||||
key={v4()}
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
"size-5 border border-primary relative after:absolute transition-all after:transition-all after:size-[9px] after:scale-0 flex items-center justify-center after:bg-primary after:rounded-full rounded-full",
|
||||
inputStatus === item.id && "after:scale-100"
|
||||
)}
|
||||
/>
|
||||
<p>{item.name}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<button
|
||||
disabled={isSearching || value.length < 3}
|
||||
className="mb-6"
|
||||
onClick={fetchSearchData}
|
||||
>
|
||||
{isSearching ? (
|
||||
<div className="bg-green w-[180px] h-[48px] py-[17px] rounded-sm">
|
||||
<span className="loader"></span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="bg-green py-[17px] px-[70px] rounded-sm">
|
||||
Найти
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
|
||||
{searchData ? (
|
||||
searchData.data.expo_events.length &&
|
||||
searchData.data.posts.length ? (
|
||||
<>
|
||||
<div className="mb-12 font-light">
|
||||
По запросу « <span className="font-bold">{searchValue}</span>{" "}
|
||||
» нашлось
|
||||
{searchData.data.expo_events.length +
|
||||
searchData.data.posts.length}{" "}
|
||||
результатов
|
||||
</div>
|
||||
<div className="flex flex-col gap-9 w-full tab:mb-0 mb-20">
|
||||
{inputStatus === "all" || inputStatus === "events"
|
||||
? searchData.data.expo_events.map((item) => (
|
||||
<div className="w-full">
|
||||
<h3 className="font-bold mb-[18px] text-[16px] leading-[125%]">
|
||||
{item.title}
|
||||
</h3>
|
||||
<Link href={`/calendar/${item.id}`}>
|
||||
Перейти на страницу
|
||||
</Link>
|
||||
<hr className="mt-9 border-OUTLINE_VAR" />
|
||||
</div>
|
||||
))
|
||||
: null}
|
||||
{inputStatus === "all" || inputStatus === "news"
|
||||
? searchData.data.posts.map((item) => (
|
||||
<div className="w-full">
|
||||
<h3 className="font-bold mb-[18px] text-[16px] leading-[125%]">
|
||||
{item.title}
|
||||
</h3>
|
||||
<Link href={`/news/${item.id}`}>
|
||||
Перейти на страницу
|
||||
</Link>
|
||||
<hr className="mt-9 border-OUTLINE_VAR" />
|
||||
</div>
|
||||
))
|
||||
: null}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<h3>По вашему запросу ничего не найдено</h3>
|
||||
)
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import Image from "next/image";
|
||||
|
||||
import { contactCardData, roadCardData } from "@/lib/database/homeInfoData";
|
||||
import { useLang } from "@/utils/useLang";
|
||||
import { useAppSelector } from "@/redux/hooks";
|
||||
|
||||
export const Services = () => {
|
||||
const localization = useAppSelector(
|
||||
(state) => state.headerSlice.activeLang.localization
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`container flex flex-col md:flex-row md:w-full gap-5 lg:gap-x-[40px] items-center`}
|
||||
>
|
||||
<div className="flex flex-col items-start gap-x-5 w-full">
|
||||
<h3 className="text-[21px] leading-[100%] font-bold text-bgWhite md:text-[#B0E6A1] uppercase md:mb-10 mb-5">
|
||||
{useLang("Routes", "как добраться", localization)}
|
||||
</h3>
|
||||
<div className="flex flex-col gap-y-5 w-full">
|
||||
{roadCardData.map((item, i) => (
|
||||
<div
|
||||
className="bg-white w-full transition-all hover:hover-shadow cursor-pointer service-shadow px-[40px] py-5 text-black greenBtnShadow rounded-[2px] custom-shadow"
|
||||
key={i}
|
||||
>
|
||||
<div className="flex items-center gap-10 md:gap-5">
|
||||
<Image className="img-auto" src={item.icon} alt="icon" />
|
||||
<p className="text-[16px] leading-[120%] md:leading-[100%]">
|
||||
{localization === "ru" ? item.text : item.enText}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col items-start md:mt-0 mt-10 gap-x-5 w-full">
|
||||
<h3 className="text-[21px] leading-[100%] text-bgWhite md:text-[#FFD288] uppercase font-bold md:mb-10 mb-5">
|
||||
{useLang("Contacts", "контакты", localization)}
|
||||
</h3>
|
||||
<div className="flex flex-col gap-y-5 w-full">
|
||||
{contactCardData.map((item, i) => (
|
||||
<div
|
||||
className="bg-white w-full transition-all hover:hover-shadow cursor-pointer service-shadow px-[40px] py-5 text-black greenBtnShadow rounded-[2px]"
|
||||
key={i}
|
||||
>
|
||||
<div className="flex items-center md:gap-5 gap-10">
|
||||
<Image src={item.icon} alt="icon" />
|
||||
<p className="text-[16px] leading-[120%] md:leading-[100%]">
|
||||
{localization === "ru" ? item.text : item.enText}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import Image from "next/image";
|
||||
import { useMediaQuery } from "usehooks-ts";
|
||||
import { useAppSelector } from "@/redux/hooks";
|
||||
import { useSliderBanner } from "@/hooks/use-slider";
|
||||
import Loader from "../ui/Loader";
|
||||
|
||||
export const SliderClient = () => {
|
||||
const isTab = useMediaQuery("(min-width: 1024px)");
|
||||
const isMd = useMediaQuery("(min-width: 700px)");
|
||||
|
||||
const [data, setData] = useState<any>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const bannerType = useSliderBanner(isTab, isMd);
|
||||
const lang = useAppSelector(
|
||||
(state) => state.headerSlice.activeLang.localization
|
||||
);
|
||||
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const res = await fetch(`/api/banners?bannerType=${bannerType}`, {
|
||||
headers: {
|
||||
"Accept-Language": lang,
|
||||
},
|
||||
});
|
||||
const json = await res.json();
|
||||
setData(json);
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, [bannerType, lang]);
|
||||
|
||||
if (loading) return <Loader className="h-[600px] min-h-[320px]" />;
|
||||
|
||||
return (
|
||||
<Image
|
||||
src={data.data.banner_items?.[0]?.image || ""}
|
||||
alt="Баннер"
|
||||
width={1920}
|
||||
height={600}
|
||||
className="object-cover max-h-[600px] min-h-[320px] size-full object-center"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import clsx from "clsx";
|
||||
|
||||
interface GreenBtnProps {
|
||||
text: string | boolean;
|
||||
mt?: string;
|
||||
onEventBtn?: () => void;
|
||||
border?: boolean;
|
||||
px?: boolean;
|
||||
}
|
||||
|
||||
export const GreenBtn = ({ text, mt, onEventBtn, px }: GreenBtnProps) => {
|
||||
return (
|
||||
<button
|
||||
onClick={onEventBtn}
|
||||
className={clsx(
|
||||
`${mt} py-[17px] text-[14px] rounded-[4px] bg-green text-[#346560] hover:bg-opacity-90 font-medium transition-all`,
|
||||
{
|
||||
"px-[43px]": !px,
|
||||
"px-[70px]": px,
|
||||
}
|
||||
)}
|
||||
>
|
||||
{text}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
interface BorderProps {
|
||||
onEventBtn?: () => void;
|
||||
text: string;
|
||||
mt?: string;
|
||||
px?: boolean;
|
||||
full?: boolean;
|
||||
}
|
||||
|
||||
export const BorderBtn = ({ onEventBtn, text, mt, px, full }: BorderProps) => {
|
||||
return (
|
||||
<button
|
||||
className={clsx(`mt-[${mt}] border-btn py-[17px]`, {
|
||||
"px-[43px]": px,
|
||||
"px-[17px]": !px,
|
||||
"w-full sm:w-fit": full,
|
||||
})}
|
||||
onClick={onEventBtn}
|
||||
>
|
||||
{text}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
interface MobBtnProps {
|
||||
text: string;
|
||||
}
|
||||
|
||||
export const GreenBtnMob = ({ text }: MobBtnProps) => {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="p-3 rounded-sm bg-green w-[140px] mx-auto cursor-pointer hover:bg-lightGreen transition-all"
|
||||
>
|
||||
{text}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export const SimpleGreenBtn = ({ text }: MobBtnProps) => {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="bg-green py-[17px] px-[70px] leading-[100%] rounded-sm hover:bg-lightGreen transition-all"
|
||||
>
|
||||
{text}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
import clsx from "clsx";
|
||||
import React from "react";
|
||||
|
||||
const Loader = ({ className }: { className?: string }) => {
|
||||
return (
|
||||
<div
|
||||
className={clsx(className, "flex h-[500px] justify-center items-center")}
|
||||
>
|
||||
<div className="animate-spin border-4 border-t-transparent size-20 rounded-full border-PRIMARY" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Loader;
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
"use client";
|
||||
|
||||
import React, { Dispatch, SetStateAction } from "react";
|
||||
|
||||
interface IProps {
|
||||
lastPage?: number;
|
||||
currentPage?: number;
|
||||
totalPage?: number;
|
||||
current: number;
|
||||
setCurrent: Dispatch<SetStateAction<number>>;
|
||||
}
|
||||
|
||||
export const Pagination = ({ current, setCurrent, lastPage = 3 }: IProps) => {
|
||||
const onNext = () => {
|
||||
setCurrent(current + 1);
|
||||
};
|
||||
|
||||
const onPrev = () => {
|
||||
setCurrent(current - 1);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-[5px] justify-center">
|
||||
<button onClick={onPrev} disabled={current === 1} type="button">
|
||||
<svg
|
||||
width="32"
|
||||
height="32"
|
||||
viewBox="0 0 32 32"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<path
|
||||
d="M18 22L12 16L18 10L19.4 11.4L14.8 16L19.4 20.6L18 22Z"
|
||||
fill="#059784"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<div className="border-[1px] border-OUTLINE_VAR rounded-sm px-3 py-[9px]">
|
||||
{current}
|
||||
</div>
|
||||
<p>из {lastPage}</p>
|
||||
<button onClick={onNext} disabled={current >= lastPage} type="button">
|
||||
<svg
|
||||
width="32"
|
||||
height="32"
|
||||
viewBox="0 0 32 32"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="rotate-180 cursor-pointer"
|
||||
>
|
||||
<path
|
||||
d="M18 22L12 16L18 10L19.4 11.4L14.8 16L19.4 20.6L18 22Z"
|
||||
fill="#059784"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import Link from "next/link";
|
||||
import clsx from "clsx";
|
||||
import { usePathname } from "next/navigation";
|
||||
|
||||
import { sidebarData } from "@/lib/database/pathnames";
|
||||
import { useAppSelector } from "@/redux/hooks";
|
||||
|
||||
export const Sidebar = () => {
|
||||
const pathname = usePathname();
|
||||
|
||||
const lang = useAppSelector(
|
||||
(state) => state.headerSlice.activeLang.localization
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col bg-white rounded-lg px-6 py-4 items-start gap-y-[12px] pb-5 mt-12 sticky top-0 left-0 overflow-hidden">
|
||||
{sidebarData
|
||||
.filter(
|
||||
(obj) =>
|
||||
(pathname === "/company/aboutus" && obj.company) ||
|
||||
(pathname.includes("/members") && obj.members) ||
|
||||
(pathname.includes("/news") && obj.news) ||
|
||||
(pathname.includes("/visitors") && obj.visitors) ||
|
||||
(pathname.includes("/services") && obj.services)
|
||||
)
|
||||
.map((item, i) => (
|
||||
<div key={i}>
|
||||
<p
|
||||
className={clsx("mb-[16px] text-[16px] font-bold leading-[1.5]")}
|
||||
>
|
||||
{lang === "ru" ? item.pathname : item.pathnameEn}
|
||||
</p>
|
||||
<div className="flex flex-col items-start gap-y-[8px]">
|
||||
<div className="flex flex-col gap-[10px] pl-4 pr-10">
|
||||
{item.info.map((obj, i) => (
|
||||
<Link
|
||||
href={obj.link}
|
||||
className={clsx(
|
||||
"cursor-pointer py-1 leading-[130%] transition-all hover:opacity-80",
|
||||
{
|
||||
"hover:text-PRIMARY text-PRIMARY hover:cursor-default":
|
||||
obj.link === pathname,
|
||||
}
|
||||
)}
|
||||
key={i}
|
||||
>
|
||||
{lang === "ru" ? obj.title : obj.titleEn}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
"use client";
|
||||
|
||||
import { useAppSelector } from "@/redux/hooks";
|
||||
import { selectHeader } from "@/redux/slices/headerSlice";
|
||||
import { useLang } from "@/utils/useLang";
|
||||
import Link from "next/link";
|
||||
|
||||
export const BreadCrumbs = ({
|
||||
second,
|
||||
third,
|
||||
path,
|
||||
path2,
|
||||
cursor = false,
|
||||
}: {
|
||||
second: string;
|
||||
third?: string;
|
||||
path?: string;
|
||||
path2?: string;
|
||||
cursor?: boolean;
|
||||
}) => {
|
||||
const { activeLang } = useAppSelector(selectHeader);
|
||||
|
||||
return (
|
||||
<div className="text-[12px] text-[#6B7674] flex items-center mob:mb-6 mb-5">
|
||||
<Link href={"/"}>
|
||||
{useLang("Home", "Главная", activeLang.localization)}
|
||||
</Link>
|
||||
|
||||
<p className="px-1">/</p>
|
||||
|
||||
{third ? <Link href={path ? path : ""}>{second}</Link> : <p>{second}</p>}
|
||||
|
||||
{third && <p className="px-1">/</p>}
|
||||
|
||||
{third && <p>{third}</p>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
"use client";
|
||||
|
||||
import { burgerMenuData } from "@/lib/database/pathnames";
|
||||
import { useAppDispatch, useAppSelector } from "@/redux/hooks";
|
||||
import Link from "next/link";
|
||||
import React from "react";
|
||||
import Image from "next/image";
|
||||
import { v4 } from "uuid";
|
||||
|
||||
import arrow from "@/public/assets/icons/header/burger-arrow.svg";
|
||||
import {
|
||||
setBurgerDrop,
|
||||
setBurgerOpen,
|
||||
setFooterDrop,
|
||||
} from "@/redux/slices/burgerSlice";
|
||||
|
||||
export const BurgerDrop = ({
|
||||
filter,
|
||||
setDrop,
|
||||
}: {
|
||||
filter: string;
|
||||
setDrop: (name: boolean) => void;
|
||||
}) => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
return burgerMenuData
|
||||
.filter((obj) => !obj.only)
|
||||
.map(
|
||||
(item) =>
|
||||
item.pathname === filter && (
|
||||
<div key={v4()} className="flex flex-col">
|
||||
<div
|
||||
onClick={() => {
|
||||
setDrop(false);
|
||||
dispatch(setBurgerDrop(""));
|
||||
dispatch(setFooterDrop(""));
|
||||
}}
|
||||
className="cursor-pointer flex items-center gap-[10px] mb-[10px]"
|
||||
>
|
||||
<Image src={arrow} alt="стрелка" className="rotate-180" />
|
||||
<h3 className="leading-[135%] text-[18px]">{item.title}</h3>
|
||||
</div>
|
||||
<hr className="border-bgWhite mb-5" />
|
||||
|
||||
<div className="flex flex-col leading-[150%] gap-5">
|
||||
{item.info?.map((subj) => (
|
||||
<Link
|
||||
key={v4()}
|
||||
href={subj.link}
|
||||
className="cursor-pointer text-white"
|
||||
onClick={() => {
|
||||
dispatch(setBurgerOpen(false));
|
||||
setDrop(false);
|
||||
}}
|
||||
>
|
||||
{subj.title}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,265 @@
|
|||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { useEffect, useState } from "react";
|
||||
import { v4 } from "uuid";
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
import { useAppDispatch, useAppSelector } from "@/redux/hooks";
|
||||
import { setBurgerOpen } from "@/redux/slices/burgerSlice";
|
||||
import { activeLangType, setActiveLang } from "@/redux/slices/headerSlice";
|
||||
import clsx from "clsx";
|
||||
import { burgerMenu, burgerMenu2 } from "@/lib/database/header";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { lang } from "./lang-menu";
|
||||
|
||||
interface flagTypes {
|
||||
// title: 'Ру' | 'En' | 'Tm';
|
||||
title: "Ру" | "En";
|
||||
localization: "ru" | "en";
|
||||
// localization: 'ru' | 'en' | 'tm';
|
||||
}
|
||||
|
||||
const burgerLangs: flagTypes[] = [
|
||||
// {
|
||||
// title: 'Tm',
|
||||
// localization: 'tm',
|
||||
// },
|
||||
{
|
||||
title: "Ру",
|
||||
localization: "ru",
|
||||
},
|
||||
{
|
||||
title: "En",
|
||||
localization: "en",
|
||||
},
|
||||
];
|
||||
|
||||
export const BurgerMenu = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const wrapper = document.querySelector(".wrapper");
|
||||
const { refresh } = useRouter();
|
||||
|
||||
const localization = useAppSelector(
|
||||
(state) => state.headerSlice.activeLang.localization
|
||||
);
|
||||
|
||||
const [activeMenu, setActiveMenu] = useState<string>("");
|
||||
const [activeMenu2, setActiveMenu2] = useState<string>("");
|
||||
|
||||
const setActiveTitle = () => {
|
||||
if (activeMenu.includes("/ser"))
|
||||
return (
|
||||
(localization === "ru" && "Услуги") ||
|
||||
(localization === "en" && "Services")
|
||||
);
|
||||
};
|
||||
|
||||
const setActiveTitle2 = () => {
|
||||
if (activeMenu2.includes("/company"))
|
||||
return (
|
||||
(localization === "ru" && "О компании") ||
|
||||
(localization === "en" && "About company")
|
||||
);
|
||||
};
|
||||
|
||||
const setLang = (lang: activeLangType) => {
|
||||
// Устанавливаем cookie через document.cookie
|
||||
document.cookie = `lang=${lang.localization}; path=/; max-age=31536000`;
|
||||
dispatch(setActiveLang(lang));
|
||||
refresh();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const cookieLang = document.cookie
|
||||
.split("; ")
|
||||
.find((row) => row.startsWith("lang="))
|
||||
?.split("=")[1];
|
||||
|
||||
if (cookieLang) {
|
||||
const foundLang = lang.find((l) => l.localization === cookieLang);
|
||||
if (foundLang) dispatch(setActiveLang(foundLang));
|
||||
}
|
||||
}, [dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
wrapper?.classList.remove("overflow-hidden");
|
||||
wrapper?.classList.add("overflow-hidden");
|
||||
|
||||
return () => {
|
||||
wrapper?.classList.remove("overflow-hidden");
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ x: "100%", opacity: 0 }}
|
||||
animate={{ x: 0, opacity: 1 }}
|
||||
transition={{
|
||||
duration: 0.5,
|
||||
ease: [0.55, 0, 0.1, 1],
|
||||
}}
|
||||
exit={{
|
||||
x: "100%",
|
||||
opacity: 0,
|
||||
}}
|
||||
className="bg-green overflow-auto fixed w-full z-[900] top-[74px] bottom-0 left-0 min-h-[100vh] h-full px-4 py-10 flex flex-col overflow-y-auto"
|
||||
>
|
||||
{activeMenu && (
|
||||
<div>
|
||||
<div
|
||||
onClick={() => setActiveMenu("")}
|
||||
className="flex cursor-pointer"
|
||||
>
|
||||
<svg
|
||||
className="rotate-180"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12.6 12L8 7.4L9.4 6L15.4 12L9.4 18L8 16.6L12.6 12Z"
|
||||
fill="#303F4D"
|
||||
/>
|
||||
</svg>
|
||||
<h2 className="text-[18px] ml-[10px] leading-[135%]">
|
||||
{setActiveTitle()}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="mt-[10px] opacity-50 mb-5 h-[1px] w-full bg-blueBg" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeMenu && (
|
||||
<div className="flex flex-col gap-5 leading-[150%]">
|
||||
{activeMenu.includes("/services") &&
|
||||
burgerMenu
|
||||
.filter((item) => item.services)
|
||||
.map((item, i) =>
|
||||
item.dropDown?.map((obj) => (
|
||||
<Link
|
||||
key={i}
|
||||
onClick={() => dispatch(setBurgerOpen(false))}
|
||||
href={obj.link}
|
||||
>
|
||||
{localization === "en" ? obj.titleEn : obj.title}
|
||||
</Link>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="leading-[135%] text-[18px] mb-10 flex flex-col gap-5">
|
||||
{!activeMenu &&
|
||||
burgerMenu.map((item, i) =>
|
||||
!item.drop ? (
|
||||
<Link
|
||||
key={i}
|
||||
onClick={() => {
|
||||
dispatch(setBurgerOpen(false));
|
||||
}}
|
||||
href={item.link}
|
||||
>
|
||||
{(localization === "en" && item.titleEn) ||
|
||||
(localization === "ru" && item.title)}
|
||||
</Link>
|
||||
) : (
|
||||
<div
|
||||
key={i}
|
||||
className="cursor-pointer flex items-center justify-between"
|
||||
onClick={() => {
|
||||
setActiveMenu(item.link);
|
||||
}}
|
||||
>
|
||||
{(localization === "en" && item.titleEn) ||
|
||||
(localization === "ru" && item.title)}
|
||||
{item.drop && (
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12.6 12L8 7.4L9.4 6L15.4 12L9.4 18L8 16.6L12.6 12Z"
|
||||
fill="#303F4D"
|
||||
/>
|
||||
</svg>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="h-[1px] w-full opacity-50 bg-blueBg" />
|
||||
|
||||
{activeMenu2 && (
|
||||
<div>
|
||||
<div
|
||||
onClick={() => setActiveMenu2("")}
|
||||
className="flex cursor-pointer pt-4"
|
||||
>
|
||||
<img
|
||||
src="/assets/icons/header/burger-arrow.svg"
|
||||
alt="arrow"
|
||||
className="rotate-180"
|
||||
/>
|
||||
<h2 className="text-[18px] ml-[10px] leading-[135%]">
|
||||
{setActiveTitle2()}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="mt-[10px] opacity-50 mb-5 h-[1px] w-full bg-[#F2F9FF]" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div
|
||||
className={clsx("leading-[135%] text-[14px] flex flex-col gap-5", {
|
||||
"mt-10": !activeMenu2,
|
||||
})}
|
||||
>
|
||||
{activeMenu2.includes("/company") &&
|
||||
burgerMenu2
|
||||
.filter((item) => item.company)
|
||||
.map((obj) =>
|
||||
obj.dropDown?.map((item) => (
|
||||
<Link
|
||||
key={v4()}
|
||||
href={item.link}
|
||||
onClick={() => {
|
||||
dispatch(setBurgerOpen(false));
|
||||
}}
|
||||
>
|
||||
{(localization === "en" && item.titleEn) ||
|
||||
(localization === "ru" && item.title)}
|
||||
</Link>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center mx-auto gap-10 mt-10">
|
||||
{burgerLangs.map((item, i) => (
|
||||
<div
|
||||
key={i}
|
||||
onClick={() => {
|
||||
dispatch(setActiveLang(item));
|
||||
dispatch(setBurgerOpen(false));
|
||||
setLang(item);
|
||||
}}
|
||||
className="flex cursor-pointer items-center gap-[10px]"
|
||||
>
|
||||
<img
|
||||
src={`/assets/icons/header/${item.localization}.svg`}
|
||||
alt="flag"
|
||||
/>
|
||||
<p>{item.title}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
"use client";
|
||||
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import Image from "next/image";
|
||||
import clsx from "clsx";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
|
||||
import triangle from "@/public/assets/icons/drop-icon.svg";
|
||||
import { useAppSelector, useAppDispatch } from "@/redux/hooks";
|
||||
import { activeLangType, selectHeader } from "@/redux/slices/headerSlice";
|
||||
import { setActiveLang } from "@/redux/slices/headerSlice";
|
||||
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
||||
|
||||
export const lang = [
|
||||
{
|
||||
title: "Русский",
|
||||
localization: "ru",
|
||||
},
|
||||
// {
|
||||
// title: 'Tm',
|
||||
// localization: 'tm',
|
||||
// },
|
||||
{
|
||||
title: "English",
|
||||
localization: "en",
|
||||
},
|
||||
];
|
||||
|
||||
export const LangMenu = () => {
|
||||
const { activeLang } = useAppSelector(selectHeader);
|
||||
const { refresh } = useRouter();
|
||||
const [active, setActive] = useState(false);
|
||||
const dispatch = useAppDispatch();
|
||||
const menuRef = useRef<HTMLDivElement>(null);
|
||||
const setLang = (lang: activeLangType) => {
|
||||
// Устанавливаем cookie через document.cookie
|
||||
document.cookie = `lang=${lang.localization}; path=/; max-age=31536000`;
|
||||
dispatch(setActiveLang(lang));
|
||||
refresh();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const cookieLang = document.cookie
|
||||
.split("; ")
|
||||
.find((row) => row.startsWith("lang="))
|
||||
?.split("=")[1];
|
||||
|
||||
if (cookieLang) {
|
||||
const foundLang = lang.find((l) => l.localization === cookieLang);
|
||||
if (foundLang) dispatch(setActiveLang(foundLang));
|
||||
}
|
||||
}, [dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleClick = (e: MouseEvent) => {
|
||||
if (menuRef.current && !menuRef.current.contains(e.target as Node)) {
|
||||
setActive(false);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("click", handleClick);
|
||||
|
||||
return () => document.removeEventListener("click", handleClick);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={menuRef}
|
||||
className="relative w-[90px] cursor-pointer flex items-center gap-x-5"
|
||||
onClick={() => {
|
||||
setActive(!active);
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center px-[12px]">
|
||||
<p>{activeLang.title}</p>
|
||||
<Image
|
||||
src={triangle}
|
||||
alt="arrow"
|
||||
className={clsx("transition-rotate duration-300 img-auto", {
|
||||
"rotate-180": active,
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
<AnimatePresence>
|
||||
{active && (
|
||||
<motion.div
|
||||
initial={{
|
||||
opacity: 0,
|
||||
}}
|
||||
animate={{
|
||||
opacity: 1,
|
||||
}}
|
||||
exit={{
|
||||
opacity: 0,
|
||||
}}
|
||||
transition={{
|
||||
duration: 0.2,
|
||||
}}
|
||||
className="absolute rounded-[4px] w-[112px] overflow-hidden custom-shadow z-10 flex flex-col top-[27px] bg-[#EEF1F0] transition-all duration-300"
|
||||
>
|
||||
{lang
|
||||
.filter((item) => item.title !== activeLang.title)
|
||||
.map((item, i) => (
|
||||
<div
|
||||
key={i}
|
||||
onClick={() => setLang(item)}
|
||||
className={clsx(
|
||||
"p-3 pr-[22px] py-4 text-extraSm transition-all",
|
||||
{
|
||||
"hover:bg-ON_SECONDARY_CONTAINER/[8%]":
|
||||
item.title === item.title,
|
||||
}
|
||||
)}
|
||||
>
|
||||
{item.title}
|
||||
</div>
|
||||
))}
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import Link from 'next/link';
|
||||
import { FC, PropsWithChildren } from 'react';
|
||||
|
||||
interface Props {
|
||||
href: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const LinkButton: FC<PropsWithChildren<Props>> = ({
|
||||
className,
|
||||
href,
|
||||
children,
|
||||
}: PropsWithChildren<Props>) => {
|
||||
return (
|
||||
<Link href={href} className={className}>
|
||||
<button
|
||||
className={clsx(
|
||||
'px-12 py-[17px] text-[14px] rounded-[4px] bg-green text-[#346560] hover:bg-opacity-90 font-medium transition-all',
|
||||
className,
|
||||
)}>
|
||||
{children}
|
||||
</button>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import { FC, PropsWithChildren } from 'react';
|
||||
import clsx from 'clsx';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const PrimaryButton: FC<PropsWithChildren<Props>> = ({ className, children }) => {
|
||||
return (
|
||||
<button
|
||||
className={clsx(
|
||||
'bg-PRIMARY text-[14px] text-white rounded-[4px] font-medium h-12 hover:bg-PRIMARY/90 transition-all',
|
||||
className,
|
||||
)}>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import clsx from 'clsx';
|
||||
import { FC, PropsWithChildren } from 'react';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const SecondaryButton: FC<PropsWithChildren<Props>> = ({ className, children }) => {
|
||||
return (
|
||||
<button
|
||||
className={clsx(
|
||||
'bg-SECONDARY_CONTAINER rounded-[4px] text-ON_SECONDARY_CONTAINER text-[14px] font-medium h-12',
|
||||
className,
|
||||
)}>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
import React from "react";
|
||||
|
||||
export const Title = ({ text }: { text: string | boolean }) => {
|
||||
return (
|
||||
<h2 className={`text-[26px] sm:text-[34px] leading-[115%] font-normal`}>
|
||||
{text}
|
||||
</h2>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
[.ShellClassInfo]
|
||||
LocalizedResourceName=@turkmen_expo,0
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
export const useSliderBanner = (isTab: boolean, isMd: boolean): string => {
|
||||
if (isTab) {
|
||||
return 'main-surat';
|
||||
} else if (isMd) {
|
||||
return 'medium-surat';
|
||||
} else {
|
||||
return 'small-surat';
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
export const useStorage = (key: string) => {
|
||||
const setItem = (value: unknown) => {
|
||||
try {
|
||||
window.localStorage.setItem(key, JSON.stringify(value));
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
const getItem = () => {
|
||||
try {
|
||||
const item = window.localStorage.getItem(key);
|
||||
return item ? JSON.parse(item) : "";
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
const removeItem = () => {
|
||||
try {
|
||||
window.localStorage.removeItem(key);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
return { setItem, getItem, removeItem };
|
||||
};
|
||||
|
|
@ -0,0 +1 @@
|
|||
export const baseAPI = "https://turkmenexpo.com/app/api/v1/";
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
export const burgerMenu = [
|
||||
{
|
||||
title: "Календарь мероприятий",
|
||||
titleEn: "Calendar of events",
|
||||
link: "/calendar",
|
||||
},
|
||||
{
|
||||
services: true,
|
||||
title: "Услуги",
|
||||
drop: true,
|
||||
titleEn: "Services",
|
||||
link: "/services/organization",
|
||||
|
||||
dropDown: [
|
||||
{
|
||||
title: "Организация выставок",
|
||||
titleEn: "Organization of exhibitions",
|
||||
link: "/services/organization",
|
||||
},
|
||||
{
|
||||
title: "B2B Встречи и Матчмейкинг",
|
||||
titleEn: "B2B Meetings & Business Matchmaking",
|
||||
link: "/services/meetings",
|
||||
},
|
||||
{
|
||||
title: "Бизнес-Миссии",
|
||||
titleEn: "Business Missions",
|
||||
link: "/services/missions",
|
||||
},
|
||||
|
||||
{
|
||||
title: "Рекламная деятельность",
|
||||
titleEn: "Advertising activity",
|
||||
link: "/services/advertising",
|
||||
},
|
||||
{
|
||||
title: "Организация гибридных мероприятий",
|
||||
titleEn: "Organization of hybrid events",
|
||||
link: "/services/hybrid-organizations",
|
||||
},
|
||||
{
|
||||
title: "Сертификация",
|
||||
titleEn: "Certification",
|
||||
link: "/services/certification",
|
||||
},
|
||||
{
|
||||
title: "Экспедирование грузов и таможенное оформление",
|
||||
titleEn: "Freight forwarding and customs clearance",
|
||||
link: "/services/forwarding",
|
||||
},
|
||||
{
|
||||
title: "Аутсорсинг бух.учета и аудита",
|
||||
titleEn: "Outsourcing of accounting and auditing",
|
||||
link: "/services/outsourcing",
|
||||
},
|
||||
{
|
||||
title: "Обучения персонала",
|
||||
titleEn: "Personnel-training",
|
||||
link: "/services/personnel-training",
|
||||
},
|
||||
{
|
||||
title: "Бизнес - туры",
|
||||
titleEn: "Business - tours",
|
||||
link: "/services/business-tours",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "О компании",
|
||||
titleEn: "Calendar of events",
|
||||
link: "/company/aboutus",
|
||||
},
|
||||
{
|
||||
title: "Новости",
|
||||
titleEn: "News",
|
||||
link: "/news",
|
||||
},
|
||||
{
|
||||
title: "Контакты",
|
||||
titleEn: "Contacts",
|
||||
link: "/contacts",
|
||||
},
|
||||
|
||||
// { en: true, title: "Exhibition", link: "/exhibition-about" },
|
||||
// { en: true, title: "Participants", link: "/participants-info" },
|
||||
// { en: true, title: "For visitors", link: "/visitors-info" },
|
||||
];
|
||||
|
||||
export const burgerMenu2 = [
|
||||
{
|
||||
company: true,
|
||||
title: "О компании",
|
||||
|
||||
titleEn: "About company",
|
||||
link: "/company/aboutus",
|
||||
drop: true,
|
||||
dropDown: [
|
||||
{
|
||||
title: "О нас",
|
||||
titleEn: "About us",
|
||||
link: "/company/aboutus",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Новости",
|
||||
|
||||
titleEn: "News",
|
||||
link: "/news",
|
||||
},
|
||||
{
|
||||
title: "FAQ",
|
||||
|
||||
titleEn: "FAQ",
|
||||
link: "/faq",
|
||||
},
|
||||
{
|
||||
title: "Контакты",
|
||||
|
||||
titleEn: "Контакты",
|
||||
link: "/contacts",
|
||||
},
|
||||
];
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
import car from '@/public/assets/icons/service/car.svg';
|
||||
import bus from '@/public/assets/icons/service/bus.svg';
|
||||
import track from '@/public/assets/icons/service/track.svg';
|
||||
|
||||
import { RoadCardProps } from '../types';
|
||||
|
||||
export const roadCardData: RoadCardProps[] = [
|
||||
{
|
||||
icon: car,
|
||||
text: 'На машине',
|
||||
|
||||
enText: 'Car',
|
||||
},
|
||||
{
|
||||
icon: bus,
|
||||
text: 'На автобусе',
|
||||
|
||||
enText: 'Public transport',
|
||||
},
|
||||
{
|
||||
icon: track,
|
||||
text: 'Freight transport',
|
||||
|
||||
enText: 'Car',
|
||||
},
|
||||
];
|
||||
|
||||
import call from '@/public/assets/icons/service/call.svg';
|
||||
import megaphone from '@/public/assets/icons/service/megaphone.svg';
|
||||
import bag from '@/public/assets/icons/service/bag.svg';
|
||||
|
||||
export const contactCardData: RoadCardProps[] = [
|
||||
{
|
||||
icon: call,
|
||||
text: 'Справочный центр',
|
||||
|
||||
enText: 'Call centre',
|
||||
},
|
||||
{
|
||||
icon: bag,
|
||||
text: 'Услуги',
|
||||
|
||||
enText: 'Servise bureau',
|
||||
},
|
||||
{
|
||||
icon: megaphone,
|
||||
text: 'Реклама',
|
||||
|
||||
enText: 'Advertising',
|
||||
},
|
||||
];
|
||||
|
||||
import partner from '@/public/assets/icons/home/partner.svg';
|
||||
import { StaticImageData } from 'next/image';
|
||||
|
||||
export const partnersData: StaticImageData[] = [partner, partner, partner, partner, partner];
|
||||
|
|
@ -0,0 +1,432 @@
|
|||
interface MenuType {
|
||||
pathname: string;
|
||||
pathnameEn: string;
|
||||
company?: boolean;
|
||||
members?: boolean;
|
||||
visitors?: boolean;
|
||||
news?: boolean;
|
||||
services?: boolean;
|
||||
info: {
|
||||
titleEn: string;
|
||||
title: string;
|
||||
link: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
export const sidebarData: MenuType[] = [
|
||||
{
|
||||
company: true,
|
||||
pathname: 'О компании',
|
||||
pathnameEn: 'About company',
|
||||
info: [
|
||||
{ title: 'Коротко о нас', titleEn: 'About us', link: '/company/aboutus' },
|
||||
// { title: 'Выставочная деятельность', link: '' },
|
||||
// { title: 'История и награды', link: '' },
|
||||
// { title: 'Партнеры', link: '' },
|
||||
// { title: 'Работы в компании', link: '' },
|
||||
// { title: 'Наши издания', link: '' },
|
||||
],
|
||||
},
|
||||
{
|
||||
members: true,
|
||||
pathname: 'Участникам',
|
||||
pathnameEn: 'Participants',
|
||||
|
||||
info: [
|
||||
{
|
||||
title: 'Информация для участников',
|
||||
titleEn: 'Information for participants',
|
||||
link: '/members',
|
||||
},
|
||||
{
|
||||
title: 'Онлайн заявка для участников',
|
||||
titleEn: 'Online application',
|
||||
link: '/members/bid',
|
||||
},
|
||||
{
|
||||
title: 'Правила для участников',
|
||||
titleEn: 'Rules for participants',
|
||||
link: '/members/members-rules',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
news: true,
|
||||
pathname: 'Новости',
|
||||
pathnameEn: 'News',
|
||||
|
||||
info: [
|
||||
{ title: 'Новости', titleEn: 'News', link: '/news' },
|
||||
// { title: 'Пресс-релизы', link: '' },
|
||||
],
|
||||
},
|
||||
{
|
||||
visitors: true,
|
||||
pathname: 'Посетителям',
|
||||
pathnameEn: 'Visitors',
|
||||
|
||||
info: [
|
||||
{
|
||||
title: 'Информация для посетителей',
|
||||
titleEn: 'Information for visitors',
|
||||
link: '/visitors',
|
||||
},
|
||||
{
|
||||
title: 'Порядок регистрации посетителей',
|
||||
titleEn: 'Entrance rules',
|
||||
link: '/visitors/rules-for-visitors',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
services: true,
|
||||
pathname: 'Услуги',
|
||||
pathnameEn: 'Services',
|
||||
|
||||
info: [
|
||||
{
|
||||
title: 'Организация выставок',
|
||||
titleEn: 'Organization of exhibitions',
|
||||
link: '/services/organization',
|
||||
},
|
||||
{
|
||||
title: 'B2B Встречи и Матчмейкинг',
|
||||
titleEn: 'B2B Meetings & Business Matchmaking',
|
||||
link: '/services/meetings',
|
||||
},
|
||||
{
|
||||
title: 'Бизнес-Миссии',
|
||||
titleEn: 'Business Missions',
|
||||
link: '/services/missions',
|
||||
},
|
||||
{
|
||||
title: 'Маркетинговые услуги',
|
||||
titleEn: 'Advertising activity',
|
||||
link: '/services/advertising',
|
||||
},
|
||||
{
|
||||
title: 'Организация гибридных мероприятий',
|
||||
titleEn: 'Organization of hybrid events',
|
||||
link: '/services/hybrid-organizations',
|
||||
},
|
||||
{
|
||||
title: 'Сертификация',
|
||||
titleEn: 'Certification',
|
||||
link: '/services/certification',
|
||||
},
|
||||
{
|
||||
title: 'Организация перевозки и таможенное оформление',
|
||||
titleEn: 'Freight forwarding and customs clearance',
|
||||
link: '/services/forwarding',
|
||||
},
|
||||
{
|
||||
title: 'Аутсорсинг бухгалтерского учета и аудита',
|
||||
titleEn: 'Outsourcing of accounting and auditing',
|
||||
link: '/services/outsourcing',
|
||||
},
|
||||
{
|
||||
title: 'Тренинг для персонала',
|
||||
titleEn: 'Personnel-training',
|
||||
link: '/services/personnel-training',
|
||||
},
|
||||
{
|
||||
title: 'Бизнес - туры',
|
||||
titleEn: 'Business - tours',
|
||||
link: '/services/business-tours',
|
||||
},
|
||||
{
|
||||
title: 'Дополнительные услуги',
|
||||
titleEn: 'Additional servies',
|
||||
link: '/services/additional',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
interface BurgerDataTypes {
|
||||
pathname: string;
|
||||
company?: boolean;
|
||||
members?: boolean;
|
||||
calendar?: boolean;
|
||||
faq?: boolean;
|
||||
news?: boolean;
|
||||
only?: boolean;
|
||||
services?: boolean;
|
||||
visitors?: boolean;
|
||||
contacts?: boolean;
|
||||
first?: boolean;
|
||||
title: string;
|
||||
drop?: string;
|
||||
info?: {
|
||||
title: string;
|
||||
link: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
export const burgerMenuData: BurgerDataTypes[] = [
|
||||
{
|
||||
pathname: '/calendar',
|
||||
calendar: true,
|
||||
only: true,
|
||||
title: 'Календарь мероприятий',
|
||||
first: true,
|
||||
},
|
||||
{
|
||||
members: true,
|
||||
title: 'Участникам',
|
||||
pathname: '/members',
|
||||
drop: 'members',
|
||||
first: true,
|
||||
info: [
|
||||
{ title: 'Информация для участников', link: '/members' },
|
||||
{ title: 'Онлайн заявка для участников', link: '/members/bid' },
|
||||
],
|
||||
},
|
||||
{
|
||||
pathname: '/visitors',
|
||||
visitors: true,
|
||||
only: true,
|
||||
title: 'Посетителям',
|
||||
first: true,
|
||||
},
|
||||
{
|
||||
pathname: '/services',
|
||||
services: true,
|
||||
only: true,
|
||||
title: 'Услуги',
|
||||
first: true,
|
||||
},
|
||||
|
||||
{
|
||||
pathname: '/faq',
|
||||
faq: true,
|
||||
only: true,
|
||||
title: 'FAQ',
|
||||
},
|
||||
{
|
||||
pathname: '/contacts',
|
||||
contacts: true,
|
||||
only: true,
|
||||
title: 'Контакты',
|
||||
},
|
||||
{
|
||||
company: true,
|
||||
title: 'О компании',
|
||||
pathname: '/company/aboutus',
|
||||
drop: 'company',
|
||||
info: [
|
||||
{ title: 'Коротко о нас', link: '/company/aboutus' },
|
||||
// { title: "Выставочная деятельность", link: "" },
|
||||
// { title: "История и награды", link: "" },
|
||||
// { title: "Партнеры", link: "" },
|
||||
// { title: "Работы в компании", link: "" },
|
||||
// { title: "Наши издания", link: "" },
|
||||
],
|
||||
},
|
||||
{
|
||||
news: true,
|
||||
title: 'Новости',
|
||||
pathname: '/news',
|
||||
drop: 'news',
|
||||
info: [
|
||||
{ title: 'Новости', link: '/news' },
|
||||
// { title: "Пресс-релизы", link: "/news" },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
interface HeaderType {
|
||||
title: string;
|
||||
link: string;
|
||||
id: number;
|
||||
one?: boolean;
|
||||
en?: boolean;
|
||||
}
|
||||
|
||||
export const headerMenu: HeaderType[] = [
|
||||
{ title: 'О компании', link: '/company/aboutus', id: 1 },
|
||||
{ title: 'Новости', link: '/news', id: 2 },
|
||||
{ title: 'FAQ', link: '/faq', id: 3 },
|
||||
{ title: 'Контакты', link: '/contacts', id: 4 },
|
||||
|
||||
{ en: true, title: 'About company', link: '/company/aboutus', id: 1 },
|
||||
{ en: true, title: 'News', link: '/news', id: 2 },
|
||||
{ en: true, title: 'FAQ', link: '/faq', id: 3 },
|
||||
{ en: true, title: 'Contacts', link: '/contacts', id: 4 },
|
||||
];
|
||||
|
||||
export const headerMenu2: HeaderType[] = [
|
||||
{ title: 'Календарь мероприятий', link: '/calendar', id: 1 },
|
||||
{ title: 'Услуги', link: '/services/organization', id: 2 },
|
||||
{ title: 'О компании', link: '/company/aboutus', id: 3 },
|
||||
{ title: 'Новости', link: '/news', id: 4 },
|
||||
{ title: 'Контакты', link: '/contacts', id: 5 },
|
||||
|
||||
// { title: 'Участникам', link: '/members', id: 2 },
|
||||
// { title: 'Посетителям', link: '/visitors', id: 3 },
|
||||
|
||||
{ en: true, title: 'Calendar of events', link: '/calendar', id: 1 },
|
||||
{ en: true, title: 'Services', link: '/services/organization', id: 2 },
|
||||
{ en: true, title: 'About company', link: '/company/aboutus', id: 3 },
|
||||
{ en: true, title: 'News', link: '/news', id: 4 },
|
||||
{ en: true, title: 'Contacts', link: '/contacts', id: 5 },
|
||||
|
||||
// { en: true, title: 'Participants', link: '/members', id: 2 },
|
||||
// { en: true, title: 'Visitors', link: '/visitors', id: 3 },
|
||||
];
|
||||
|
||||
interface FooterType {
|
||||
title: string;
|
||||
link: string;
|
||||
}
|
||||
|
||||
export const footerMenu = [
|
||||
{ title: 'Календарь мероприятий', link: '/calendar', one: true },
|
||||
{ title: 'Участникам', link: '/members' },
|
||||
{ title: 'Посетителям', link: '' },
|
||||
{ title: 'Услуги', link: '/services/organization' },
|
||||
];
|
||||
|
||||
export const footerMenu2: FooterType[] = [
|
||||
{ title: 'Территория комплекса', link: '' },
|
||||
{ title: 'О компании', link: '/company/aboutus' },
|
||||
{ title: 'Пресс-центр', link: '' },
|
||||
{ title: 'FAQ', link: '/faq' },
|
||||
{ title: 'Контакты', link: '/contacts' },
|
||||
{ title: 'Справочный центр', link: '' },
|
||||
];
|
||||
|
||||
export const footerInfo: string[] = [
|
||||
'Адрес: 744000, г. Ашхабад, просп. Битарап Туркменистан, 183',
|
||||
'Тел.: +99362006200',
|
||||
'+99312454111',
|
||||
'E-mail: contact@turkmenexpo.com',
|
||||
'Address: Ashgabat, 183 Bitarap Turkmenistan',
|
||||
'ave.,744000',
|
||||
'E-mail: contact@turkmenexpo.com',
|
||||
];
|
||||
|
||||
export const topMenu = [
|
||||
{
|
||||
path: 'about',
|
||||
links: [{ active: 'Главная', default: '/ О компании / Коротко нас' }],
|
||||
},
|
||||
{
|
||||
path: 'members',
|
||||
links: [
|
||||
{
|
||||
active: 'Главная',
|
||||
active2: ' / Участникам',
|
||||
default: ' / Информация для участников',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'members/bid',
|
||||
links: [
|
||||
{
|
||||
active: 'Главная',
|
||||
default: ' / Участникам / Онлайн заявка для участников',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'events',
|
||||
links: [
|
||||
{
|
||||
active: 'Главная ',
|
||||
default: '/ Календарь мероприятий',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'faq',
|
||||
links: [
|
||||
{
|
||||
active: 'Главная',
|
||||
default: ' / FAQ',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'contacts',
|
||||
links: [
|
||||
{
|
||||
active: 'Главная',
|
||||
default: ' / Контакты',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'calendar',
|
||||
links: [
|
||||
{
|
||||
active: 'Главная',
|
||||
default: ' / Календарь мероприятий',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'news',
|
||||
links: [
|
||||
{
|
||||
active: 'Главная',
|
||||
default: ' / Новости',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const burgerLinks = [
|
||||
{
|
||||
title: 'О компании',
|
||||
about: true,
|
||||
links: [
|
||||
{ name: 'Коротко о нас', link: '/about/company' },
|
||||
{ name: 'Выставочная деятельность', link: '/about/company' },
|
||||
{ name: 'История и награды', link: '/about/company' },
|
||||
{ name: 'Партнеры', link: '/about/company' },
|
||||
{ name: 'Работа в компании', link: '/about/company' },
|
||||
{ name: 'Наши издания', link: '/about/company' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Новости',
|
||||
news: true,
|
||||
links: [
|
||||
{ name: 'Новости', link: '/news' },
|
||||
{ name: 'Пресс центр', link: '/news' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Участникам',
|
||||
members: true,
|
||||
links: [
|
||||
{ name: 'Участникам', link: '/members' },
|
||||
{ name: 'Онлайн заявка', link: '/members/bid' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Контакты',
|
||||
contacts: true,
|
||||
links: [
|
||||
{ name: 'Новости', link: '/news' },
|
||||
{ name: 'Пресс центр', link: '/news' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'FAQ',
|
||||
faq: true,
|
||||
links: [
|
||||
{ name: 'faq', link: '/news' },
|
||||
{ name: 'Пресс центр', link: '/news' },
|
||||
],
|
||||
},
|
||||
{
|
||||
news: true,
|
||||
links: [
|
||||
{ name: 'Новости', link: '/news' },
|
||||
{ name: 'Пресс центр', link: '/news' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import { cookies } from "next/headers";
|
||||
|
||||
export const getLang = () => {
|
||||
const cookieStore = cookies();
|
||||
const langCookie = cookieStore.get("lang")?.value || "ru";
|
||||
|
||||
// Возвращаем полный объект языка
|
||||
return {
|
||||
title: langCookie === "en" ? "English" : "Русский",
|
||||
localization: langCookie,
|
||||
};
|
||||
};
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
import { StaticImageData } from 'next/image';
|
||||
|
||||
export type EventCardProps = {
|
||||
dark?: boolean;
|
||||
img: StaticImageData;
|
||||
title: string;
|
||||
text: string;
|
||||
suptitle: string;
|
||||
footerText: string;
|
||||
organizer: string;
|
||||
date: string;
|
||||
calendar: string;
|
||||
id: number;
|
||||
};
|
||||
|
||||
export type NewsCardProps = {
|
||||
img: StaticImageData;
|
||||
date: string;
|
||||
text: string;
|
||||
id: number;
|
||||
};
|
||||
|
||||
export type RoadCardProps = {
|
||||
icon: any;
|
||||
text: string;
|
||||
enText: string;
|
||||
};
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
export interface CalendarType {
|
||||
data: EventDatum[];
|
||||
links: Links;
|
||||
meta: Meta;
|
||||
}
|
||||
|
||||
export interface EventDatum {
|
||||
id: number;
|
||||
title: string;
|
||||
description: string;
|
||||
category: string;
|
||||
organizers: Organizer[];
|
||||
coorganizers?: Coorganizers[];
|
||||
starts_at: string;
|
||||
ends_at: string;
|
||||
date: string;
|
||||
images: CalendarImage[];
|
||||
background_images: CalendarImage[];
|
||||
web_site: string;
|
||||
location: string;
|
||||
timing: Timing[];
|
||||
installation_date: string;
|
||||
dismantling_date: string;
|
||||
event_topic: string;
|
||||
}
|
||||
|
||||
export interface Coorganizers {
|
||||
id: number;
|
||||
name: string;
|
||||
address: string;
|
||||
phones: Phone[];
|
||||
fax: string;
|
||||
email: string;
|
||||
web_site: string;
|
||||
}
|
||||
|
||||
export interface Phone {
|
||||
phone: string;
|
||||
}
|
||||
|
||||
export interface CalendarImage {
|
||||
id: number;
|
||||
disk_name: string;
|
||||
file_name: string;
|
||||
path: string;
|
||||
extension: Extension;
|
||||
}
|
||||
|
||||
export enum Extension {
|
||||
ExtensionJPG = "JPG",
|
||||
Jpg = "jpg",
|
||||
PNG = "png",
|
||||
}
|
||||
|
||||
export interface Organizer {
|
||||
id: number;
|
||||
name: string;
|
||||
address: string;
|
||||
phones: PhoneElement[];
|
||||
fax: string;
|
||||
email: string;
|
||||
web_site: string;
|
||||
}
|
||||
|
||||
export interface PhoneElement {
|
||||
phone: PhoneEnum;
|
||||
}
|
||||
|
||||
export enum PhoneEnum {
|
||||
The99312398881 = "(+99312) 39 88 81",
|
||||
The99312398981 = "(+99312) 39 89 81",
|
||||
The99362006200 = "+993 62 006200",
|
||||
}
|
||||
|
||||
export interface Timing {
|
||||
date: null;
|
||||
time: null;
|
||||
}
|
||||
|
||||
export interface Links {
|
||||
first: string;
|
||||
last: string;
|
||||
prev: null;
|
||||
next: null;
|
||||
}
|
||||
|
||||
export interface Meta {
|
||||
current_page: number;
|
||||
from: number;
|
||||
last_page: number;
|
||||
links: Link[];
|
||||
path: string;
|
||||
per_page: number;
|
||||
to: number;
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface Link {
|
||||
url: null | string;
|
||||
label: string;
|
||||
active: boolean;
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
export interface ContactsDataType {
|
||||
data: Datum[];
|
||||
}
|
||||
|
||||
export interface Datum {
|
||||
header: string;
|
||||
services: Service[];
|
||||
}
|
||||
|
||||
export interface Service {
|
||||
title: string;
|
||||
phone: string;
|
||||
email: string;
|
||||
web_site: string;
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
export interface EventType {
|
||||
data: Data[];
|
||||
}
|
||||
|
||||
export interface Data {
|
||||
id: number;
|
||||
title: string;
|
||||
description: string;
|
||||
category: string;
|
||||
organizer: Organizer;
|
||||
coorganizer: Organizer;
|
||||
starts_at: Date;
|
||||
ends_at: Date;
|
||||
images: Image[];
|
||||
web_site: null;
|
||||
location: null;
|
||||
timing: any[];
|
||||
event_topic: null;
|
||||
}
|
||||
|
||||
export interface Image {
|
||||
id: number;
|
||||
disk_name: string;
|
||||
file_name: string;
|
||||
path: string;
|
||||
extension: string;
|
||||
}
|
||||
|
||||
export interface Organizer {
|
||||
name: string;
|
||||
address: null;
|
||||
phones: null;
|
||||
fax: null;
|
||||
email: null;
|
||||
web_site: null;
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
export interface FaqDataType {
|
||||
data: Datum[];
|
||||
}
|
||||
|
||||
export interface Datum {
|
||||
header: string;
|
||||
faq_items: FaqItemsType[];
|
||||
}
|
||||
|
||||
export interface FaqItemsType {
|
||||
answer: string;
|
||||
question: string;
|
||||
}
|
||||
|
||||
export interface RadioDataType {
|
||||
data: Datu[];
|
||||
}
|
||||
|
||||
export interface Datu {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
export interface NewsData {
|
||||
data: Datum[];
|
||||
links: Links;
|
||||
meta: Meta;
|
||||
}
|
||||
|
||||
export interface Datum {
|
||||
id: number;
|
||||
title: string;
|
||||
published_at: string;
|
||||
featured_images: FeaturedImage[];
|
||||
content_html: string;
|
||||
}
|
||||
|
||||
export interface FeaturedImage {
|
||||
id: number;
|
||||
disk_name: string;
|
||||
file_name: string;
|
||||
path: string;
|
||||
extension: string;
|
||||
}
|
||||
|
||||
export interface Links {
|
||||
first: string;
|
||||
last: string;
|
||||
prev: null;
|
||||
next: null;
|
||||
}
|
||||
|
||||
export interface Meta {
|
||||
current_page: number;
|
||||
from: number;
|
||||
last_page: number;
|
||||
links: Link[];
|
||||
path: string;
|
||||
per_page: number;
|
||||
to: number;
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface Link {
|
||||
url: null | string;
|
||||
label: string;
|
||||
active: boolean;
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
export interface NewsItemData {
|
||||
data: Data;
|
||||
}
|
||||
|
||||
export interface Data {
|
||||
id: number;
|
||||
title: string;
|
||||
published_at: string;
|
||||
featured_images: FeaturedImage[];
|
||||
content_html: string;
|
||||
}
|
||||
|
||||
export interface FeaturedImage {
|
||||
id: number;
|
||||
disk_name: string;
|
||||
file_name: string;
|
||||
path: string;
|
||||
extension: string;
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
export interface NewsPageType {
|
||||
data: Data;
|
||||
}
|
||||
|
||||
export interface Data {
|
||||
id: number;
|
||||
title: string;
|
||||
published_at: string;
|
||||
featured_images: FeaturedImage[];
|
||||
content_html: string;
|
||||
}
|
||||
|
||||
export interface FeaturedImage {
|
||||
id: number;
|
||||
disk_name: string;
|
||||
file_name: string;
|
||||
path: string;
|
||||
extension: string;
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
export interface ParticipantsDataType {
|
||||
data: Datum[];
|
||||
}
|
||||
|
||||
export interface Datum {
|
||||
title: string;
|
||||
content: string;
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
export interface PartnersType {
|
||||
data: Datum[];
|
||||
}
|
||||
|
||||
export interface Datum {
|
||||
images: Image[];
|
||||
link: string;
|
||||
}
|
||||
|
||||
export interface Image {
|
||||
id: number;
|
||||
disk_name: string;
|
||||
file_name: string;
|
||||
path: string;
|
||||
extension: string;
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
export type PostParticipantFormTypes = {
|
||||
company_name: string;
|
||||
phone: string;
|
||||
email: string;
|
||||
area: number;
|
||||
contact_person: string;
|
||||
what_demonstrated: string;
|
||||
web_site: string;
|
||||
};
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
export interface SearchTypes {
|
||||
status_code: number;
|
||||
message: string;
|
||||
data: Data;
|
||||
}
|
||||
|
||||
export interface Data {
|
||||
expo_events: ExpoEvent[];
|
||||
posts: Post[];
|
||||
}
|
||||
|
||||
export interface ExpoEvent {
|
||||
id: number;
|
||||
title: string;
|
||||
description: string;
|
||||
category: string;
|
||||
organizer: Organizer;
|
||||
starts_at: Date;
|
||||
ends_at: Date;
|
||||
images: Image[];
|
||||
background_images: Image[];
|
||||
web_site: string;
|
||||
location: string;
|
||||
timing: Timing[];
|
||||
event_topic: string;
|
||||
}
|
||||
|
||||
export interface Image {
|
||||
id: number;
|
||||
disk_name: string;
|
||||
file_name: string;
|
||||
path: string;
|
||||
extension: string;
|
||||
}
|
||||
|
||||
export interface Organizer {
|
||||
name: string;
|
||||
address: string;
|
||||
phones: Phone[];
|
||||
fax: string;
|
||||
email: string;
|
||||
web_site: string;
|
||||
}
|
||||
|
||||
export interface Phone {
|
||||
phone: string;
|
||||
}
|
||||
|
||||
export interface Timing {
|
||||
date: string;
|
||||
time: string;
|
||||
}
|
||||
|
||||
export interface Post {
|
||||
id: number;
|
||||
title: string;
|
||||
published_at: string;
|
||||
featured_images: Image[];
|
||||
content_html: string;
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
export interface ServicesType {
|
||||
data: Datum[];
|
||||
}
|
||||
|
||||
export interface Datum {
|
||||
title: string;
|
||||
content: string;
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
export interface SliderType {
|
||||
data: Data;
|
||||
}
|
||||
|
||||
export interface Data {
|
||||
code: string;
|
||||
banner_items: BannerItem[];
|
||||
}
|
||||
|
||||
export interface BannerItem {
|
||||
title: string;
|
||||
text: string;
|
||||
image: string;
|
||||
link: string;
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
export interface EventPageType {
|
||||
data: Data;
|
||||
}
|
||||
|
||||
export interface Data {
|
||||
id: number;
|
||||
title: string;
|
||||
description: string;
|
||||
category: string;
|
||||
organizers: Organizer[];
|
||||
coorganizers: any[];
|
||||
starts_at: Date;
|
||||
ends_at: Date;
|
||||
date: string;
|
||||
images: Image[];
|
||||
background_images: any[];
|
||||
web_site: string;
|
||||
location: string;
|
||||
timing: Timing[];
|
||||
installation_date: string;
|
||||
dismantling_date: string;
|
||||
event_topic: string;
|
||||
url_registration: string;
|
||||
url_web: string;
|
||||
url_detailed: string;
|
||||
our: number;
|
||||
}
|
||||
|
||||
export interface Image {
|
||||
id: number;
|
||||
disk_name: string;
|
||||
file_name: string;
|
||||
path: string;
|
||||
extension: string;
|
||||
}
|
||||
|
||||
export interface Organizer {
|
||||
id: number;
|
||||
name: string;
|
||||
address: string;
|
||||
phones: Phone[];
|
||||
fax: string;
|
||||
email: string;
|
||||
web_site: string;
|
||||
}
|
||||
|
||||
export interface Phone {
|
||||
phone: string;
|
||||
}
|
||||
|
||||
export interface Timing {
|
||||
date: null;
|
||||
time: null;
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
images: { unoptimized: true },
|
||||
distDir: "build",
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"name": "turkmen_expo_client",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"out": "npx serve@latest out",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^3.3.4",
|
||||
"@react-google-maps/api": "^2.19.3",
|
||||
"@reduxjs/toolkit": "^2.2.1",
|
||||
"@types/uuid": "^9.0.8",
|
||||
"clsx": "^2.1.0",
|
||||
"framer-motion": "^11.0.25",
|
||||
"next": "14.1.0",
|
||||
"pm2": "^5.3.1",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"react-hook-form": "^7.50.1",
|
||||
"react-redux": "^9.1.0",
|
||||
"serve": "^14.2.1",
|
||||
"sharp": "^0.33.2",
|
||||
"swiper": "^11.0.6",
|
||||
"use-debounce": "^10.0.0",
|
||||
"usehooks-ts": "^3.1.0",
|
||||
"uuid": "^9.0.1",
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"autoprefixer": "^10.0.1",
|
||||
"postcss": "^8",
|
||||
"tailwindcss": "^3.3.0",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
E39636BA666F04A450B8FC56D9A05A3F2EFC6D3E1ED62E834CE055A20630AB45
|
||||
comodoca.com
|
||||
yWg9G5g4S4
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="19" height="20" viewBox="0 0 19 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14 20V17H11V15H14V12H16V15H19V17H16V20H14ZM2 18C1.45 18 0.979333 17.8043 0.588 17.413C0.196667 17.0217 0.000666667 16.5507 0 16V4C0 3.45 0.196 2.97933 0.588 2.588C0.98 2.19667 1.45067 2.00067 2 2H3V0H5V2H11V0H13V2H14C14.55 2 15.021 2.196 15.413 2.588C15.805 2.98 16.0007 3.45067 16 4V10.1C15.6667 10.05 15.3333 10.025 15 10.025C14.6667 10.025 14.3333 10.05 14 10.1V8H2V16H9C9 16.3333 9.025 16.6667 9.075 17C9.125 17.3333 9.21667 17.6667 9.35 18H2ZM2 6H14V4H2V6Z" fill="#8D9399"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 593 B |
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="10" height="9" viewBox="0 0 10 9" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1 4L4.5 7.5L9 0.5" stroke="#059784" stroke-width="1.5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 168 B |
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M15.9997 12L10.6663 17.3333L21.333 17.3333L15.9997 12ZM15.9997 2.66665C17.8441 2.66665 19.5774 3.01687 21.1997 3.71731C22.8219 4.41776 24.233 5.36753 25.433 6.56665C26.633 7.76665 27.5828 9.17776 28.2823 10.8C28.9819 12.4222 29.3321 14.1555 29.333 16C29.333 17.8444 28.9828 19.5778 28.2823 21.2C27.5819 22.8222 26.6321 24.2333 25.433 25.4333C24.233 26.6333 22.8219 27.5831 21.1997 28.2826C19.5775 28.9822 17.8441 29.3324 15.9997 29.3333C14.1552 29.3333 12.4219 28.9831 10.7997 28.2826C9.17745 27.5822 7.76634 26.6324 6.56634 25.4333C5.36634 24.2333 4.41612 22.8222 3.71567 21.2C3.01523 19.5778 2.66545 17.8444 2.66634 16C2.66634 14.1555 3.01656 12.4222 3.71701 10.8C4.41745 9.17776 5.36723 7.76665 6.56634 6.56665C7.76634 5.36665 9.17745 4.41643 10.7997 3.71598C12.4219 3.01554 14.1552 2.66576 15.9997 2.66665ZM15.9997 5.33331C13.0219 5.33331 10.4997 6.36665 8.43301 8.43332C6.36634 10.5 5.33301 13.0222 5.33301 16C5.33301 18.9778 6.36634 21.5 8.43301 23.5666C10.4997 25.6333 13.0219 26.6666 15.9997 26.6666C18.9775 26.6666 21.4997 25.6333 23.5663 23.5666C25.633 21.5 26.6663 18.9778 26.6663 16C26.6663 13.0222 25.633 10.5 23.5663 8.43331C21.4997 6.36665 18.9775 5.33331 15.9997 5.33331Z" fill="#059784"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5 5C4.73478 5 4.48043 5.10536 4.29289 5.29289C4.11141 5.47438 4.00688 5.71844 4.00033 5.97437C4.23032 9.6183 5.78159 13.0537 8.36394 15.6361C10.9463 18.2184 14.3817 19.7697 18.0256 19.9997C18.2816 19.9931 18.5256 19.8886 18.7071 19.7071C18.8946 19.5196 19 19.2652 19 19V15.677L15.4193 14.2448L14.3575 16.0145C14.0897 16.4608 13.5244 16.6271 13.0577 16.3969C10.6887 15.2285 8.77146 13.3113 7.60314 10.9423C7.37294 10.4756 7.53924 9.91027 7.9855 9.64251L9.75523 8.58067L8.32297 5H5ZM2.87868 3.87868C3.44129 3.31607 4.20435 3 5 3H9C9.4089 3 9.77661 3.24895 9.92848 3.62861L11.9285 8.62861C12.1108 9.08432 11.9354 9.60497 11.5145 9.85749L9.8405 10.8619C10.6644 12.2055 11.7945 13.3356 13.1381 14.1595L14.1425 12.4855C14.395 12.0646 14.9157 11.8892 15.3714 12.0715L20.3714 14.0715C20.751 14.2234 21 14.5911 21 15V19C21 19.7957 20.6839 20.5587 20.1213 21.1213C19.5587 21.6839 18.7957 22 18 22C17.9798 22 17.9595 21.9994 17.9393 21.9982C13.7948 21.7463 9.88576 19.9863 6.94972 17.0503C4.01369 14.1142 2.25371 10.2052 2.00184 6.06066C2.00061 6.04046 2 6.02023 2 6C2 5.20435 2.31607 4.44129 2.87868 3.87868Z" fill="#2C57A7"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 6.1 KiB |