reinit
This commit is contained in:
parent
f7fdf51526
commit
26d5647782
|
|
@ -0,0 +1,37 @@
|
|||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
# /out/
|
||||
# /out/
|
||||
|
||||
# production
|
||||
# /out
|
||||
|
||||
# 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
|
||||
|
||||
.vscode
|
||||
|
|
@ -0,0 +1,222 @@
|
|||
import baseUrl from '@/baseUrl';
|
||||
import { AddPostModel } from '@/models/add.post.model';
|
||||
import { IAllVotes } from '@/models/allVotes.model';
|
||||
import { BannerModel } from '@/models/banner.model';
|
||||
import { CategoriesModel } from '@/models/categories.model';
|
||||
import { ChannelsModel } from '@/models/channels.model';
|
||||
import { HomeModel } from '@/models/home.model';
|
||||
import { LiveDescriptionModel } from '@/models/liveDescription.model';
|
||||
import { MarqueeModel } from '@/models/marquee.model';
|
||||
import { NewsModel } from '@/models/news.model';
|
||||
import { NewsItemModel } from '@/models/newsItem.model';
|
||||
import { PageItemModel } from '@/models/pageItem.model';
|
||||
import { PlansModel } from '@/models/plans.model';
|
||||
import { PropertiesModel } from '@/models/properties.model';
|
||||
import { IQuizQuestionsHistory } from '@/models/quizQuestionHistory.model';
|
||||
import { IQuizQuestions } from '@/models/quizQuestions.model';
|
||||
import { IQuizQuestionsWinners } from '@/models/quizQuestionsWinners.model';
|
||||
import { MessagesByTvAdmin } from '@/models/sms/messagesByTvAdmis.model';
|
||||
import { IMyTvAdmins } from '@/models/sms/my.tv.admins.model';
|
||||
import { VideoModel } from '@/models/video.model';
|
||||
import { VideosModel } from '@/models/videos.model';
|
||||
import { IVote } from '@/models/vote.model';
|
||||
import routes from '@/routes';
|
||||
import { CloudFog } from 'lucide-react';
|
||||
|
||||
export class Queries {
|
||||
public static async getNews(
|
||||
page: number,
|
||||
{ perPage = 8 }: { perPage?: number },
|
||||
): Promise<NewsModel> {
|
||||
return await fetch(
|
||||
`${baseUrl.NEWS_SRC}${routes.news}?locale=tm&count=${perPage}&page=${page}`,
|
||||
{
|
||||
next: { revalidate: 3600 },
|
||||
},
|
||||
).then((res) => res.json().then((res) => res as NewsModel));
|
||||
}
|
||||
|
||||
public static async getlastNews(): Promise<NewsModel> {
|
||||
return await fetch(`${baseUrl.NEWS_SRC}${routes.news}?locale=tm&count=5`, {
|
||||
next: { revalidate: 3600 },
|
||||
}).then((res) => res.json().then((res) => res as NewsModel));
|
||||
}
|
||||
|
||||
public static async getNewsItem(id: string): Promise<NewsItemModel> {
|
||||
return await fetch(`${baseUrl.NEWS_SRC}${routes.newsItem(id)}?locale=tm`, {
|
||||
next: { revalidate: 3600 },
|
||||
}).then((res) => res.json().then((res) => res as NewsItemModel));
|
||||
}
|
||||
|
||||
public static async getCategories(): Promise<CategoriesModel> {
|
||||
return await fetch(`${baseUrl.MATERIALS_SRC}${routes.categories}`, {
|
||||
next: { revalidate: 3600 },
|
||||
}).then((res) => res.json().then((res) => res as CategoriesModel));
|
||||
}
|
||||
|
||||
public static async getChannels(): Promise<ChannelsModel> {
|
||||
return await fetch(`${baseUrl.MATERIALS_SRC}${routes.channels}`, {
|
||||
next: { revalidate: 3600 },
|
||||
}).then((res) => res.json().then((res) => res as ChannelsModel));
|
||||
}
|
||||
|
||||
public static async getVideos(search: string): Promise<VideosModel> {
|
||||
return await fetch(`${baseUrl.MATERIALS_SRC}${routes.videos(search)}`, {
|
||||
next: { revalidate: 3600 },
|
||||
}).then((res) => res.json().then((res) => res as VideosModel));
|
||||
}
|
||||
|
||||
public static async getLastVideos(): Promise<VideosModel> {
|
||||
return await fetch(`${baseUrl.MATERIALS_SRC}${routes.lastVideos}`, {
|
||||
next: { revalidate: 3600 },
|
||||
}).then((res) => res.json().then((res) => res as VideosModel));
|
||||
}
|
||||
|
||||
public static async getVideo(id: number): Promise<VideoModel> {
|
||||
return await fetch(`${baseUrl.MATERIALS_SRC}${routes.video(id)}`, {
|
||||
next: { revalidate: 3600 },
|
||||
}).then((res) => res.json().then((res) => res as VideoModel));
|
||||
}
|
||||
|
||||
public static async getPage(id: string): Promise<PageItemModel> {
|
||||
return await fetch(`${baseUrl.MATERIALS_SRC}${routes.pageItem(id)}`, {
|
||||
next: { revalidate: 3600 },
|
||||
}).then((res) => res.json().then((res) => res as PageItemModel));
|
||||
}
|
||||
|
||||
public static async getHome(): Promise<HomeModel> {
|
||||
return await fetch(`${baseUrl.MATERIALS_SRC}${routes.home}`, {
|
||||
next: { revalidate: 3600 },
|
||||
}).then((res) => res.json().then((res) => res as HomeModel));
|
||||
}
|
||||
|
||||
public static async getSmallSlider1(): Promise<HomeModel> {
|
||||
return await fetch(`${baseUrl.MATERIALS_SRC}${routes.homeSmallSlider_1}`, {
|
||||
next: { revalidate: 3600 },
|
||||
}).then((res) => res.json().then((res) => res as HomeModel));
|
||||
}
|
||||
public static async getSmallSlider2(): Promise<HomeModel> {
|
||||
return await fetch(`${baseUrl.MATERIALS_SRC}${routes.homeSmallSlider_2}`, {
|
||||
next: { revalidate: 3600 },
|
||||
}).then((res) => res.json().then((res) => res as HomeModel));
|
||||
}
|
||||
public static async getSmallSlider3(): Promise<HomeModel> {
|
||||
return await fetch(`${baseUrl.MATERIALS_SRC}${routes.homeSmallSlider_1}`, {
|
||||
next: { revalidate: 3600 },
|
||||
}).then((res) => res.json().then((res) => res as HomeModel));
|
||||
}
|
||||
public static async getSmallSlider4(): Promise<HomeModel> {
|
||||
return await fetch(`${baseUrl.MATERIALS_SRC}${routes.homeSmallSlider_2}`, {
|
||||
next: { revalidate: 3600 },
|
||||
}).then((res) => res.json().then((res) => res as HomeModel));
|
||||
}
|
||||
|
||||
public static async getMarquee(): Promise<MarqueeModel> {
|
||||
return await fetch(`${baseUrl.MATERIALS_SRC}${routes.marquee}?on_morquee=1`, {
|
||||
next: { revalidate: 3600 },
|
||||
}).then((res) => res.json().then((res) => res as MarqueeModel));
|
||||
}
|
||||
|
||||
public static async getBanner(): Promise<BannerModel> {
|
||||
return await fetch(`${baseUrl.MATERIALS_SRC}${routes.banner}`, {
|
||||
next: { revalidate: 3600 },
|
||||
}).then((res) => res.json().then((res) => res as BannerModel));
|
||||
}
|
||||
|
||||
public static async getLiveDescription(channel: number): Promise<LiveDescriptionModel> {
|
||||
return await fetch(`${baseUrl.MATERIALS_SRC}${routes.channelItem(channel)}`, {
|
||||
next: { revalidate: 3600 },
|
||||
}).then((res) => res.json().then((res) => res as LiveDescriptionModel));
|
||||
}
|
||||
|
||||
public static async getProperties(): Promise<PropertiesModel> {
|
||||
return await fetch(`${baseUrl.API_SRC}${routes.properties}`, {
|
||||
next: { revalidate: 3600 },
|
||||
}).then((res) => res.json().then((res) => res as PropertiesModel));
|
||||
}
|
||||
|
||||
public static async getPlans(property_id: number): Promise<PlansModel> {
|
||||
return await fetch(`${baseUrl.API_SRC}${routes.plans(String(property_id))}`, {
|
||||
next: { revalidate: 3600 },
|
||||
}).then((res) => res.json().then((res) => res as PlansModel));
|
||||
}
|
||||
|
||||
public static async postAdvert(data: AddPostModel): Promise<Response> {
|
||||
return await fetch(`${baseUrl.API_SRC}${routes.addPost}`, {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
cache: 'no-cache',
|
||||
body: JSON.stringify(data),
|
||||
method: 'POST',
|
||||
});
|
||||
}
|
||||
|
||||
// Quiz fetching ===========================================================================
|
||||
public static async getQuizQuestions(): Promise<IQuizQuestions> {
|
||||
return await fetch(`${baseUrl.QUIZ_SRC}${routes.getQuizQuestions}`, {
|
||||
next: { revalidate: 3600 },
|
||||
}).then((res) => res.json().then((res) => res as IQuizQuestions));
|
||||
}
|
||||
|
||||
public static async getQuiz(quiz_id: string): Promise<IQuizQuestions> {
|
||||
return await fetch(`${baseUrl.QUIZ_SRC}${routes.getQuiz(quiz_id)}`, {
|
||||
next: { revalidate: 3600 },
|
||||
}).then((res) => res.json().then((res) => res as IQuizQuestions));
|
||||
}
|
||||
|
||||
public static async getQuizHistory(id: number): Promise<IQuizQuestionsHistory> {
|
||||
return await fetch(`${baseUrl.QUIZ_SRC}${routes.getQuizQuestionHistory(id)}`, {
|
||||
next: { revalidate: 3600 },
|
||||
}).then((res) => res.json().then((res) => res as any));
|
||||
}
|
||||
|
||||
public static async getQuizWinners(id: number): Promise<IQuizQuestionsWinners> {
|
||||
return await fetch(`${baseUrl.QUIZ_SRC}${routes.getQuizQuestionsWinners(id)}`, {
|
||||
next: { revalidate: 3600 },
|
||||
}).then((res) => res.json().then((res) => res as IQuizQuestionsWinners));
|
||||
}
|
||||
|
||||
// ======================================================================================
|
||||
|
||||
// Votes ================================================================================
|
||||
public static async getAllVotes(): Promise<IAllVotes> {
|
||||
return await fetch(`${baseUrl.QUIZ_SRC}${routes.allVotes}`, {
|
||||
next: { revalidate: 3600 },
|
||||
}).then((res) => res.json().then((res) => res as IAllVotes));
|
||||
}
|
||||
|
||||
public static async getVote(vote_id: string): Promise<IVote> {
|
||||
return await fetch(`${baseUrl.QUIZ_SRC}${routes.vote(vote_id)}`, {
|
||||
next: { revalidate: 3600 },
|
||||
}).then((res) => res.json().then((res) => res as IVote));
|
||||
}
|
||||
|
||||
// ============================================================================================
|
||||
|
||||
// Sms ========================================================================================
|
||||
public static async getAdmins(): Promise<IMyTvAdmins> {
|
||||
const token = localStorage.getItem('access_token');
|
||||
|
||||
return await fetch(`${baseUrl.SMS_SRC}${routes.myTvAdmins}`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
}).then((res) => res.json().then((res) => res as IMyTvAdmins));
|
||||
}
|
||||
|
||||
public static async getMessages(
|
||||
id: number,
|
||||
current_page: number,
|
||||
dateValue: string,
|
||||
activeSort: string,
|
||||
searchFetch: string,
|
||||
): Promise<MessagesByTvAdmin> {
|
||||
const token = localStorage.getItem('access_token');
|
||||
|
||||
return await fetch(
|
||||
`${baseUrl.SMS_SRC}${routes.messagesByTvAdmin(id)}?per_page=60&page=${current_page}${
|
||||
dateValue ? '&filter_by_date=' + dateValue.toString() : ''
|
||||
}${searchFetch ? '&search=' + searchFetch : ''}&order=${activeSort}`,
|
||||
{
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
},
|
||||
).then((res) => res.json().then((res) => res as MessagesByTvAdmin));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
'use client';
|
||||
import { Queries } from '@/api/queries';
|
||||
import Loader from '@/components/Loader';
|
||||
import PageTitle from '@/components/PageTitle';
|
||||
import MainNews from '@/components/news/MainNews';
|
||||
import NewsGrid from '@/components/news/NewsGrid';
|
||||
import Item from '@/components/news/NewsItem';
|
||||
import GlobalContext from '@/context/GlobalContext';
|
||||
import MainProvider from '@/providers/MainProvider';
|
||||
import Hydrate from '@/utils/HydrateClient';
|
||||
import getQueryClient from '@/utils/getQueryClient';
|
||||
import { dehydrate, useQuery } from '@tanstack/react-query';
|
||||
import { data } from 'autoprefixer';
|
||||
import { NextSeo } from 'next-seo';
|
||||
import Image from 'next/image';
|
||||
import { useContext } from 'react';
|
||||
import { useMediaQuery } from 'usehooks-ts';
|
||||
|
||||
interface IParams {
|
||||
params: {
|
||||
page_id: string;
|
||||
};
|
||||
}
|
||||
|
||||
const PageItem = ({ params }: IParams) => {
|
||||
const responsive = useMediaQuery('(max-width: 425px)');
|
||||
|
||||
const { data, isFetching, error } = useQuery({
|
||||
queryKey: ['page_item'],
|
||||
queryFn: () => Queries.getPage(params.page_id),
|
||||
});
|
||||
|
||||
if (isFetching) return <Loader height={'100%'} />;
|
||||
if (error) return <h1>{JSON.stringify(error)}</h1>;
|
||||
|
||||
return (
|
||||
<div className="container">
|
||||
<div className="flex flex-col gap-8 py-6">
|
||||
<NextSeo title={data!.data.title} description={data!.data.content} />
|
||||
<div className="flex flex-col gap-2">
|
||||
{data?.data.title ? <PageTitle title={data?.data.title} /> : <Loader />}
|
||||
</div>
|
||||
|
||||
<div className="main-news flex md:flex-row flex-col gap-6">
|
||||
{data?.data.image && data.data.title ? (
|
||||
responsive && data.data.mobile_image ? (
|
||||
<div className="w-full h-[300px] relative ">
|
||||
<Image
|
||||
src={data?.data.image}
|
||||
alt={data?.data.title}
|
||||
unoptimized
|
||||
unselectable="off"
|
||||
fill
|
||||
priority
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="w-full lg:h-[800px] md:h-[650px] h-[350px] relative lg:max-w-[500px] md:max-w-[300px]">
|
||||
<Image
|
||||
src={data?.data.image}
|
||||
alt={data?.data.title}
|
||||
unoptimized
|
||||
unselectable="off"
|
||||
fill
|
||||
priority
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<Loader />
|
||||
)}
|
||||
<div className="flex flex-col gap-3 text-black text-lg">
|
||||
{data?.data.content ? (
|
||||
<p
|
||||
className="font-roboto font-normal flex flex-col gap-4"
|
||||
dangerouslySetInnerHTML={{ __html: data?.data.content }}></p>
|
||||
) : (
|
||||
<Loader />
|
||||
)}
|
||||
{/* <p
|
||||
className="font-roboto font-normal flex flex-col gap-4"
|
||||
dangerouslySetInnerHTML={{ __html: data!.data.content_html }}></p> */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PageItem;
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
const AboutUs = () => {
|
||||
return (
|
||||
<div className="about-us">
|
||||
<h1 className="hidden">About us</h1>
|
||||
<div className="container">
|
||||
<div className="about-us-inner flex gap-x-8">
|
||||
<div className="py-11 flex flex-col gap-6">
|
||||
<div className="flex flex-col gap-14 pb-5 border-b border-[#BBBBBB] dark:border-b-black border-solid">
|
||||
<h2 className="font-aeroport text-[32px] font-bold text-black transition-all dark:text-white">
|
||||
Biz Barada
|
||||
</h2>
|
||||
<p className="font-mw_sans text-lg font-light text-black transition-all dark:text-white">
|
||||
The State Committee of Turkmenistan for Television, Radio Broadcasting and
|
||||
Cinematography was established on October 17, 2011 by a decree of the President of
|
||||
Turkmenistan. The main task of the Committee is to implement the state policy of
|
||||
Turkmenistan in the field of television, radio and film industry.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4 border-b border-[#BBBBBB] dark:border-b-black border-solid pb-5">
|
||||
<h5 className="font-aeroport font-normal text-[22px] text-black transition-all dark:text-white">
|
||||
There are 8 TV channels within the Committee:
|
||||
</h5>
|
||||
<ul className="flex flex-col gap-2 font-mw_sans font-light text-black text-lg list-disc pl-7 transition-all dark:text-white">
|
||||
<li>Arkadag;</li>
|
||||
<li>Altyn Asyr: Turkmenistan;</li>
|
||||
<li>Yashlyk;</li>
|
||||
<li>Miras;</li>
|
||||
<li>Turkmenistan;</li>
|
||||
<li>Turkmen Owazy;</li>
|
||||
<li>Ashgabat;</li>
|
||||
<li>Sport.</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4 border-b border-[#BBBBBB] dark:border-b-black border-solid pb-5">
|
||||
<h5 className="font-aeroport font-normal text-black transition-all dark:text-white text-[22px]">
|
||||
4 radio channels:
|
||||
</h5>
|
||||
<ul className="flex flex-col gap-2 font-mw_sans font-light text-black transition-all dark:text-white text-lg list-disc pl-7">
|
||||
<li>Watan;</li>
|
||||
<li>Chartarapdan;</li>
|
||||
<li>Miras;</li>
|
||||
<li>Owaz.</li>
|
||||
</ul>
|
||||
</div>
|
||||
{/* <div className="flex flex-col gap-4 border-b border-[#BBBBBB] dark:border-b-black border-solid pb-5"> */}
|
||||
<div className="flex flex-col gap-4">
|
||||
<p className="font-mw_sans text-lg font-light text-black transition-all dark:text-white">
|
||||
In the prosperous period of our sovereign state, it broadcasts widely to promote the
|
||||
national values of the Turkmen people, to further develop our culture and art;
|
||||
</p>
|
||||
<h5 className="font-aeroport font-normal text-black transition-all dark:text-white text-[22px]">
|
||||
There are also 4 economic settlements under the State Committee:
|
||||
</h5>
|
||||
<ul className="flex flex-col gap-2 font-mw_sans text-black transition-all dark:text-white text-lg list-disc pl-7">
|
||||
<li> Oguzhan “Turkmenfilm” Association</li>
|
||||
<li>“Teleradiomerkezi” State Enterprise</li>
|
||||
<li>“Mahabat” advertising company</li>
|
||||
<li>School of production</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AboutUs;
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import AdvertWindow from '@/components/advert/AdvertWindow';
|
||||
import StepsProvider from '@/providers/StepsProvider';
|
||||
|
||||
const Advert = () => {
|
||||
return (
|
||||
<div className="advert-add">
|
||||
<div className="container">
|
||||
<div className="inner flex justify-center w-full">
|
||||
<StepsProvider>
|
||||
<AdvertWindow />
|
||||
</StepsProvider>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Advert;
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
interface IProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
function RootLayout({ children }: IProps) {
|
||||
return (
|
||||
<div className="h-screen">
|
||||
<div className="container h-full">
|
||||
<div className="inner h-full">{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default RootLayout;
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
import LoginForm from "@/components/auth/LoginForm";
|
||||
|
||||
const Login = () => {
|
||||
return (
|
||||
<div className="login h-full flex items-center justify-center">
|
||||
<LoginForm />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Login;
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
import RecoveryForm from "@/components/auth/RecoveryForm";
|
||||
|
||||
const Recovery = () => {
|
||||
return (
|
||||
<div className="login h-full flex items-center justify-center">
|
||||
<RecoveryForm />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Recovery;
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
import SignUpForm from "@/components/auth/SignUpForm";
|
||||
|
||||
const SignUp = () => {
|
||||
return (
|
||||
<div className="login h-full flex items-center justify-center">
|
||||
<SignUpForm />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SignUp;
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import TarifWindow from '@/components/auth/TarifWindow';
|
||||
|
||||
const Login = () => {
|
||||
return (
|
||||
<div className="flex flex-row justify-center items-center w-full h-full gap-[42px]">
|
||||
<TarifWindow folder={10} days={10} price={10} />
|
||||
<TarifWindow folder={1} days={5} price={1} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Login;
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
import ContactForm from '@/components/contact_us/ContactForm';
|
||||
import ContactMap from '@/components/contact_us/ContactMap';
|
||||
|
||||
const Contact = () => {
|
||||
return (
|
||||
<div>
|
||||
<div className="container">
|
||||
<div className="inner py-8 max-w-[1000px] w-full m-auto">
|
||||
<ContactForm />
|
||||
<ContactMap />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Contact;
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
import Buble from '@/components/Buble';
|
||||
import Footer from '@/components/Footer';
|
||||
import MobileMenu from '@/components/MobileMenu';
|
||||
import Nav from '@/components/Nav';
|
||||
import GlobalContext from '@/context/GlobalContext';
|
||||
import MainProvider from '@/providers/MainProvider';
|
||||
import { useContext } from 'react';
|
||||
|
||||
interface IProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const RootLayout = ({ children }: IProps) => {
|
||||
return (
|
||||
<div className="z-20 relative">
|
||||
<MainProvider>
|
||||
<Buble />
|
||||
<div className="bg-white dark:bg-black transition-all h-full">
|
||||
<h1 className="hidden">Turkmen TV</h1>
|
||||
<Nav />
|
||||
<main>{children}</main>
|
||||
<Footer />
|
||||
<MobileMenu />
|
||||
</div>
|
||||
</MainProvider>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RootLayout;
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
'use client';
|
||||
import Image from 'next/image';
|
||||
|
||||
import Link from 'next/link';
|
||||
import ReactPlayer from 'react-player';
|
||||
import { v4 } from 'uuid';
|
||||
import { Queries } from '@/api/queries';
|
||||
import Loader from '@/components/Loader';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import channels from '@/channels';
|
||||
import { useLocalStorage } from 'usehooks-ts';
|
||||
import { ParseString } from '@/utils/parseString';
|
||||
import Banner from '@/components/live/Banner';
|
||||
|
||||
const page = () => {
|
||||
const [activeChannel, setActiveChanell] = useLocalStorage('activeChannel', {
|
||||
channelName: 'Arkadag',
|
||||
channelNumber: 1,
|
||||
});
|
||||
|
||||
const { data, isFetching, error } = useQuery({
|
||||
queryKey: ['channel_description', activeChannel],
|
||||
queryFn: () => Queries.getLiveDescription(activeChannel.channelNumber),
|
||||
});
|
||||
|
||||
// if (isFetching) return <Loader height={'100%'} />;
|
||||
// if (error) return <h1>{JSON.stringify(error)}</h1>;
|
||||
|
||||
return (
|
||||
<div className="live">
|
||||
<div className="container w-full">
|
||||
<div className="live-wrapper wrapper flex gap-8 pt-8 pb-14">
|
||||
<div className="live-main flex flex-col mb-14 w-full h-fit ">
|
||||
<div className="live-player relative md:h-[550px] sm:h-full h-[250px] w-full border-black border-2">
|
||||
{channels.map((channel) =>
|
||||
channel.name === activeChannel.channelName ? (
|
||||
<ReactPlayer
|
||||
height={'100%'}
|
||||
width={'100%'}
|
||||
controls
|
||||
url={channel.source}
|
||||
playing={true}
|
||||
key={v4()}
|
||||
/>
|
||||
) : null,
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="bg-black flex flex-col">
|
||||
<div className="live-cahnnels flex flex-wrap md:justify-between gap-6 p-5">
|
||||
{channels
|
||||
? channels.map((channel) => {
|
||||
return (
|
||||
<Link
|
||||
key={channel.id}
|
||||
onClick={() =>
|
||||
setActiveChanell({
|
||||
channelName: channel.name,
|
||||
channelNumber: channel.channel,
|
||||
})
|
||||
}
|
||||
className={`channels-img flex flex-col justify-center items-center gap-2 hover:brightness-100 transition-all ${
|
||||
channel.name === activeChannel.channelName
|
||||
? 'brightness-100'
|
||||
: 'brightness-50'
|
||||
}`}
|
||||
href={''}>
|
||||
<div className="relative py-3 px-3 md:w-20 md:h-20 w-16 h-16 rounded-full bg-white overflow-hidden flex flex-col items-center justify-center">
|
||||
<Image
|
||||
src={channel.img}
|
||||
unoptimized
|
||||
alt={channel.name}
|
||||
fill
|
||||
priority
|
||||
className="w-full h-full object-contain"
|
||||
/>
|
||||
</div>
|
||||
<h3 className="text-white md:text-base text-xs">{channel.name}</h3>
|
||||
</Link>
|
||||
);
|
||||
})
|
||||
: null}
|
||||
</div>
|
||||
|
||||
{data?.data.length ? (
|
||||
<div className="flex items-center gap-10 pt-5 w-full text-white border-t-[1px] border-[#7f7f7f] p-5">
|
||||
{/* <h3 className="font-bold text-base">
|
||||
{`${ParseString.parseTime(data.data[0].start)} - ${ParseString.parseTime(
|
||||
data.data[0].end,
|
||||
)}`}
|
||||
</h3> */}
|
||||
<h4 className="text-base">{data.data[0].content}</h4>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Banner />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default page;
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
import { Queries } from '@/api/queries';
|
||||
import PageTitle from '@/components/PageTitle';
|
||||
import MainNews from '@/components/news/MainNews';
|
||||
import NewsGrid from '@/components/news/NewsGrid';
|
||||
import Item from '@/components/news/NewsItem';
|
||||
import Hydrate from '@/utils/HydrateClient';
|
||||
import getQueryClient from '@/utils/getQueryClient';
|
||||
import { dehydrate } from '@tanstack/react-query';
|
||||
import Image from 'next/image';
|
||||
import StaticImage from '@/public/staticPageImage.jpg';
|
||||
|
||||
export async function generateStaticParams() {
|
||||
const news = await Queries.getNews(1, { perPage: 20 });
|
||||
|
||||
return news.data.map((item) => ({
|
||||
slug: item.id.toString(),
|
||||
}));
|
||||
}
|
||||
|
||||
interface IParams {
|
||||
params: {
|
||||
slug: string;
|
||||
};
|
||||
}
|
||||
|
||||
const NewsItemStatic = async ({ params }: IParams) => {
|
||||
const queryClient = getQueryClient();
|
||||
|
||||
const dehydratedState = dehydrate(queryClient);
|
||||
|
||||
return (
|
||||
<div className="news-item">
|
||||
<div className="container">
|
||||
<Hydrate state={{ dehydratedState }}>
|
||||
<div className="news-body py-11">
|
||||
<div className="flex flex-col gap-8">
|
||||
{/* <NextSeo title={data!.data.title} description={data!.data.excerpt} /> */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<PageTitle title={'MHB books'} />
|
||||
{/* <p className="text-lg">{data?.data.published_at}</p> */}
|
||||
</div>
|
||||
|
||||
<div className="main-news flex flex-col gap-6">
|
||||
<div className="w-full lg:h-[600px] md:h-[400px] h-[250px] relative">
|
||||
<Image
|
||||
src={StaticImage}
|
||||
alt={'kitaplar'}
|
||||
unoptimized
|
||||
unselectable="off"
|
||||
fill
|
||||
priority
|
||||
className="w-full object-cover h-[600px]"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-3 text-black text-lg">
|
||||
{/* <h2 className="font-mw_sans font-bold">{data!.data.title}</h2> */}
|
||||
<p className="font-roboto font-normal flex flex-col gap-4 md:text-xl text-lg">
|
||||
Mahabat müdirliginiň neşir önümleri:
|
||||
<br /> 1. 3+ we 5+ ýaşly çagalar üçin Zehin soraglary. Bahasy 23 manat;
|
||||
<br /> 2. Çagalar üçin reňkleme kitaplary. Bahasy: 13 manat;
|
||||
<br /> 3. Ulylar üçin "Sözýetim" güýmenjesi. Bahasy: 10 manat;
|
||||
<br /> 4. Çagalara kompýuter programirleme diline giriş "Başarjaň". Bahasy: 38
|
||||
manat;
|
||||
<br /> 5. Elwan depderim reňkleme kitaby. Bahasy: 28 manat;
|
||||
<br /> 6. 7 ýaşdan ýokary çagalar üçin niýetlenen erteki kitaplary. Bahasy: 8
|
||||
manat;
|
||||
<br />
|
||||
<br />{' '}
|
||||
<a
|
||||
href="https://forms.gle/g6qm76tZihd4conb8"
|
||||
target="_blank"
|
||||
className="text-[#337AB7] font-bold md:text-xl text-lg">
|
||||
Satyn almak üçin şu düwmä basyň!
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Hydrate>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default NewsItemStatic;
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
import { Queries } from "@/api/queries";
|
||||
import PageTitle from "@/components/PageTitle";
|
||||
import MainNews from "@/components/news/MainNews";
|
||||
import NewsGrid from "@/components/news/NewsGrid";
|
||||
import Item from "@/components/news/NewsItem";
|
||||
import Hydrate from "@/utils/HydrateClient";
|
||||
import getQueryClient from "@/utils/getQueryClient";
|
||||
import { dehydrate } from "@tanstack/react-query";
|
||||
|
||||
export async function generateStaticParams() {
|
||||
const news = await Queries.getNews(1, { perPage: 20 });
|
||||
|
||||
return news.data.map((item) => ({
|
||||
slug: item.id.toString(),
|
||||
}));
|
||||
}
|
||||
|
||||
interface IParams {
|
||||
params: {
|
||||
slug: string;
|
||||
};
|
||||
}
|
||||
|
||||
const NewsItem = async ({ params }: IParams) => {
|
||||
const queryClient = getQueryClient();
|
||||
await queryClient.prefetchQuery({
|
||||
queryKey: ["news_item", params.slug],
|
||||
queryFn: () => Queries.getNewsItem(params.slug),
|
||||
});
|
||||
await queryClient.prefetchInfiniteQuery({
|
||||
queryKey: ["news", "infinite"],
|
||||
queryFn: ({ pageParam = 1 }) => Queries.getNews(pageParam, {}),
|
||||
});
|
||||
|
||||
const dehydratedState = dehydrate(queryClient);
|
||||
|
||||
return (
|
||||
<div className="news-item">
|
||||
<div className="container">
|
||||
<Hydrate state={{ dehydratedState }}>
|
||||
<div className="news-body py-11">
|
||||
<Item id={params.slug} />
|
||||
<div className="pt-8">
|
||||
<NewsGrid isSlides perPage={20} title="Habarlar" />
|
||||
</div>
|
||||
</div>
|
||||
</Hydrate>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default NewsItem;
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
import { Queries } from '@/api/queries';
|
||||
import NewsGrid from '@/components/news/NewsGrid';
|
||||
import Hydrate from '@/utils/HydrateClient';
|
||||
import getQueryClient from '@/utils/getQueryClient';
|
||||
import { dehydrate } from '@tanstack/react-query';
|
||||
|
||||
const News = async () => {
|
||||
const queryClient = getQueryClient();
|
||||
await queryClient.prefetchInfiniteQuery({
|
||||
queryKey: ['news', 'infinite'],
|
||||
queryFn: ({ pageParam = 1 }) => Queries.getNews(pageParam, {}),
|
||||
});
|
||||
|
||||
const dehydratedState = dehydrate(queryClient);
|
||||
|
||||
return (
|
||||
<div className="">
|
||||
<div className="container">
|
||||
<div className="inner flex flex-col gap-14 py-11">
|
||||
<Hydrate state={dehydratedState}>
|
||||
<NewsGrid isExtendable />
|
||||
</Hydrate>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default News;
|
||||
|
|
@ -0,0 +1,160 @@
|
|||
'use client';
|
||||
|
||||
import { Queries } from '@/api/queries';
|
||||
import Loader from '@/components/Loader';
|
||||
import QuizQuestion from '@/components/quiz/QuizQuestion';
|
||||
import QuizQuestionList from '@/components/quiz/QuizQuestionList';
|
||||
import QuizSearch from '@/components/quiz/QuizSearch';
|
||||
import QuizTable from '@/components/quiz/QuizTable';
|
||||
import QuizWinnerTable from '@/components/quiz/QuizWinnerTable';
|
||||
import GradientTitle from '@/components/vote/GradientTitle';
|
||||
import { IQuizQuestions, Question } from '@/models/quizQuestions.model';
|
||||
import QuizProvider from '@/providers/QuizProvider';
|
||||
import { Validator } from '@/utils/validator';
|
||||
import Image from 'next/image';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useMediaQuery } from 'usehooks-ts';
|
||||
|
||||
interface IParams {
|
||||
params: {
|
||||
quiz_id: string;
|
||||
};
|
||||
}
|
||||
|
||||
const page = ({ params }: IParams) => {
|
||||
const [quizFinished, setQuizFinished] = useState<boolean>(false);
|
||||
const [data, setData] = useState<IQuizQuestions>();
|
||||
|
||||
// const { data, error, isFetching } = useQuery(
|
||||
// ['quiz_questions'],
|
||||
// () => Queries.getQuizQuestions(),
|
||||
// {
|
||||
// keepPreviousData: true,
|
||||
// },
|
||||
// );
|
||||
|
||||
useEffect(() => {
|
||||
if (!params.quiz_id) {
|
||||
Queries.getQuizQuestions().then((res) => {
|
||||
setData(res);
|
||||
res
|
||||
? res.data.questions[res.data.questions.length - 1].status === 'closed'
|
||||
? setQuizFinished(true)
|
||||
: setQuizFinished(false)
|
||||
: null;
|
||||
});
|
||||
} else {
|
||||
Queries.getQuiz(params.quiz_id).then((res) => {
|
||||
setData(res);
|
||||
res
|
||||
? res.data.questions[res.data.questions.length - 1]?.status === 'closed'
|
||||
? setQuizFinished(true)
|
||||
: setQuizFinished(false)
|
||||
: null;
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
const mobile = useMediaQuery('(max-width: 768px)');
|
||||
|
||||
if (data) {
|
||||
if (!data.data) {
|
||||
return (
|
||||
<main className="h-full py-[200px]">
|
||||
<div className="container">
|
||||
<GradientTitle title={data?.message} size="big" />
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="pt-[60px] pb-[200px]">
|
||||
{typeof data !== 'string' ? (
|
||||
<div className="container flex flex-col md:gap-[200px] gap-[80px]">
|
||||
<QuizProvider>
|
||||
<div className="flex flex-col gap-[100px]">
|
||||
<div className="flex flex-col gap-[45px]">
|
||||
<div className="flex flex-col gap-[10px] md:gap-[5px]">
|
||||
<h3 className="text-base md:text-[14px] text-textLight font-semibold md:font-normal">
|
||||
{data ? Validator.reveseDate(data?.data.date) : null}
|
||||
</h3>
|
||||
<h1 className="text-textBlack text-[32px] md:text-[60px] leading-[100%] font-semibold">
|
||||
{data?.data.title}
|
||||
</h1>
|
||||
<h3 className="text-base font-medium leading-[125%] md:text-[14px] text-textDarkt mt-[5px] max-w-[600px]">
|
||||
{data?.data.description}
|
||||
</h3>
|
||||
</div>
|
||||
{data?.data.banner ? (
|
||||
<div className="relative w-full md:min-h-[150px] md:h-auto h-[100px]">
|
||||
{mobile ? (
|
||||
<Image
|
||||
src={
|
||||
data.data.banner_mobile !== null
|
||||
? data.data.banner_mobile
|
||||
: data.data.banner
|
||||
}
|
||||
alt={'banner'}
|
||||
unoptimized
|
||||
unselectable="off"
|
||||
fill
|
||||
className="rounded-[8px]"
|
||||
/>
|
||||
) : (
|
||||
<Image
|
||||
src={data?.data.banner}
|
||||
alt={'banner'}
|
||||
unoptimized
|
||||
unselectable="off"
|
||||
fill
|
||||
className="rounded-[8px]"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{data?.data.rules && data.data.notes ? (
|
||||
<QuizTable rules={data?.data.rules} notes={data?.data.notes} />
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{data?.data.id && quizFinished ? <QuizSearch quizId={data?.data.id} /> : null}
|
||||
|
||||
<div className="flex flex-col md:gap-[160px] gap-[80px]">
|
||||
{data?.data ? (
|
||||
<QuizQuestionList
|
||||
dynamic
|
||||
id={params.quiz_id}
|
||||
initialQuestionsData={data}
|
||||
setQuizFinished={setQuizFinished}
|
||||
quizFinished={quizFinished}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{data?.data.id && quizFinished ? (
|
||||
<QuizWinnerTable quizId={data?.data.id} quizFinished={quizFinished} />
|
||||
) : null}
|
||||
</div>
|
||||
</QuizProvider>
|
||||
</div>
|
||||
) : (
|
||||
<div className="container text-[40px] flex items-center justify-center font-bold text-textLight min-h-[30vh]">
|
||||
Непредвиденная ошибка. Нет активной викторины.
|
||||
</div>
|
||||
)}
|
||||
</main>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<main className="h-full py-[200px]">
|
||||
<div className="container">
|
||||
<Loader />
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default page;
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
'use client';
|
||||
import { Queries } from '@/api/queries';
|
||||
import Loader from '@/components/Loader';
|
||||
import QuizQuestion from '@/components/quiz/QuizQuestion';
|
||||
import QuizQuestionList from '@/components/quiz/QuizQuestionList';
|
||||
import QuizSearch from '@/components/quiz/QuizSearch';
|
||||
import QuizTable from '@/components/quiz/QuizTable';
|
||||
import QuizWinnerTable from '@/components/quiz/QuizWinnerTable';
|
||||
import { IQuizQuestions, Question } from '@/models/quizQuestions.model';
|
||||
import QuizProvider from '@/providers/QuizProvider';
|
||||
import { Validator } from '@/utils/validator';
|
||||
import Image from 'next/image';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useMediaQuery } from 'usehooks-ts';
|
||||
|
||||
const page = () => {
|
||||
const [quizFinished, setQuizFinished] = useState<boolean>(false);
|
||||
const [data, setData] = useState<IQuizQuestions>();
|
||||
|
||||
// const { data, error, isFetching } = useQuery(
|
||||
// ['quiz_questions'],
|
||||
// () => Queries.getQuizQuestions(),
|
||||
// {
|
||||
// keepPreviousData: true,
|
||||
// },
|
||||
// );
|
||||
|
||||
useEffect(() => {
|
||||
Queries.getQuizQuestions().then((res) => {
|
||||
setData(res);
|
||||
res
|
||||
? res.data.questions[res.data.questions.length - 1]?.status === 'closed'
|
||||
? setQuizFinished(true)
|
||||
: setQuizFinished(false)
|
||||
: null;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const mobile = useMediaQuery('(max-width: 768px)');
|
||||
|
||||
return (
|
||||
<main className="pt-[60px] pb-[200px]">
|
||||
{typeof data !== 'string' ? (
|
||||
<div className="container flex flex-col md:gap-[200px] gap-[80px]">
|
||||
<QuizProvider>
|
||||
<div className="flex flex-col gap-[100px]">
|
||||
<div className="flex flex-col gap-[45px]">
|
||||
<div className="flex flex-col gap-[10px] md:gap-[5px]">
|
||||
<h3 className="text-base md:text-[14px] text-textLight font-semibold md:font-normal">
|
||||
{data ? Validator.reveseDate(data?.data.date) : null}
|
||||
</h3>
|
||||
<h1 className="text-textBlack text-[32px] md:text-[60px] leading-[100%] font-semibold">
|
||||
{data?.data.title}
|
||||
</h1>
|
||||
<h3 className="text-base font-medium leading-[125%] md:text-[14px] text-textDarkt mt-[5px] max-w-[600px]">
|
||||
{data?.data.description}
|
||||
</h3>
|
||||
</div>
|
||||
{data?.data.banner ? (
|
||||
<div className="relative w-full md:min-h-[150px] md:h-auto h-[100px]">
|
||||
{mobile ? (
|
||||
<Image
|
||||
src={
|
||||
data.data.banner_mobile !== null
|
||||
? data.data.banner_mobile
|
||||
: data.data.banner
|
||||
}
|
||||
alt={'banner'}
|
||||
unoptimized
|
||||
unselectable="off"
|
||||
fill
|
||||
className="rounded-[8px]"
|
||||
/>
|
||||
) : (
|
||||
<Image
|
||||
src={data?.data.banner}
|
||||
alt={'banner'}
|
||||
unoptimized
|
||||
unselectable="off"
|
||||
fill
|
||||
className="rounded-[8px]"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{data?.data.rules && data.data.notes ? (
|
||||
<QuizTable rules={data?.data.rules} notes={data?.data.notes} />
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{data?.data.id && quizFinished ? <QuizSearch quizId={data?.data.id} /> : null}
|
||||
|
||||
<div className="flex flex-col md:gap-[160px] gap-[80px]">
|
||||
{data?.data ? (
|
||||
<QuizQuestionList
|
||||
id="active"
|
||||
initialQuestionsData={data}
|
||||
setQuizFinished={setQuizFinished}
|
||||
quizFinished={quizFinished}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{data?.data.id && quizFinished ? (
|
||||
<QuizWinnerTable quizId={data?.data.id} quizFinished={quizFinished} />
|
||||
) : null}
|
||||
</div>
|
||||
</QuizProvider>
|
||||
</div>
|
||||
) : (
|
||||
<div className="container text-[40px] flex items-center justify-center font-bold text-textLight min-h-[30vh]">
|
||||
Непредвиденная ошибка. Нет активной викторины.
|
||||
</div>
|
||||
)}
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
||||
export default page;
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import Dashboard from '@/components/dashboard/Dashboard';
|
||||
import { SmsProvider } from '@/context/SmsContext';
|
||||
|
||||
const page = () => {
|
||||
return (
|
||||
<SmsProvider>
|
||||
<Dashboard />
|
||||
</SmsProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default page;
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
'use client';
|
||||
// src/app/layout.tsx
|
||||
import { AuthProvider } from '@/context/AuthContext';
|
||||
import { SmsContext, SmsProvider } from '@/context/SmsContext';
|
||||
import { PropsWithChildren, useState } from 'react';
|
||||
|
||||
export default function SmsLayout({ children }: PropsWithChildren) {
|
||||
return (
|
||||
<main className="min-h-screen">
|
||||
<div className="container">
|
||||
<AuthProvider>{children}</AuthProvider>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
'use client';
|
||||
// src/app/sms/sign_up/page.tsx
|
||||
import { useState, FormEvent, useContext, useEffect } from 'react';
|
||||
import { AuthContext } from '@/context/AuthContext';
|
||||
import Loader from '@/components/Loader';
|
||||
|
||||
const Page = () => {
|
||||
const [login, setLogin] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const authContext = useContext(AuthContext);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
if (!authContext) {
|
||||
throw new Error('AuthContext must be used within an AuthProvider');
|
||||
}
|
||||
|
||||
const { login: loginUser, userLogedIn } = authContext;
|
||||
const { checkUserLoggedIn } = authContext;
|
||||
|
||||
const handleSubmit = (e: FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
loginUser(login, password);
|
||||
setLogin('');
|
||||
setPassword('');
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
checkUserLoggedIn();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<main className="container flex justify-center h-screen items-center ">
|
||||
<form onSubmit={handleSubmit} id="create-course-form">
|
||||
<div className="bg-[#F5F5FA] p-10 rounded-[25px] w-[522px] -mt-28 shadow-tableShadow">
|
||||
<h2 className="mb-10 text-[40px] leading-none text-#242429 font-semibold">SMS ulgamy</h2>
|
||||
<div className="flex flex-col gap-2 mb-5 leading-[150%]">
|
||||
<label htmlFor="login" className="text-[16px] font-semibold text-[#242429]">
|
||||
Login
|
||||
</label>
|
||||
<input
|
||||
id="login"
|
||||
type="text"
|
||||
className="input-style"
|
||||
value={login}
|
||||
onChange={(e) => setLogin(e.target.value)}
|
||||
placeholder="Login giriziň"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<label htmlFor="password" className="text-[16px] font-semibold text-[#242429]">
|
||||
Açar sözi
|
||||
</label>
|
||||
<input
|
||||
id="password"
|
||||
type="password"
|
||||
className="input-style"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
placeholder="Açar sözi giriziň"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className="p-3 bg-[#7A7ACC] text-[18px] leading-[150%] font-medium text-white w-full rounded-xl mt-[30px]">
|
||||
Ulgama gir
|
||||
</button>
|
||||
|
||||
{userLogedIn === false ? (
|
||||
<p className="text-center pt-[16px] text-[16px] font-[600] text-red-400">
|
||||
Login ýa-da açar sözi ýalňyş
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
</form>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
||||
export default Page;
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
'use client';
|
||||
|
||||
import { Calendar } from '@/components/ui/calendar';
|
||||
import { clsx } from 'clsx';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
const page = () => {
|
||||
const [date, setDate] = useState<Date>();
|
||||
|
||||
const calendarRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const mount = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (mount.current) {
|
||||
if (calendarRef.current) {
|
||||
const calendarFooter = document.createElement('div');
|
||||
calendarFooter.classList.add(
|
||||
'calendar-footer',
|
||||
'flex',
|
||||
'justify-between',
|
||||
'items-center',
|
||||
'mt-6',
|
||||
'px-3',
|
||||
);
|
||||
|
||||
const calendar = document.querySelector('.calendar');
|
||||
|
||||
const btn1 = document.createElement('div');
|
||||
btn1.classList.add('btn1', 'text-textLight', 'cursor-pointer', 'text-[14px]');
|
||||
btn1.textContent = 'Отменить';
|
||||
|
||||
const btn2 = document.createElement('button');
|
||||
btn2.classList.add('btn2');
|
||||
btn2.textContent = 'Выбрать';
|
||||
|
||||
calendarFooter.appendChild(btn1);
|
||||
calendarFooter.appendChild(btn2);
|
||||
calendar?.appendChild(calendarFooter);
|
||||
}
|
||||
}
|
||||
mount.current = true;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<section className="container">
|
||||
<div className="" ref={calendarRef}>
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={date}
|
||||
onSelect={setDate}
|
||||
initialFocus
|
||||
// onDayClick={() => setActiveDay(true)}
|
||||
className={clsx(
|
||||
'my-20 calendar bg-white w-fit rounded-[8px] shadow-[0_2px_32px_rgba(0,0,0,0.3)] transition-all',
|
||||
// { 'day-styles': activeDay },
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default page;
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
import VideoList from '@/components/VideoList';
|
||||
import VideoPlayer from '@/components/VideoPlayer';
|
||||
import InfoBlock from '@/components/InfoBlock';
|
||||
import getQueryClient from '@/utils/getQueryClient';
|
||||
import { Queries } from '@/api/queries';
|
||||
import Hydrate from '@/utils/HydrateClient';
|
||||
import { dehydrate, useMutation } from '@tanstack/react-query';
|
||||
import SectionTitle from '@/components/SectionTitle';
|
||||
|
||||
interface IParams {
|
||||
params: {
|
||||
video_id: number;
|
||||
category_id: string;
|
||||
content_url: string;
|
||||
banner_url: string;
|
||||
};
|
||||
}
|
||||
|
||||
const VideoItem = async ({ params }: IParams) => {
|
||||
const queryClient = getQueryClient();
|
||||
// queryClient.prefetchQuery({
|
||||
// queryKey: ['video', `video:${params.video_id}`],
|
||||
// queryFn: () => Queries.getVideo(params.video_id),
|
||||
// });
|
||||
|
||||
// queryClient.prefetchQuery({
|
||||
// queryKey: ['video', 'all'],
|
||||
// queryFn: () => Queries.getVideos(''),
|
||||
// });
|
||||
await queryClient.prefetchQuery({
|
||||
queryKey: ['video', `video:${params.video_id}`],
|
||||
queryFn: () => Queries.getVideo(params.video_id),
|
||||
});
|
||||
await queryClient.prefetchInfiniteQuery({
|
||||
queryKey: ['video', 'all'],
|
||||
queryFn: () => Queries.getVideos(''),
|
||||
});
|
||||
await queryClient.prefetchInfiniteQuery({
|
||||
queryKey: ['videos', 'infinite', ''],
|
||||
queryFn: ({ pageParam = 1 }) =>
|
||||
Queries.getVideos(
|
||||
'?' +
|
||||
String(
|
||||
new URLSearchParams({
|
||||
page: pageParam,
|
||||
per_page: '8',
|
||||
}),
|
||||
),
|
||||
),
|
||||
});
|
||||
const dehydratedState = dehydrate(queryClient);
|
||||
|
||||
return (
|
||||
<div className="video-item mt-6">
|
||||
<div className="container">
|
||||
<Hydrate state={dehydratedState}>
|
||||
<div className="video-item-inner">
|
||||
<div className="video-item-wrapper flex flex-col gap-10 relative pb-14">
|
||||
<InfoBlock video_id={params.video_id} />
|
||||
|
||||
<div className="video-item-inner w-full flex flex-col gap-4">
|
||||
<SectionTitle title={'Beylekiler'} />
|
||||
<VideoList isSlides />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Hydrate>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default VideoItem;
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
import MaterialsProvider from "@/providers/MaterialsProvider";
|
||||
|
||||
interface IProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
function RootLayout({ children }: IProps) {
|
||||
return (
|
||||
<div>
|
||||
<MaterialsProvider>{children}</MaterialsProvider>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default RootLayout;
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
import { Queries } from '@/api/queries';
|
||||
import Aside from '@/components/Aside';
|
||||
import Categories from '@/components/Categories';
|
||||
import SearchBar from '@/components/SearchBar';
|
||||
import VideoList from '@/components/VideoList';
|
||||
import Hydrate from '@/utils/HydrateClient';
|
||||
import getQueryClient from '@/utils/getQueryClient';
|
||||
import { dehydrate } from '@tanstack/react-query';
|
||||
|
||||
export async function generateStaticParams() {
|
||||
const categories = await Queries.getCategories();
|
||||
|
||||
return categories.data.map((category) => ({
|
||||
category_id: String(category.id),
|
||||
}));
|
||||
}
|
||||
|
||||
const Treasury = async () => {
|
||||
const queryClient = getQueryClient();
|
||||
await queryClient.prefetchQuery({
|
||||
queryKey: ['categories'],
|
||||
queryFn: () => Queries.getCategories(),
|
||||
});
|
||||
await queryClient.prefetchQuery({
|
||||
queryKey: ['channels'],
|
||||
queryFn: () => Queries.getChannels(),
|
||||
});
|
||||
|
||||
await queryClient.prefetchInfiniteQuery({
|
||||
queryKey: ['videos', 'infinite', ''],
|
||||
queryFn: ({ pageParam = 1 }) =>
|
||||
Queries.getVideos(
|
||||
'?' +
|
||||
String(
|
||||
new URLSearchParams({
|
||||
page: pageParam,
|
||||
per_page: '8',
|
||||
}),
|
||||
),
|
||||
),
|
||||
});
|
||||
|
||||
const dehydratedState = dehydrate(queryClient);
|
||||
|
||||
return (
|
||||
<div className="treasury relative">
|
||||
<div className="container">
|
||||
<div className="treasury-inner pt-8 pb-14">
|
||||
<h1 className="hidden">Treasury</h1>
|
||||
<Hydrate state={dehydratedState}>
|
||||
<div className="treasury-wrapper flex md:flex-row gap-x-8 flex-col ">
|
||||
<Aside line />
|
||||
<div className="treasury-top flex flex-col gap-6 w-full">
|
||||
<SearchBar />
|
||||
<Categories />
|
||||
<VideoList isExtendable />
|
||||
</div>
|
||||
</div>
|
||||
</Hydrate>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Treasury;
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
'use client';
|
||||
import ParticipantsList from '@/components/vote/ParticipantsList';
|
||||
import VoteProvider from '@/providers/VoteProvider';
|
||||
|
||||
interface IParams {
|
||||
params: {
|
||||
vote_id: string;
|
||||
};
|
||||
}
|
||||
|
||||
const page = ({ params }: IParams) => {
|
||||
return (
|
||||
<main className="pt-[60px] pb-[120px]">
|
||||
<div className="container">
|
||||
<VoteProvider>
|
||||
<div className="flex flex-col items-center w-full">
|
||||
<ParticipantsList vote_id={params.vote_id} />
|
||||
</div>
|
||||
</VoteProvider>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
||||
export default page;
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
import PageBage from '@/components/vote/PageBage';
|
||||
import ParticipantsList from '@/components/vote/ParticipantsList';
|
||||
import VoteProvider from '@/providers/VoteProvider';
|
||||
|
||||
const page = () => {
|
||||
return (
|
||||
<main className="pt-[60px] pb-[120px]">
|
||||
<div className="container">
|
||||
<VoteProvider>
|
||||
<div className="flex flex-col items-center w-full">
|
||||
<ParticipantsList />
|
||||
</div>
|
||||
</VoteProvider>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
||||
export default page;
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
import errorIcon from '@/public/error-icon.svg';
|
||||
|
||||
const Failed = () => {
|
||||
return (
|
||||
<main className="success flex items-center justify-center h-screen">
|
||||
<div className="flex flex-col gap-4 items-center justify-center h-screen">
|
||||
<Image
|
||||
src={errorIcon}
|
||||
alt="close icon"
|
||||
width={100}
|
||||
height={100}
|
||||
className="w-[100px] h-[100px] object-contain"
|
||||
/>
|
||||
<h1 className="text-black font-mw_sans font-bold text-[32px]"> Näsazlyk ýüze çykdy</h1>
|
||||
{/* <p className="font-roboto text-black text-lg text-center">Something went wrong.</p> */}
|
||||
<Link
|
||||
href={'/advert'}
|
||||
className="flex items-center justify-center gap-2 text-white text-lg font-roboto text-center rounded-[30px] bg-mlightblue py-4 min-w-[480px]">
|
||||
<Image
|
||||
src={'/arrow-left.svg'}
|
||||
alt="arrow"
|
||||
width={32}
|
||||
height={14}
|
||||
className="w-8 h-[14px] object-contain"
|
||||
/>
|
||||
<p>Yza geç</p>
|
||||
</Link>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
export default Failed;
|
||||
|
|
@ -0,0 +1,209 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 222.2 84% 4.9%;
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 222.2 84% 4.9%;
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 222.2 84% 4.9%;
|
||||
--primary: 222.2 47.4% 11.2%;
|
||||
--primary-foreground: 210 40% 98%;
|
||||
--secondary: 210 40% 96.1%;
|
||||
--secondary-foreground: 222.2 47.4% 11.2%;
|
||||
--muted: 210 40% 96.1%;
|
||||
--muted-foreground: 215.4 16.3% 46.9%;
|
||||
--accent: 210 40% 96.1%;
|
||||
--accent-foreground: 222.2 47.4% 11.2%;
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
--border: 214.3 31.8% 91.4%;
|
||||
--input: 214.3 31.8% 91.4%;
|
||||
--ring: 222.2 84% 4.9%;
|
||||
--radius: 0.5rem;
|
||||
--chart-1: 12 76% 61%;
|
||||
--chart-2: 173 58% 39%;
|
||||
--chart-3: 197 37% 24%;
|
||||
--chart-4: 43 74% 66%;
|
||||
--chart-5: 27 87% 67%;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 222.2 84% 4.9%;
|
||||
--foreground: 210 40% 98%;
|
||||
--card: 222.2 84% 4.9%;
|
||||
--card-foreground: 210 40% 98%;
|
||||
--popover: 222.2 84% 4.9%;
|
||||
--popover-foreground: 210 40% 98%;
|
||||
--primary: 210 40% 98%;
|
||||
--primary-foreground: 222.2 47.4% 11.2%;
|
||||
--secondary: 217.2 32.6% 17.5%;
|
||||
--secondary-foreground: 210 40% 98%;
|
||||
--muted: 217.2 32.6% 17.5%;
|
||||
--muted-foreground: 215 20.2% 65.1%;
|
||||
--accent: 217.2 32.6% 17.5%;
|
||||
--accent-foreground: 210 40% 98%;
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
--border: 217.2 32.6% 17.5%;
|
||||
--input: 217.2 32.6% 17.5%;
|
||||
--ring: 212.7 26.8% 83.9%;
|
||||
--chart-1: 220 70% 50%;
|
||||
--chart-2: 160 60% 45%;
|
||||
--chart-3: 30 80% 55%;
|
||||
--chart-4: 280 65% 60%;
|
||||
--chart-5: 340 75% 55%;
|
||||
}
|
||||
}
|
||||
|
||||
/* @layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
} */
|
||||
|
||||
/* <<<<<<< HEAD * {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
=======>>>>>>>2c1eddb85d62e4709470605eb8df7458c2d6c37e body, */
|
||||
|
||||
* {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
html {
|
||||
overflow-x: hidden;
|
||||
/* min-height: 50vh; */
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1316px;
|
||||
padding: 0 30px;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
/* overflow-x: hidden; */
|
||||
}
|
||||
|
||||
.text-stroke {
|
||||
text-shadow: -1px -1px 0 white, 1px -1px 0 white, -1px 1px 0 white, 1px 1px 0 white;
|
||||
}
|
||||
|
||||
.big-swiper .swiper-pagination-bullet {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
background: white;
|
||||
border: none;
|
||||
transition: 0.3 all ease;
|
||||
}
|
||||
|
||||
.small-swiper .swiper-pagination-bullet {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background: white;
|
||||
opacity: 0.7;
|
||||
transition: 0.3 all ease;
|
||||
}
|
||||
|
||||
.big-swiper .swiper-pagination-bullet-active,
|
||||
.small-swiper .swiper-pagination-bullet-active {
|
||||
opacity: 1;
|
||||
background: #010431;
|
||||
transition: 0.3 all ease;
|
||||
}
|
||||
|
||||
.small-swiper .swiper-button-next:after {
|
||||
color: white;
|
||||
content: url('/arrow-right-small.svg');
|
||||
}
|
||||
.small-swiper .swiper-button-prev:after {
|
||||
color: white;
|
||||
content: url('/arrow-left-small.svg');
|
||||
}
|
||||
|
||||
.big-swiper .swiper-button-next:after {
|
||||
color: white;
|
||||
content: url('/arrow-right-big.svg');
|
||||
}
|
||||
.big-swiper .swiper-button-prev:after {
|
||||
color: white;
|
||||
content: url('/arrow-left-big.svg');
|
||||
}
|
||||
|
||||
video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
main,
|
||||
nav,
|
||||
footer {
|
||||
z-index: 10;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
transition: all 0.3s ease;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.MuiInputBase-root.MuiOutlinedInput-root {
|
||||
font-family: var(--font-roboto);
|
||||
border-radius: 0;
|
||||
border: 2px solid rgba(139, 218, 255, 0.24);
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
p a {
|
||||
display: inline;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
big {
|
||||
font-size: inherit !important;
|
||||
}
|
||||
|
||||
.clamped {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 4;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.input-style {
|
||||
@apply bg-[#E6E6FA] rounded-xl py-3 px-4 placeholder:text-[#BCBCD6] outline-none;
|
||||
}
|
||||
|
||||
.calendar [aria-label='Go to next month'] {
|
||||
@apply shadow-sm transition-all;
|
||||
}
|
||||
|
||||
.calendar [aria-label='Go to previous month'] {
|
||||
@apply shadow-sm transition-all;
|
||||
}
|
||||
|
||||
.day-styles [name='day'] {
|
||||
@apply p-4 text-textDarkt leading-[140%] bg-purple-600 rounded-full;
|
||||
}
|
||||
|
||||
.btn2 {
|
||||
@apply text-textBlack leading-[140%] py-1 px-3 rounded-[8px] bg-white border border-[#E6E6FA] font-semibold shadow-sm;
|
||||
}
|
||||
|
||||
.pagination-item {
|
||||
@apply px-3 py-1 rounded-md border border-textBlack/20 text-textBlack/80 text-[12px] cursor-pointer hover:bg-textBlack transition-all hover:text-white;
|
||||
}
|
||||
|
||||
.pagination-current {
|
||||
@apply px-3 py-1 rounded-md border bg-fillButtonAccentDefault text-white text-[12px];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
import localFont from 'next/font/local';
|
||||
import Script from 'next/script';
|
||||
|
||||
import { Roboto } from 'next/font/google';
|
||||
import { Merriweather } from 'next/font/google';
|
||||
import { Merriweather_Sans } from 'next/font/google';
|
||||
import { Alexandria } from 'next/font/google';
|
||||
|
||||
import 'swiper/swiper-bundle.css';
|
||||
import './globals.css';
|
||||
import QueryProvider from '@/providers/QueryProvider';
|
||||
// FONTS
|
||||
const aeroport = localFont({
|
||||
src: '../fonts/Aeroport.otf',
|
||||
variable: '--font-aeroport',
|
||||
});
|
||||
const roboto = Roboto({
|
||||
subsets: ['latin'],
|
||||
weight: ['300', '400', '700'],
|
||||
variable: '--font-roboto',
|
||||
});
|
||||
const mw = Merriweather({
|
||||
subsets: ['cyrillic', 'cyrillic-ext', 'latin', 'latin-ext'],
|
||||
weight: '700',
|
||||
variable: '--font-mw',
|
||||
});
|
||||
const mw_sans = Merriweather_Sans({
|
||||
subsets: ['cyrillic-ext', 'latin', 'latin-ext'],
|
||||
weight: ['300', '400', '700'],
|
||||
variable: '--font-mwsans',
|
||||
});
|
||||
const alexandria = Alexandria({
|
||||
subsets: ['latin', 'latin-ext'],
|
||||
variable: '--font-alexandria',
|
||||
});
|
||||
|
||||
export const metadata = {
|
||||
title: 'Turkmen TV',
|
||||
};
|
||||
|
||||
interface IProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export default function RootLayout({ children }: IProps) {
|
||||
return (
|
||||
<html
|
||||
lang="tm"
|
||||
className={`${aeroport.variable} ${mw.variable} ${roboto.variable} ${mw_sans.variable} ${alexandria.variable}`}>
|
||||
<head>
|
||||
<link rel="icon" href="/logo.png" sizes="any" />
|
||||
</head>
|
||||
<body className="-z-0 relative">
|
||||
<QueryProvider>{children}</QueryProvider>
|
||||
</body>
|
||||
|
||||
<Script
|
||||
id="ganalytics-import"
|
||||
async
|
||||
src="https://www.googletagmanager.com/gtag/js?id=G-F2267QXY9T"></Script>
|
||||
<Script id="ganalytics-body">
|
||||
{`
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
|
||||
gtag('config', 'G-F2267QXY9T');`}
|
||||
</Script>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
import { Queries } from '@/api/queries';
|
||||
import MainSwiper from '@/components/home/MainSwiper';
|
||||
import Marque from '@/components/home/Marque';
|
||||
import SmallSwipers from '@/components/home/SmallSwipers';
|
||||
import Toolbar from '@/components/home/Toolbar';
|
||||
import Hydrate from '@/utils/HydrateClient';
|
||||
import getQueryClient from '@/utils/getQueryClient';
|
||||
import { dehydrate } from '@tanstack/react-query';
|
||||
|
||||
const Home = async () => {
|
||||
const queryClient = getQueryClient();
|
||||
queryClient.prefetchQuery({
|
||||
queryKey: ['home'],
|
||||
queryFn: () => Queries.getHome(),
|
||||
});
|
||||
|
||||
const dehydratedState = dehydrate(queryClient);
|
||||
return (
|
||||
<main className="main">
|
||||
<Hydrate state={dehydratedState}>
|
||||
<div className="grid lg:grid-cols-home_custom grid-cols-1">
|
||||
{/* <Toolbar /> */}
|
||||
<MainSwiper />
|
||||
<SmallSwipers />
|
||||
<Marque />
|
||||
</div>
|
||||
</Hydrate>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
||||
export default Home;
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
|
||||
const Success = () => {
|
||||
return (
|
||||
<main className="success flex items-center justify-center h-screen">
|
||||
<div className="flex flex-col gap-4 items-center justify-center h-screen">
|
||||
<Image
|
||||
src={'/success.svg'}
|
||||
alt="success icon"
|
||||
width={100}
|
||||
height={100}
|
||||
className="w-[100px] h-[100px] object-contain"
|
||||
/>
|
||||
<h1 className="text-black font-mw_sans font-bold text-[32px]">Gutlaýarys!</h1>
|
||||
<p className="font-roboto text-black text-lg text-center">
|
||||
Siziň sargydyňyz kabul edildi, “mahabat@turkmentv.gov.tm” elektron poçtasyna
|
||||
rekwizitleriňizi ugratmagyňyzy haýyş edýäris!
|
||||
</p>
|
||||
<Link
|
||||
href={'/advert'}
|
||||
className="flex items-center justify-center gap-2 text-white text-lg font-roboto text-center rounded-[30px] bg-mlightblue py-4 min-w-[480px]">
|
||||
<Image
|
||||
src={'/arrow-left.svg'}
|
||||
alt="arrow"
|
||||
width={32}
|
||||
height={14}
|
||||
className="w-8 h-[14px] object-contain"
|
||||
/>
|
||||
<p>Yza geç</p>
|
||||
</Link>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
export default Success;
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
export default {
|
||||
API_SRC: 'https://turkmentv.gov.tm/v2/api',
|
||||
// API_SRC: 'http://turkmentv.gov.tm:8080/api',
|
||||
|
||||
// MATERIALS_SRC: 'http://turkmentv.gov.tm:8082/api',
|
||||
MATERIALS_SRC: 'https://turkmentv.gov.tm/v2/api',
|
||||
|
||||
// NEWS_SRC: 'http://216.250.11.231:8083/api/v1',
|
||||
NEWS_SRC: 'https://turkmenistaninfo.gov.tm/app/api/v1',
|
||||
|
||||
// Quiz and voting
|
||||
QUIZ_SRC: 'https://sms.turkmentv.gov.tm/api',
|
||||
|
||||
// SMS
|
||||
SMS_SRC: 'https://extra.turkmentv.gov.tm/api',
|
||||
};
|
||||
|
||||
// export default {
|
||||
// API_SRC: 'http://turkmentv.gov.tm:8080/api',
|
||||
// MATERIALS_SRC: 'http://turkmentv.gov.tm/v2/api',
|
||||
// NEWS_SRC: 'http://216.250.11.231/habar/api/v1',
|
||||
// };
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
import { IChannels } from '@/typings/channels.type';
|
||||
|
||||
const channels: IChannels[] = [
|
||||
{
|
||||
id: 1,
|
||||
img: '/video_arkadag.jpg',
|
||||
name: 'Arkadag',
|
||||
source: 'https://alpha.tv.online.tm/hls/ch000.m3u8',
|
||||
channel: 1,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
img: '/video_altyn_asyr.jpg',
|
||||
name: 'Altyn Asyr',
|
||||
source: 'https://alpha.tv.online.tm/hls/ch001.m3u8',
|
||||
channel: 2,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
img: '/video_yaslyk.jpg',
|
||||
name: 'Ýaşlyk',
|
||||
source: 'https://alpha.tv.online.tm/hls/ch002.m3u8',
|
||||
channel: 3,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
img: '/video_miras.jpg',
|
||||
name: 'Miras',
|
||||
source: 'https://alpha.tv.online.tm/hls/ch003.m3u8',
|
||||
channel: 4,
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
img: '/video_turkmenistan.jpg',
|
||||
name: 'Türkmenistan',
|
||||
source: 'https://alpha.tv.online.tm/hls/ch007.m3u8',
|
||||
channel: 5,
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
img: '/video_turkmen_owaz.jpg',
|
||||
name: 'Türkmen Owazy',
|
||||
source: 'https://alpha.tv.online.tm/hls/ch005.m3u8',
|
||||
channel: 6,
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
img: '/video_ashgabat.jpg',
|
||||
name: 'Aşgabat',
|
||||
source: 'https://alpha.tv.online.tm/hls/ch006.m3u8',
|
||||
channel: 7,
|
||||
},
|
||||
|
||||
{
|
||||
id: 8,
|
||||
img: '/video_turkmen_sport.jpg',
|
||||
name: 'Türkmen Sporty',
|
||||
source: 'https://alpha.tv.online.tm/hls/ch004.m3u8',
|
||||
channel: 8,
|
||||
},
|
||||
];
|
||||
|
||||
export default channels;
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "default",
|
||||
"rsc": true,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.ts",
|
||||
"css": "app/globals.css",
|
||||
"baseColor": "slate",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
'use client';
|
||||
import { v4 } from 'uuid';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { Queries } from '@/api/queries';
|
||||
import Image from 'next/image';
|
||||
import Loader from './Loader';
|
||||
import closeIcon from '@/public/close-outline.svg';
|
||||
import { useContext, useState } from 'react';
|
||||
import MaterialsContext from '@/context/MaterialsContext';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
interface IProps {
|
||||
line: boolean;
|
||||
}
|
||||
|
||||
const Aside = ({ line = true }: IProps) => {
|
||||
const { data, error, isFetching } = useQuery({
|
||||
queryKey: ['channels'],
|
||||
queryFn: () => Queries.getChannels(),
|
||||
});
|
||||
const { params, setParams } = useContext(MaterialsContext);
|
||||
|
||||
const [channelActive, setChannelActive] = useState(false);
|
||||
const channelHandler = (id: number, state: boolean) => {
|
||||
setChannelActive(state);
|
||||
setParams({ ...params, channel_id: String(id) });
|
||||
};
|
||||
|
||||
if (isFetching) return <Loader />;
|
||||
if (error) return <h1>{JSON.stringify(error)}</h1>;
|
||||
|
||||
return (
|
||||
<aside
|
||||
className={`aside flex flex-wrap md:flex-col md:gap-3 gap-3 md:pr-7 h-fit w-fit pb-5 sticky top-5 justify-between ${
|
||||
line &&
|
||||
'md:border md:border-t-0 md:border-l-0 md:border-b-0 md:border-r-gray-400 md:transition-all md:dark:border-r-black'
|
||||
}`}>
|
||||
{data
|
||||
? data.data.map((image) => (
|
||||
<motion.div
|
||||
onClick={() => channelHandler(image.id, true)}
|
||||
className="aside-img w-14 h-14 rounded-full overflow-hidden border bg-white border-gray-500 cursor-pointer"
|
||||
key={v4()}
|
||||
initial={{ border: 'solid 1px rgb(107, 114, 128)' }}
|
||||
animate={
|
||||
params.channel_id === String(image.id) ? { border: 'solid 3px #FFAB48' } : {}
|
||||
}>
|
||||
<Image
|
||||
src={image.image}
|
||||
alt={image.name}
|
||||
unoptimized
|
||||
unselectable="off"
|
||||
className="h-full w-full object-contain rounded-full"
|
||||
width={44}
|
||||
height={44}
|
||||
/>
|
||||
</motion.div>
|
||||
))
|
||||
: null}
|
||||
{channelActive && (
|
||||
<motion.div
|
||||
onClick={() => channelHandler(0, false)}
|
||||
className="aside-img w-14 h-14 rounded-full overflow-hidden border bg-white border-gray-500 cursor-pointer p-2"
|
||||
key={v4()}
|
||||
initial={{ border: 'solid 1px rgb(107, 114, 128)' }}
|
||||
animate={params.channel_id === String(0) ? { border: 'solid 3px #FFAB48' } : {}}>
|
||||
<Image
|
||||
src={closeIcon}
|
||||
alt="close"
|
||||
unoptimized
|
||||
unselectable="off"
|
||||
className="h-full w-full object-contain rounded-full"
|
||||
width={44}
|
||||
height={44}
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
</aside>
|
||||
);
|
||||
};
|
||||
|
||||
export default Aside;
|
||||
// export default Aside;
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
import Image from "next/image";
|
||||
|
||||
const AsideAdd = () => {
|
||||
return (
|
||||
<aside className="w-full">
|
||||
<Image
|
||||
className="w-full h-80% object-cover"
|
||||
src={"/aside-add.jpg"}
|
||||
alt="aside-add"
|
||||
unoptimized
|
||||
unselectable="off"
|
||||
width={100}
|
||||
height={200}
|
||||
/>
|
||||
</aside>
|
||||
);
|
||||
};
|
||||
|
||||
export default AsideAdd;
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
const Buble = () => {
|
||||
return (
|
||||
<div className="-z-0 relative pointer-events-none">
|
||||
<div className="buble absolute top-10 left-28 w-96 h-96 bg-[#bfa6e7] rounded-full mix-blend-multiply filter blur-3xl opacity-70 animate-buble transition-colors dark:bg-[#3448FF]"></div>
|
||||
<div className="buble absolute bottom-20 right-28 w-96 h-96 bg-[#bfa6e7] rounded-full mix-blend-multiply filter blur-3xl opacity-70 animate-buble transition-colors dark:bg-[#3448FF]"></div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Buble;
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
interface IProps {
|
||||
name: string;
|
||||
width?: string;
|
||||
type: 'submit' | 'button' | 'reset';
|
||||
onClick?: () => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const Button = (props: IProps) => {
|
||||
return (
|
||||
<div className="flex justify-end">
|
||||
<button
|
||||
disabled={props.disabled}
|
||||
type={props.type}
|
||||
onClick={props.onClick}
|
||||
style={props.width ? { width: props.width } : {}}
|
||||
className={`min-w-[200px] bg-mlightblue font-roboto font-bold text-lg text-white text-center rounded-five py-2 px-5 flex justify-center flex-shrink-0 h-fit`}>
|
||||
{props.name}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Button;
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
"use client";
|
||||
import Category from "./Category";
|
||||
import { v4 } from "uuid";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { Queries } from "@/api/queries";
|
||||
import Loader from "./Loader";
|
||||
|
||||
const Categories = () => {
|
||||
const { data, error, isFetching } = useQuery({
|
||||
queryKey: ["categories"],
|
||||
queryFn: () => Queries.getCategories(),
|
||||
});
|
||||
|
||||
if (isFetching) return <Loader />;
|
||||
if (error) return <h1>{JSON.stringify(error)}</h1>;
|
||||
|
||||
return (
|
||||
<ul className="flex flex-wrap gap-x-2 gap-y-3">
|
||||
<Category id={0} name={"Hemme"} key={v4()} isInitial />
|
||||
{data
|
||||
? data.data.map((category) => (
|
||||
<Category id={category.id} name={category.name} key={v4()} />
|
||||
))
|
||||
: null}
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
|
||||
export default Categories;
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
'use client';
|
||||
import { useContext } from 'react';
|
||||
import { ICategory } from '@/typings/category.type';
|
||||
import GlobalContext from '@/context/GlobalContext';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import MaterialsContext from '@/context/MaterialsContext';
|
||||
|
||||
interface IProps extends ICategory {
|
||||
isInitial?: boolean;
|
||||
}
|
||||
|
||||
const Category = ({ id, name, isInitial }: IProps) => {
|
||||
const path = usePathname() ?? '/';
|
||||
const context = useContext(GlobalContext);
|
||||
const { params, setParams } = useContext(MaterialsContext);
|
||||
const category = params.category_id;
|
||||
const { theme } = context.themeContext;
|
||||
return (
|
||||
<li className="flex-1 max-w-[180px] cursor-pointer">
|
||||
<div
|
||||
onClick={() => {
|
||||
setParams({ ...params, category_id: String(id) });
|
||||
}}
|
||||
className="bg-categorybg rounded-five px-3 py-2 flex justify-center w-full whitespace-nowrap font-roboto transition-all text-black dark:text-white dark:bg-[#38383880]"
|
||||
style={
|
||||
category
|
||||
? category === String(id)
|
||||
? {
|
||||
backgroundColor: theme === 'light' ? '#FFAB48' : '#FFFFFF',
|
||||
color: 'black',
|
||||
}
|
||||
: {}
|
||||
: path.endsWith('/treasury') && isInitial
|
||||
? {
|
||||
backgroundColor: theme === 'light' ? '#FFAB48' : '#FFFFFF',
|
||||
color: 'black',
|
||||
}
|
||||
: {}
|
||||
}>
|
||||
{name}
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
};
|
||||
|
||||
export default Category;
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
const ContactDetails = () => {
|
||||
return (
|
||||
<div className="flex flex-col pl-6 md:text-[22px] text-base text-black font-light font-mw_sans border-l-[6px] border-mblue h-fit min-w-[350px] dark:text-white">
|
||||
<h2 className="md:text-[32px] text-xl font-normal">MAHABAT MÜDIRLIGI</h2>
|
||||
<a href="tel:+99312493705">Phone: +993 12 493705</a>
|
||||
<span>Aşgabat şäheri Oguzhan 13</span>
|
||||
<a href="mailto:mahabat@turkmentv.gov.tm">mahabat@turkmentv.gov.tm</a>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ContactDetails;
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
'use client';
|
||||
import { ChangeEvent, HTMLInputTypeAttribute, useState } from 'react';
|
||||
|
||||
interface ICustomInput {
|
||||
name: string;
|
||||
type?: HTMLInputTypeAttribute;
|
||||
label?: string;
|
||||
placeholder?: string;
|
||||
isTextArea?: boolean;
|
||||
required?: boolean;
|
||||
isBlack?: boolean;
|
||||
value: {
|
||||
value: string;
|
||||
setValue: (newValue: ICustomInput['value']['value']) => void;
|
||||
};
|
||||
validate?: {
|
||||
isValid: boolean;
|
||||
errMsg: string;
|
||||
};
|
||||
}
|
||||
|
||||
const CustomInput = ({
|
||||
value,
|
||||
name,
|
||||
isTextArea,
|
||||
label,
|
||||
placeholder,
|
||||
type,
|
||||
required,
|
||||
isBlack,
|
||||
validate,
|
||||
}: ICustomInput) => {
|
||||
const [isTouched, setIsTouched] = useState<boolean>(false);
|
||||
return (
|
||||
<div className="custom-input relative">
|
||||
{label ? (
|
||||
<label
|
||||
htmlFor={name}
|
||||
className="text-black md:text-lg text-base px-4 bg-white font-mw_sans absolute -top-[14px] left-[2%]"
|
||||
style={isBlack ? { background: '#1a1a1a', color: '#fff' } : {}}>
|
||||
{label}
|
||||
</label>
|
||||
) : null}
|
||||
{!isTextArea ? (
|
||||
<div className="flexs flex-col gap-2">
|
||||
<input
|
||||
type={type}
|
||||
name={name}
|
||||
id={name}
|
||||
required={required}
|
||||
placeholder={placeholder}
|
||||
className="p-5 border border-solid border-[#B2B1B1] rounded-[5px] font-roboto text-[#9A9A9A] md:text-[22px] text-base w-full"
|
||||
style={isBlack ? { background: '#1a1a1a', color: '#fff' } : {}}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) => {
|
||||
if (!isTouched) {
|
||||
setIsTouched(true);
|
||||
}
|
||||
value.setValue(e.target.value);
|
||||
}}
|
||||
/>
|
||||
{isTouched && validate && !validate.isValid ? (
|
||||
<span className="text-mred text-sm font-bold font-roboto">{validate.errMsg}</span>
|
||||
) : null}
|
||||
</div>
|
||||
) : (
|
||||
<textarea
|
||||
name={name}
|
||||
id={name}
|
||||
required={required}
|
||||
placeholder={placeholder}
|
||||
rows={5}
|
||||
className="resize-none p-5 border border-solid border-[#B2B1B1] rounded-[5px] font-roboto text-[#9A9A9A] md:text-[22px] text-base w-full"
|
||||
onChange={(e: ChangeEvent<HTMLTextAreaElement>) => value.setValue(e.target.value)}
|
||||
style={isBlack ? { background: '#1a1a1a', color: '#fff' } : {}}></textarea>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomInput;
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
'use client';
|
||||
import { useRef, useState } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { v4 } from 'uuid';
|
||||
import { BiSolidDownArrow } from 'react-icons/bi';
|
||||
import { useOnClickOutside } from 'usehooks-ts';
|
||||
|
||||
interface ICustomSelect {
|
||||
name: string;
|
||||
label?: string;
|
||||
placeholder?: string;
|
||||
required?: boolean;
|
||||
items: string[];
|
||||
isBlack?: boolean;
|
||||
setValue: (newValue: string) => void;
|
||||
}
|
||||
|
||||
const CustomSelect = ({
|
||||
name,
|
||||
label,
|
||||
placeholder,
|
||||
required,
|
||||
items,
|
||||
setValue,
|
||||
isBlack,
|
||||
}: ICustomSelect) => {
|
||||
const [open, setOpen] = useState<boolean>(false);
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const [input, setInput] = useState<string>();
|
||||
|
||||
useOnClickOutside(ref, () => setOpen(false));
|
||||
return (
|
||||
<div ref={ref} className="custom-input relative cursor-pointer" onClick={() => setOpen(!open)}>
|
||||
{label ? (
|
||||
<label
|
||||
htmlFor={name}
|
||||
className="text-black text-lg px-4 bg-white font-mw_sans absolute -top-[14px] left-[2%]"
|
||||
style={isBlack ? { background: '#1a1a1a', color: '#fff' } : {}}>
|
||||
{label}
|
||||
</label>
|
||||
) : null}
|
||||
<input
|
||||
readOnly
|
||||
type={'text'}
|
||||
name={name}
|
||||
id={name}
|
||||
required={required}
|
||||
placeholder={placeholder}
|
||||
className="p-5 border border-solid border-[#B2B1B1] rounded-[5px] font-roboto text-[#9A9A9A] text-[22px] w-full pointer-events-none"
|
||||
style={isBlack ? { background: '#1a1a1a', color: '#fff' } : {}}
|
||||
value={input}
|
||||
/>
|
||||
<motion.div
|
||||
className="absolute top-[40%] right-4"
|
||||
initial={{ rotate: '0deg' }}
|
||||
animate={
|
||||
open
|
||||
? {
|
||||
rotate: ' 180deg',
|
||||
transition: { type: 'tween', duration: 0.4 },
|
||||
}
|
||||
: { rotate: '0deg', transition: { type: 'tween', duration: 0.4 } }
|
||||
}>
|
||||
<BiSolidDownArrow color="#B2B1B1" />
|
||||
</motion.div>
|
||||
<motion.ul
|
||||
className="absolute top-[100%] left-0 w-full h-fit bg-white z-20 rounded-[5px] border border-solid overflow-hidden border-[#B2B1B1] max-h-52 overflow-y-auto"
|
||||
initial={{ height: '0%' }}
|
||||
animate={
|
||||
open ? { height: 'fit-content', opacity: '100%' } : { height: '0%', opacity: '0%' }
|
||||
}>
|
||||
{items.map((item) => (
|
||||
<motion.li
|
||||
className="p-5 text-[20px] text-black w-full z-30 relative cursor-pointer border-b border-solid border-[#B2B1B1]"
|
||||
style={isBlack ? { color: '#fff' } : {}}
|
||||
initial={!isBlack ? { background: '#fff' } : { background: '#1a1a1a' }}
|
||||
whileHover={!isBlack ? { background: '#dadada' } : { background: '#151515' }}
|
||||
onClick={() => {
|
||||
setValue(item);
|
||||
setInput(item);
|
||||
}}
|
||||
key={v4()}>
|
||||
{item}
|
||||
</motion.li>
|
||||
))}
|
||||
</motion.ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default CustomSelect;
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
import Link from 'next/link';
|
||||
import Image from 'next/image';
|
||||
import apple from '@/public/App Store Button Black.svg';
|
||||
import play from '@/public/Google Play Button Black.svg';
|
||||
import app1 from '@/public/tv-mobile.png';
|
||||
import app2 from '@/public/tv-mobile2.png';
|
||||
|
||||
const Footer = () => {
|
||||
const year = new Date().getFullYear();
|
||||
return (
|
||||
<footer className="footer w-full bg-[#121268] z-auto">
|
||||
<div className="container">
|
||||
<div className="footer-inner py-[22px] md:px-8 md:grid md:grid-cols-footer flex flex-col gap-6 overflow-x-hidden">
|
||||
<div className="flex flex-col md:gap-14 gap-6">
|
||||
<ul className="grid grid-cols-2 gap-3">
|
||||
<li>
|
||||
<Link
|
||||
href={'/news'}
|
||||
className="w-full h-full text-left font-roboto text-lg text-white">
|
||||
Habarlar
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href={'/advert'}
|
||||
className="w-full h-full text-left font-roboto text-lg text-white">
|
||||
Mahabat
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href={'/treasury'}
|
||||
className="w-full h-full text-left font-roboto text-lg text-white">
|
||||
Hazyna
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href={'/about_us'}
|
||||
className="w-full h-full text-left font-roboto text-lg text-white">
|
||||
Biz barada
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href={'/live'}
|
||||
className="w-full h-full text-left font-roboto text-lg text-white">
|
||||
Göni Ýaýlym
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href={'/contact_us'}
|
||||
className="w-full h-full text-left font-roboto text-lg text-white">
|
||||
Habarlaşmak üçin
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
<div className="flex md:flex-row flex-col md:gap-12 gap-6">
|
||||
<div className="flex items-center gap-6">
|
||||
<div className="relative w-20 h-20 rounded-full overflow-hidden">
|
||||
<Image src={app2} alt="asf" fill />
|
||||
</div>
|
||||
<div className="flex flex-col gap-3 w-fit">
|
||||
<Link
|
||||
href="https://apps.apple.com/ru/app/t%C3%BCrkmen-radio/id1622189361"
|
||||
className="relative h-12 w-40 block"
|
||||
target="_blank">
|
||||
<Image src={apple} alt="app store" fill />
|
||||
</Link>
|
||||
<Link
|
||||
href="https://play.google.com/store/apps/details?id=com.app.telecom_radio"
|
||||
className="relative h-12 w-40 block"
|
||||
target="_blank">
|
||||
<Image src={play} alt="google play" fill />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-6">
|
||||
<div className="relative w-20 h-20 rounded-lg overflow-hidden">
|
||||
<Image src={app1} alt="asf" fill />
|
||||
</div>
|
||||
<div className="flex flex-col gap-3 w-fit">
|
||||
<Link
|
||||
href="https://apps.apple.com/ru/app/t%C3%BCrkmen-tv/id915833573"
|
||||
className="relative h-12 w-40 block"
|
||||
target="_blank">
|
||||
<Image src={apple} alt="app store" fill />
|
||||
</Link>
|
||||
<Link
|
||||
href="https://play.google.com/store/apps/details?id=tm.ykjam.turkmentv"
|
||||
className="relative h-12 w-40 block"
|
||||
target="_blank">
|
||||
<Image src={play} alt="google play" fill />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p className="font-alexandria text-[#5151CF] md:block hidden">
|
||||
{year} © TurkmenTV. All rights reserved.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-9">
|
||||
<p className="relative font-roboto font-normal opacity-60 text-white flex flex-col gap-3 pl-[18px]">
|
||||
<a href="tel:+99312493705">Phone: +993 12 493705</a>
|
||||
<span>Aşgabat şäheri Oguzhan 13</span>
|
||||
<a href="mailto:mahabat@turkmentv.gov.tm">mahabat@turkmentv.gov.tm</a>
|
||||
<span className="absolute top-0 left-0 w-[5px] h-full bg-white"></span>
|
||||
</p>
|
||||
</div>
|
||||
<p className="font-alexandria text-[#5151CF] md:hidden block">
|
||||
{year} © TurkmenTV. All rights reserved.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
};
|
||||
|
||||
export default Footer;
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
import Image from "next/image";
|
||||
|
||||
interface IProps {
|
||||
width: number;
|
||||
height: number;
|
||||
element: string;
|
||||
}
|
||||
|
||||
const Icon = ({ width, height, element }: IProps) => {
|
||||
return (
|
||||
<div style={{ width, height }}>
|
||||
<Image
|
||||
src={element}
|
||||
unoptimized
|
||||
unselectable="off"
|
||||
alt={`icon_${element}`}
|
||||
width={width}
|
||||
height={height}
|
||||
></Image>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Icon;
|
||||
|
|
@ -0,0 +1,187 @@
|
|||
'use client';
|
||||
// NextJs components
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
// React query
|
||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||
import { Queries } from '@/api/queries';
|
||||
// Components
|
||||
import Loader from './Loader';
|
||||
import VideoPlayer from './VideoPlayer';
|
||||
import SectionTitle from './SectionTitle';
|
||||
// Images and cions
|
||||
import { BiLike, BiSolidDislike, BiSolidLike, BiDislike } from 'react-icons/bi';
|
||||
import { SlEye } from 'react-icons/sl';
|
||||
import aydym from '@/public/aydym-com.webp';
|
||||
import horjun from '@/public/horjun.png';
|
||||
import belet from '@/public/belet.jpg';
|
||||
import { useState } from 'react';
|
||||
import { useLocalStorage } from 'usehooks-ts';
|
||||
import axios from 'axios';
|
||||
|
||||
interface IProps {
|
||||
video_id: number;
|
||||
}
|
||||
|
||||
const InfoBlock = ({ video_id }: IProps) => {
|
||||
const [isLikedId, setIsLikedId] = useLocalStorage<number[]>('isLiked', []) || [];
|
||||
const [isDisLikedId, setIsDisLikedId] = useLocalStorage<number[]>('isDisliked', []) || [];
|
||||
|
||||
async function addLikes() {
|
||||
return axios.post(`https://turkmentv.gov.tm/v2/api/material/${video_id}/likes`, {
|
||||
like: isLikedId.includes(video_id) ? true : false,
|
||||
});
|
||||
}
|
||||
async function addDisLikes() {
|
||||
return axios.post(`https://turkmentv.gov.tm/v2/api/material/${video_id}/dislikes`, {
|
||||
dislike: isDisLikedId.includes(video_id) ? true : false,
|
||||
});
|
||||
}
|
||||
|
||||
const mutationLikes = useMutation(() => addLikes());
|
||||
const mutationDisLikes = useMutation(() => addDisLikes());
|
||||
|
||||
const onLikeHandler = () => {
|
||||
if (isLikedId.includes(video_id)) {
|
||||
setIsLikedId(isLikedId.filter((id) => id !== video_id));
|
||||
} else {
|
||||
if (isDisLikedId.includes(video_id)) {
|
||||
setIsDisLikedId(isDisLikedId.filter((id) => id !== video_id));
|
||||
mutationDisLikes.mutate();
|
||||
}
|
||||
setIsLikedId([...isLikedId, video_id]);
|
||||
}
|
||||
|
||||
mutationLikes.mutate();
|
||||
};
|
||||
|
||||
const onDisLikeHandler = () => {
|
||||
if (isDisLikedId.includes(video_id)) {
|
||||
setIsDisLikedId(isDisLikedId.filter((id) => id !== video_id));
|
||||
} else {
|
||||
if (isLikedId.includes(video_id)) {
|
||||
setIsLikedId(isLikedId.filter((id) => id !== video_id));
|
||||
mutationLikes.mutate();
|
||||
}
|
||||
setIsDisLikedId([...isDisLikedId, video_id]);
|
||||
}
|
||||
|
||||
mutationDisLikes.mutate();
|
||||
};
|
||||
|
||||
const { data, isFetching, error } = useQuery({
|
||||
queryKey: ['video', video_id, mutationLikes, mutationDisLikes],
|
||||
queryFn: () => Queries.getVideo(video_id),
|
||||
});
|
||||
|
||||
if (isFetching) return <Loader height={200} />;
|
||||
if (error) return <h1>{JSON.stringify(error)}</h1>;
|
||||
|
||||
return (
|
||||
<div className="flex gap-6 max-w-[1220px] w-full justify-between h-full">
|
||||
<div className="flex flex-col gap-6 w-full h-full">
|
||||
<div className="flex justify-between gap-[32px] w-full h-full">
|
||||
<div className="w-full flex flex-col gap-6 h-full">
|
||||
<div className=" flex flex-col gap-2">
|
||||
<SectionTitle title={data!.data.title} />
|
||||
<div className="flex gap-2 items-center">
|
||||
<SlEye
|
||||
style={{ transition: '150ms all cubic-bezier(0.4, 0, 0.2, 1)' }}
|
||||
width={30}
|
||||
height={18}
|
||||
className="w-[30px] h-[18px]"
|
||||
/>
|
||||
<span className="font-roboto text-lg font-normal text-[#494949] transition-all dark:text-white">
|
||||
{data!.data.view}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-[40px] h-full w-full">
|
||||
<div className="flex gap-[40px] md:flex-row flex-col h-full w-full">
|
||||
<div>
|
||||
<VideoPlayer video_id={video_id} maxHeight="700px" maxWidth="100%" />
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-6">
|
||||
{data?.data.desc ? (
|
||||
<p className="font-roboto text-lg w-full text-black">{data!.data.desc}</p>
|
||||
) : null}
|
||||
<div className="flex items-center bg-slate-50 rounded-full overflow-hidden w-fit">
|
||||
<div
|
||||
className="flex items-center gap-4 cursor-pointer py-4 px-6 hover:bg-slate-100"
|
||||
onClick={() => onLikeHandler()}>
|
||||
<button>
|
||||
{isLikedId.includes(video_id) ? (
|
||||
<BiSolidLike size={30} />
|
||||
) : (
|
||||
<BiLike size={30} />
|
||||
)}
|
||||
</button>
|
||||
<span className="block text-xl ">{data?.data.likes}</span>
|
||||
</div>
|
||||
<div
|
||||
className="flex items-center gap-4 cursor-pointer py-4 px-6 hover:bg-slate-100"
|
||||
onClick={() => onDisLikeHandler()}>
|
||||
<button>
|
||||
{isDisLikedId.includes(video_id) ? (
|
||||
<BiSolidDislike size={30} />
|
||||
) : (
|
||||
<BiDislike size={30} />
|
||||
)}
|
||||
</button>
|
||||
<span className="block text-xl ">{data?.data.dislikes}</span>
|
||||
</div>
|
||||
</div>
|
||||
{data?.data.aydym_com_url ||
|
||||
data?.data.horjun_content_url ||
|
||||
data?.data.belet_url ? (
|
||||
<div className="flex flex-col gap-6">
|
||||
<h2 className="text-2xl font-semibold">Beýleki platformalarda seret:</h2>
|
||||
<div className="flex gap-11 items-center">
|
||||
{data?.data.aydym_com_url ? (
|
||||
<Link
|
||||
href={data?.data.aydym_com_url}
|
||||
target="_blank"
|
||||
className="flex flex-col items-center justify-center gap-2">
|
||||
<div className="relative h-16 w-16 flex items-center justify-center overflow-hidden border-[#00AEFF] border-[1.5px] p-2 rounded-full">
|
||||
<Image src={aydym} alt="Aydym.com" className="rounded-full" />
|
||||
</div>
|
||||
<h3>Aydym.com</h3>
|
||||
</Link>
|
||||
) : null}
|
||||
{data?.data.horjun_content_url ? (
|
||||
<Link
|
||||
href={data?.data.horjun_content_url}
|
||||
target="_blank"
|
||||
className="flex flex-col items-center justify-center gap-2">
|
||||
<div className="relative h-16 w-16 flex items-center justify-center overflow-hidden border-[#00AEFF] border-[1.5px] p-2 rounded-full">
|
||||
<Image src={horjun} alt="HorjunTv" className="rounded-full" />
|
||||
</div>
|
||||
<h3>HorjunTv</h3>
|
||||
</Link>
|
||||
) : null}
|
||||
{data?.data.belet_url ? (
|
||||
<Link
|
||||
href={data.data.belet_url}
|
||||
target="_blank"
|
||||
className="flex flex-col items-center justify-center gap-2">
|
||||
<div className="relative h-16 w-16 flex items-center justify-center overflow-hidden border-[#00AEFF] border-[1.5px] p-2 rounded-full">
|
||||
<Image src={belet} alt="BeletTv" className="rounded-full" />
|
||||
</div>
|
||||
<h3>BeletFilm</h3>
|
||||
</Link>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default InfoBlock;
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import Image from 'next/image';
|
||||
import { CSSProperties } from 'react';
|
||||
|
||||
interface IProps {
|
||||
height?: CSSProperties['height'];
|
||||
}
|
||||
|
||||
const Loader = ({ height = 'auto' }: IProps) => {
|
||||
return (
|
||||
<div className="loader my-6 w-full flex justify-center items-center" style={{ height }}>
|
||||
<Image
|
||||
src={'/spin-blue.svg'}
|
||||
alt="loader"
|
||||
width={38}
|
||||
height={38}
|
||||
unoptimized
|
||||
className="object-contain w-[50px] h-[50px]"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Loader;
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
import Image from 'next/image';
|
||||
|
||||
import map from '@/public/mahabat-map.png';
|
||||
|
||||
const Map = () => {
|
||||
return (
|
||||
<div className="w-full h-full border-2 border-black rounded-five p-4">
|
||||
<Image
|
||||
src={map}
|
||||
width={600}
|
||||
height={200}
|
||||
unoptimized
|
||||
unselectable="off"
|
||||
alt="map"
|
||||
className="w-full h-full object-contain"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Map;
|
||||
|
|
@ -0,0 +1,192 @@
|
|||
'use client';
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { AiOutlineUser } from 'react-icons/ai';
|
||||
import ThemeSwitch from './home/ThemeSwitch';
|
||||
import { useContext, useEffect, useRef, useState } from 'react';
|
||||
import GlobalContext from '@/context/GlobalContext';
|
||||
import close from '@/public/close-white.svg';
|
||||
const MobileMenu = () => {
|
||||
const path = usePathname() ?? '/';
|
||||
const { burgerOpen, setBurgerOpen } = useContext(GlobalContext).burgerContext;
|
||||
|
||||
const onClickCloseBurgerHandler = () => {
|
||||
setBurgerOpen(false);
|
||||
};
|
||||
|
||||
const [dropDownOpened, setDropDownOpened] = useState(false);
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handleOutsideClick = (event: MouseEvent) => {
|
||||
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
|
||||
setDropDownOpened(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Attach the event listener to the document
|
||||
document.addEventListener('click', handleOutsideClick);
|
||||
|
||||
// Cleanup the event listener when the component unmounts
|
||||
return () => {
|
||||
document.removeEventListener('click', handleOutsideClick);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
{burgerOpen && (
|
||||
<div className="absolute top-0 left-0 w-full h-fit bg-[#131369] z-50 py-1">
|
||||
<div className="container">
|
||||
<div className="w-full h-screen flex flex-col gap-10">
|
||||
<div className="flex items-center justify-between">
|
||||
<Link href={'/'} className="logo">
|
||||
<Image
|
||||
priority
|
||||
src={'/logo.png'}
|
||||
alt="logo"
|
||||
unoptimized
|
||||
unselectable="off"
|
||||
width={60}
|
||||
height={60}
|
||||
className="w-[60px] h-[60px] object-contain"
|
||||
/>
|
||||
</Link>
|
||||
<div
|
||||
className="relative w-[24px] h-[24px] cursor-pointer "
|
||||
onClick={() => setBurgerOpen(false)}>
|
||||
<Image src={close} fill alt="menu" />
|
||||
</div>
|
||||
</div>
|
||||
<ul className="flex flex-col gap-10 items-start justify-center p-6">
|
||||
<li>
|
||||
<Link
|
||||
className="block text-3xl text-white transition-all font-roboto font-bold dark:text-white"
|
||||
onClick={() => onClickCloseBurgerHandler()}
|
||||
href={'/news'}
|
||||
style={path.includes('news') ? { color: '#FFAB48' } : {}}>
|
||||
Habarlar
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href={'/treasury'}
|
||||
onClick={() => onClickCloseBurgerHandler()}
|
||||
className="block text-3xl text-white transition-all font-roboto font-bold dark:text-white"
|
||||
style={
|
||||
path.includes('treasury') || path.includes('video/')
|
||||
? { color: '#FFAB48' }
|
||||
: {}
|
||||
}>
|
||||
Hazyna
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href={'/live'}
|
||||
onClick={() => onClickCloseBurgerHandler()}
|
||||
className="block text-3xl text-white transition-all font-roboto font-bold dark:text-white"
|
||||
style={path.includes('live') ? { color: '#FFAB48' } : {}}>
|
||||
Göni Ýaýlym
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href={'/advert'}
|
||||
onClick={() => onClickCloseBurgerHandler()}
|
||||
className="block text-3xl text-white transition-all font-roboto font-bold dark:text-white"
|
||||
style={path.includes('advert') ? { color: '#FFAB48' } : {}}>
|
||||
Mahabat
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href={'/contact_us'}
|
||||
onClick={() => onClickCloseBurgerHandler()}
|
||||
style={path.includes('contact_us') ? { color: '#FFAB48' } : {}}
|
||||
className="font-roboto font-bold text-white text-3xl dark:text-white transition-all">
|
||||
Habarlaşmak üçin
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<div
|
||||
ref={dropdownRef}
|
||||
className="flex flex-col cursor-pointer relative"
|
||||
onClick={() => setDropDownOpened(!dropDownOpened)}>
|
||||
<div className="flex items-center gap-[8px]">
|
||||
<span className="block text-3xl text-white transition-all font-roboto font-bold">
|
||||
Interaktiw
|
||||
</span>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
className={`${
|
||||
dropDownOpened ? '' : 'rotate-180'
|
||||
} transition-all ease-in duration-150`}>
|
||||
<path
|
||||
d="M7.41 15.41L12 10.83L16.59 15.41L18 14L12 8L6 14L7.41 15.41Z"
|
||||
fill="#fff"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={` flex flex-col gap-4 rounded-[8px] transition-all duration-150 ${
|
||||
dropDownOpened
|
||||
? 'h-fit opacity-100 pointer-events-auto py-[16px] px-[24px]'
|
||||
: ' h-0 opacity-0 pointer-events-none'
|
||||
}`}>
|
||||
<Link
|
||||
href={'/quiz'}
|
||||
className="block text-2xl text-white transition-all font-roboto font-bold "
|
||||
style={path.includes('quiz') ? { color: '#FFAB48' } : {}}
|
||||
onClick={() => {
|
||||
setDropDownOpened(false);
|
||||
onClickCloseBurgerHandler();
|
||||
}}>
|
||||
Bäsleşik
|
||||
</Link>
|
||||
<Link
|
||||
href={'/vote'}
|
||||
className="block text-2xl text-white transition-all font-roboto font-bold "
|
||||
onClick={() => {
|
||||
setDropDownOpened(false);
|
||||
onClickCloseBurgerHandler();
|
||||
}}>
|
||||
Ses bermek
|
||||
</Link>
|
||||
<Link
|
||||
href={'https://shop.turkmentv.gov.tm/'}
|
||||
className="block text-2xl text-white transition-all font-roboto font-bold"
|
||||
onClick={() => {
|
||||
setDropDownOpened(false);
|
||||
onClickCloseBurgerHandler();
|
||||
}}>
|
||||
TV market
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
target="_blank"
|
||||
href={'https://turkmentv.gov.tm/mahabat/admin/user/login'}
|
||||
onClick={() => onClickCloseBurgerHandler()}
|
||||
className="font-roboto font-bold text-white text-3xl dark:text-white transition-all p-2 bg-red-500 rounded-lg ">
|
||||
Ýaýlym tertibi
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default MobileMenu;
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
'use client';
|
||||
import Image from 'next/image';
|
||||
import { FiMoreHorizontal } from 'react-icons/fi';
|
||||
|
||||
interface IProps {
|
||||
onClick: () => void;
|
||||
disabled?: boolean;
|
||||
isFetching?: boolean;
|
||||
}
|
||||
const MoreBtn = ({ onClick, disabled, isFetching }: IProps) => {
|
||||
return (
|
||||
<button
|
||||
disabled={disabled}
|
||||
onClick={onClick}
|
||||
type="button"
|
||||
className="flex gap-2 rounded-five py-2 px-2 w-[180px] bg-mlightblue items-center justify-center">
|
||||
{isFetching ? (
|
||||
<Image src="/spin.svg" alt="spin" width={38} height={38} unoptimized />
|
||||
) : (
|
||||
<>
|
||||
<FiMoreHorizontal color="white" width={14} height={7} />
|
||||
<span className="font-roboto text-lg font-bold text-white">Ýene gör</span>
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default MoreBtn;
|
||||
|
|
@ -0,0 +1,198 @@
|
|||
'use client';
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { AiOutlineUser } from 'react-icons/ai';
|
||||
import ThemeSwitch from './home/ThemeSwitch';
|
||||
import { useContext, useEffect, useRef, useState } from 'react';
|
||||
import GlobalContext from '@/context/GlobalContext';
|
||||
import burger from '@/public/menu-outline.svg';
|
||||
|
||||
const Nav = () => {
|
||||
const path = usePathname() ?? '/';
|
||||
const { burgerOpen, setBurgerOpen } = useContext(GlobalContext).burgerContext;
|
||||
const [dropDownOpened, setDropDownOpened] = useState(false);
|
||||
// const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handleOutsideClick = (event: MouseEvent) => {
|
||||
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
|
||||
setDropDownOpened(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Attach the event listener to the document
|
||||
document.addEventListener('click', handleOutsideClick);
|
||||
|
||||
// Cleanup the event listener when the component unmounts
|
||||
return () => {
|
||||
document.removeEventListener('click', handleOutsideClick);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<nav className="nav py-1 border-b-2 border-solid border-[#D9D9D9] bg-transparent dark:bg-black transition-all dark:border-black z-50 ">
|
||||
<div className="container">
|
||||
<div className="nav-inner flex justify-between gap-4 items-center">
|
||||
<div className="flex gap-11 items-center justify-start">
|
||||
<Link href={'/'} className="logo">
|
||||
<Image
|
||||
priority
|
||||
src={'/logo.png'}
|
||||
alt="logo"
|
||||
unoptimized
|
||||
unselectable="off"
|
||||
width={60}
|
||||
height={60}
|
||||
className="w-[60px] h-[60px] object-contain"
|
||||
/>
|
||||
</Link>
|
||||
<ul className="md:flex gap-5 items-center justify-start hidden">
|
||||
<li>
|
||||
<Link
|
||||
className="block text-lg text-black transition-all font-roboto font-bold dark:text-white"
|
||||
href={'/news'}
|
||||
style={path.includes('news') ? { color: '#FFAB48' } : {}}>
|
||||
Habarlar
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href={'/treasury'}
|
||||
className="block text-lg text-black transition-all font-roboto font-bold dark:text-white"
|
||||
style={
|
||||
path.includes('treasury') || path.includes('video/') ? { color: '#FFAB48' } : {}
|
||||
}>
|
||||
Hazyna
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href={'/live'}
|
||||
className="block text-lg text-black transition-all font-roboto font-bold dark:text-white"
|
||||
style={path.includes('live') ? { color: '#FFAB48' } : {}}>
|
||||
Göni Ýaýlym
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href={'/advert'}
|
||||
className="block text-lg text-black transition-all font-roboto font-bold dark:text-white"
|
||||
style={path.includes('advert') ? { color: '#FFAB48' } : {}}>
|
||||
Mahabat
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<div
|
||||
ref={dropdownRef}
|
||||
className="flex items-center cursor-pointer relative w-full"
|
||||
onClick={() => setDropDownOpened(!dropDownOpened)}>
|
||||
<span className="block text-lg text-black transition-all font-roboto font-bold dark:text-white">
|
||||
Interaktiw
|
||||
</span>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
className={`${
|
||||
dropDownOpened ? '' : 'rotate-180'
|
||||
} transition-all ease-in duration-150`}>
|
||||
<path
|
||||
d="M7.41 15.41L12 10.83L16.59 15.41L18 14L12 8L6 14L7.41 15.41Z"
|
||||
fill="#4D4D4D"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<div
|
||||
className={`absolute top-10 left-1/2 -translate-x-1/2 bg-[#353598] flex flex-col gap-4 p-[24px] rounded-[8px] transition-all duration-300 w-[150px] ${
|
||||
dropDownOpened
|
||||
? 'opacity-100 translate-y-0 pointer-events-auto'
|
||||
: ' opacity-0 translate-y-2 pointer-events-none'
|
||||
}`}>
|
||||
<Link
|
||||
href={'/quiz/active'}
|
||||
className="block min-w-fit text-lg text-white transition-all font-roboto font-bold "
|
||||
style={path.includes('quiz/active') ? { color: '#FFAB48' } : {}}
|
||||
onClick={() => setDropDownOpened(false)}>
|
||||
Bäsleşik
|
||||
</Link>
|
||||
<Link
|
||||
href={'/vote/active'}
|
||||
className="block min-w-fit text-lg text-white transition-all font-roboto font-bold "
|
||||
style={path.includes('vote/active') ? { color: '#FFAB48' } : {}}
|
||||
onClick={() => setDropDownOpened(false)}>
|
||||
Ses bermek
|
||||
</Link>
|
||||
<Link
|
||||
target="_blank"
|
||||
href={'https://shop.turkmentv.gov.tm/'}
|
||||
className="block min-w-fit text-lg text-white transition-all font-roboto font-bold"
|
||||
onClick={() => setDropDownOpened(false)}>
|
||||
TV market
|
||||
</Link>
|
||||
<Link
|
||||
href={'/sms/sign_up'}
|
||||
className="block min-w-fit text-lg text-white transition-all font-roboto font-bold"
|
||||
onClick={() => setDropDownOpened(false)}>
|
||||
SMS ulgamy
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href={'https://turkmentv.gov.tm/mahabat/admin/user/login'}
|
||||
target="_blank"
|
||||
className="block text-lg p-2 bg-red-500 rounded-lg text-white transition-all font-roboto font-bold dark:text-white">
|
||||
Ýaýlym tertibi
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="flex gap-4 items-center">
|
||||
<ul className="md:flex gap-4 items-center hidden">
|
||||
<li>
|
||||
<Link
|
||||
href={'/contact_us'}
|
||||
style={path.includes('contact_us') ? { color: '#FFAB48' } : {}}
|
||||
className="font-roboto font-normal text-black text-lg dark:text-white transition-all">
|
||||
Habarlaşmak üçin
|
||||
</Link>
|
||||
</li>
|
||||
{/* <li>
|
||||
<span className="text-[#B2B1B1]">|</span>
|
||||
</li>
|
||||
<li>
|
||||
<AiOutlineUser
|
||||
style={{
|
||||
transitionProperty: 'all',
|
||||
transitionDuration: '150ms',
|
||||
transitionTimingFunction: 'cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
}}
|
||||
color={theme === 'dark' ? '#fff' : '#000'}
|
||||
className="w-[22px] h-[22px]"
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<span className="text-[#B2B1B1]">|</span>
|
||||
</li>
|
||||
<li>
|
||||
<ThemeSwitch />
|
||||
</li> */}
|
||||
</ul>
|
||||
<div
|
||||
className="md:hidden block relative w-[24px] h-[24px] cursor-pointer p-4"
|
||||
onClick={() => setBurgerOpen(true)}>
|
||||
<Image src={burger} fill alt="menu" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
|
||||
export default Nav;
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
interface IProps {
|
||||
title: string;
|
||||
}
|
||||
const PageTitle = ({ title }: IProps) => {
|
||||
return <h1 className="font-mw_sans font-bold text-black md:text-[32px] text-[24px]">{title}</h1>;
|
||||
};
|
||||
|
||||
export default PageTitle;
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
'use client';
|
||||
import { useState } from 'react';
|
||||
import { DatePicker } from '@mui/x-date-pickers';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
const PickDate = () => {
|
||||
const [date, setDate] = useState<dayjs.Dayjs>(dayjs('2022-05-16'));
|
||||
return (
|
||||
<div className="date-picker max-w-[500px] w-full bg-fancy-input font-roboto text-lg font-normal">
|
||||
<DatePicker
|
||||
value={date}
|
||||
onChange={(newValue) => (newValue ? setDate(newValue) : null)}
|
||||
format="MMM DD, YYYY"
|
||||
className="w-full bg-transparent"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PickDate;
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
const Premium = () => {
|
||||
return (
|
||||
<div className="premium w-24 h-8 bg-[#E20000] rounded-five text-center text-white py-1 px-3 right-2 top-2 absolute z-10">
|
||||
Premium
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Premium;
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
'use client';
|
||||
|
||||
import { FormEvent, useContext, useState } from 'react';
|
||||
|
||||
import { FiSearch } from 'react-icons/fi';
|
||||
import GlobalContext from '@/context/GlobalContext';
|
||||
import MaterialsContext from '@/context/MaterialsContext';
|
||||
|
||||
const SearchBar = () => {
|
||||
const { theme } = useContext(GlobalContext).themeContext;
|
||||
const { params, setParams } = useContext(MaterialsContext);
|
||||
|
||||
const [searchValue, setSearchValue] = useState<string>('');
|
||||
|
||||
return (
|
||||
<form
|
||||
className=" border-2 border-[rgba(139, 218, 255, 0.24)] grid grid-cols-search"
|
||||
style={{
|
||||
background:
|
||||
'linear-gradient(90.91deg, rgba(55, 171, 225, 0.07) 16.09%, rgba(55, 171, 225, 0.19) 115.87%)',
|
||||
}}
|
||||
onSubmit={(e: FormEvent) => e.preventDefault()}>
|
||||
<div>
|
||||
<input
|
||||
value={searchValue}
|
||||
onChange={(e) => {
|
||||
setParams({ ...params, search: searchValue || '' });
|
||||
setSearchValue(e.target.value);
|
||||
}}
|
||||
type="text"
|
||||
className="font-roboto py-[10px] px-5 text-[#373737] text-lg border-r border-white w-full h-full bg-transparent outline-none transition-all dark:text-white"
|
||||
placeholder="Gözlemek"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
className="flex items-center justify-center px-4 py-2"
|
||||
onClick={() => {
|
||||
setParams({ ...params, search: searchValue || '' });
|
||||
}}>
|
||||
<FiSearch
|
||||
height={20}
|
||||
width={20}
|
||||
color={theme === 'light' ? '#373737' : '#fff'}
|
||||
style={{ transition: '150ms all cubic-bezier(0.4, 0, 0.2, 1)' }}
|
||||
className="block w-[22px] h-[22px]"
|
||||
/>
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export default SearchBar;
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
'use client';
|
||||
import GlobalContext from '@/context/GlobalContext';
|
||||
import { useContext } from 'react';
|
||||
import { SlEye } from 'react-icons/sl';
|
||||
|
||||
interface IProps {
|
||||
title: string;
|
||||
views?: number;
|
||||
}
|
||||
|
||||
const SectionTitle = ({ title, views }: IProps) => {
|
||||
const { theme } = useContext(GlobalContext).themeContext;
|
||||
return (
|
||||
<div className="section-title flex flex-col gap-[18px] ">
|
||||
<h2
|
||||
className="font-aeroport font-bold transition-all dark:text-white md:text-[32px] text-[24px]"
|
||||
// style={{ fontSize: views ? '38px' : '32px' }}
|
||||
>
|
||||
{title}
|
||||
</h2>
|
||||
{views ? (
|
||||
<div className="flex gap-2 items-center">
|
||||
<SlEye
|
||||
color={theme === 'light' ? '#494949' : '#fff'}
|
||||
style={{ transition: '150ms all cubic-bezier(0.4, 0, 0.2, 1)' }}
|
||||
width={30}
|
||||
height={18}
|
||||
className="w-[30px] h-[18px]"
|
||||
/>
|
||||
<span className="font-roboto text-lg font-normal text-[#494949] transition-all dark:text-white">
|
||||
{views}
|
||||
</span>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SectionTitle;
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
import { IVideo } from '@/typings/video.type';
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
|
||||
const VideoItem = ({ img, title, id, slides }: IVideo) => {
|
||||
return (
|
||||
<Link
|
||||
href={`${slides ? '' : 'treasury/'}${id}`}
|
||||
className="video-list-item flex flex-col gap-2 lg:w-[250px] md:w-[200px] sm:w-[200px] w-[300px] ">
|
||||
{img ? (
|
||||
<>
|
||||
<div className="relative sm:h-[150px] h-[200px] w-full overflow-hidden rounded-five">
|
||||
<Image
|
||||
src={img}
|
||||
alt={title}
|
||||
unoptimized
|
||||
unselectable="off"
|
||||
className="top-0 left-0 w-full h-full object-cover z-0 absolute pointer-events-none"
|
||||
width={280}
|
||||
height={160}
|
||||
/>
|
||||
<Image
|
||||
src={'/play.svg'}
|
||||
alt={'play icon'}
|
||||
unoptimized
|
||||
unselectable="off"
|
||||
className="top-[50%] left-[50%] w-[52px] h-[52px] object-cover z-10 -translate-x-[50%] -translate-y-[50%] absolute"
|
||||
width={52}
|
||||
height={52}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
{/* {premium ? <Premium /> : null} */}
|
||||
<div className="flex gap-3 justify-start items-center">
|
||||
<div className="flex flex-col">
|
||||
<h3 className="clamped font-mw_sans font-bold text-lg text-black transition-all dark:text-white overflow-hidden w-full">
|
||||
{title}
|
||||
</h3>
|
||||
{/* <span className="font-roboto text-lg font-normal text-black transition-all dark:text-white">{`${views} Views`}</span> */}
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
export default VideoItem;
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
'use client';
|
||||
|
||||
import { Swiper, SwiperSlide } from 'swiper/react';
|
||||
|
||||
import Loader from './Loader';
|
||||
import MaterialsContext from '@/context/MaterialsContext';
|
||||
import MoreBtn from './MoreBtn';
|
||||
import { Autoplay, Navigation } from 'swiper';
|
||||
import { Queries } from '@/api/queries';
|
||||
import VideoItem from './VideoItem';
|
||||
import { useContext } from 'react';
|
||||
import { useInfiniteQuery } from '@tanstack/react-query';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
interface IProps {
|
||||
isSlides?: boolean;
|
||||
isExtendable?: boolean;
|
||||
}
|
||||
|
||||
const VideoList = ({ isSlides }: IProps) => {
|
||||
const { params } = useContext(MaterialsContext);
|
||||
const { search, ...noSearchParams } = params;
|
||||
const { data, isLoading, isFetchingNextPage, error, hasNextPage, fetchNextPage } =
|
||||
useInfiniteQuery({
|
||||
queryKey: ['videos', 'infinite', params ? params : ''],
|
||||
queryFn: ({ pageParam = 1 }) =>
|
||||
params.search !== ''
|
||||
? Queries.getVideos('?' + String(new URLSearchParams({ ...params, page: pageParam })))
|
||||
: Queries.getVideos(
|
||||
'?' + String(new URLSearchParams({ ...noSearchParams, page: pageParam })),
|
||||
),
|
||||
getNextPageParam: (prevData) =>
|
||||
prevData.meta.last_page > prevData.meta.current_page
|
||||
? prevData.meta.current_page + 1
|
||||
: null,
|
||||
keepPreviousData: true,
|
||||
});
|
||||
|
||||
if (isLoading) return <Loader height={250} />;
|
||||
if (error) return <h1>{JSON.stringify(error)}</h1>;
|
||||
|
||||
return (
|
||||
<div className="video-list flex flex-col gap-8 w-full">
|
||||
{isSlides ? (
|
||||
<div className="w-full">
|
||||
<Swiper
|
||||
autoHeight
|
||||
speed={1000}
|
||||
modules={[Navigation, Autoplay]}
|
||||
spaceBetween={10}
|
||||
slidesPerGroup={1}
|
||||
slidesPerGroupAuto
|
||||
slidesPerView={'auto'}
|
||||
navigation
|
||||
autoplay={{ delay: 3000 }}
|
||||
loop>
|
||||
{data ? (
|
||||
data.pages.flatMap((data) => data.data).length > 0 ? (
|
||||
data.pages
|
||||
.flatMap((data) => data.data)
|
||||
.map((item) => (
|
||||
<SwiperSlide key={v4()} className="w-fit">
|
||||
<VideoItem
|
||||
id={item.id}
|
||||
img={item.banner_url}
|
||||
title={item.title}
|
||||
slides={true}
|
||||
/>
|
||||
</SwiperSlide>
|
||||
))
|
||||
) : (
|
||||
<p className="text-lg text-center my-10">No news for the given filters</p>
|
||||
)
|
||||
) : null}
|
||||
</Swiper>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col gap-8">
|
||||
<div className="grid lg:grid-cols-four gap-x-2 gap-y-6 md:grid-cols-3 sm:grid-cols-2 grid-cols-1">
|
||||
{data ? (
|
||||
data.pages.flatMap((data) => data.data).length > 0 ? (
|
||||
data.pages
|
||||
.flatMap((data) => data.data)
|
||||
.map((item) => (
|
||||
<VideoItem
|
||||
id={item.id}
|
||||
img={item.banner_url}
|
||||
title={item.title}
|
||||
key={v4()}
|
||||
slides={false}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<p className="text-lg text-center my-10">No news for the given filters</p>
|
||||
)
|
||||
) : null}
|
||||
</div>
|
||||
<div className="flex justify-center">
|
||||
<MoreBtn
|
||||
onClick={() => fetchNextPage()}
|
||||
disabled={!hasNextPage}
|
||||
isFetching={isFetchingNextPage}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default VideoList;
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
'use client';
|
||||
import { Queries } from '@/api/queries';
|
||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||
import { useState, useEffect } from 'react';
|
||||
import ReactPlayer from 'react-player';
|
||||
import Loader from './Loader';
|
||||
import Image from 'next/image';
|
||||
import axios from 'axios';
|
||||
interface IProps {
|
||||
maxWidth?: string;
|
||||
maxHeight?: string;
|
||||
video_id: number;
|
||||
}
|
||||
|
||||
const VideoPlayer = ({ maxHeight, maxWidth, video_id }: IProps) => {
|
||||
const [hasWindow, setHasWindow] = useState<boolean>(false);
|
||||
const [hasStartedPlaying, setHasStartedPlaying] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
setHasWindow(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const { data, isFetching, error } = useQuery({
|
||||
queryKey: ['video', `video:${video_id}`],
|
||||
queryFn: () => Queries.getVideo(video_id),
|
||||
});
|
||||
|
||||
async function addViews() {
|
||||
return axios.post(`https://turkmentv.gov.tm/v2/api/material/${video_id}/views/increment`);
|
||||
}
|
||||
|
||||
const mutation = useMutation(() => addViews());
|
||||
|
||||
const onPlayHandler = () => {
|
||||
// Check if the video has not started playing before
|
||||
if (!hasStartedPlaying) {
|
||||
// Send a POST request to the server
|
||||
mutation.mutate();
|
||||
|
||||
// Update the state to indicate that the video has started playing
|
||||
setHasStartedPlaying(true);
|
||||
}
|
||||
};
|
||||
|
||||
if (isFetching) return <Loader height={700} />;
|
||||
if (error) return <h1>{JSON.stringify(error)}</h1>;
|
||||
|
||||
return (
|
||||
<div className="w-full h-full ">
|
||||
{hasWindow ? (
|
||||
data?.data.content_url.endsWith('.mp4') ? (
|
||||
<div className="lg:w-[700px] md:w-[550px] w-full h-[200px] sm:h-[250px] md:h-[350px] lg:h-[420px]">
|
||||
{/* <ReactPlayer
|
||||
height={'100%'}
|
||||
width={'100%'}
|
||||
controls
|
||||
playsInline
|
||||
playing={true}
|
||||
light={data?.data.banner_url}
|
||||
url={data!.data.video_stream_url}
|
||||
onStart={() => onPlayHandler()}
|
||||
/> */}
|
||||
<video
|
||||
controls
|
||||
src={data!.data.video_stream_url}
|
||||
poster={data?.data.banner_url}
|
||||
playsInline
|
||||
itemType="video/mp4"
|
||||
onPlay={() => onPlayHandler()}></video>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col gap-4 h-fit">
|
||||
<div className="relative lg:w-[700px] md:w-[550px] w-full h-[200px] sm:h-[400px] md:h-[420px]">
|
||||
{data?.data.banner_url ? (
|
||||
<Image src={data?.data.banner_url} fill alt={'image'} />
|
||||
) : null}
|
||||
</div>
|
||||
<audio controls className="w-full rounded bg-white" onLoadStart={() => onPlayHandler()}>
|
||||
<source src={data?.data.content_url} />
|
||||
</audio>
|
||||
</div>
|
||||
)
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default VideoPlayer;
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
'use client';
|
||||
import Step from './Step';
|
||||
import { v4 } from 'uuid';
|
||||
import WindowOne from './windows/WindowOne';
|
||||
import { useContext, useEffect } from 'react';
|
||||
import StepsContext from '@/context/StepsContext';
|
||||
import { AnimatePresence } from 'framer-motion';
|
||||
import WindowTwo from './windows/WindowTwo';
|
||||
import WindowThree from './windows/WindowThree';
|
||||
import Calculator from './Calculator';
|
||||
|
||||
const AdvertWindow = () => {
|
||||
const { stepContext, calculatorContext } = useContext(StepsContext);
|
||||
|
||||
const { step } = stepContext;
|
||||
|
||||
const stepNames = [
|
||||
'Mahabat ýerleşdirmek üçin 3 ädimi yzarlaň!',
|
||||
'Mahabat ýerleşdirmek üçin 3 ädimi yzarlaň!',
|
||||
'Mahabat ýerleşdirmek üçin 3 ädimi yzarlaň!',
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
calculatorContext.calculatorOpen
|
||||
? (window.document.body.style.overflowY = 'hidden')
|
||||
: (window.document.body.style.overflowY = 'auto');
|
||||
}, [calculatorContext.calculatorOpen]);
|
||||
|
||||
return (
|
||||
<div className="advert-window flex flex-col gap-14 py-14 h-full max-w-[1000px] w-full">
|
||||
<h1 className="font-mw_sans font-bold text-black text-[32px]">
|
||||
Mahabat ýerleşdirmek üçin 3 ädimi yzarlaň!
|
||||
</h1>
|
||||
<div className="bg-white rounded-[10px] border border-solid border-[#D1D1D1] shadow-form md:p-8 p-4 md:block flex items-start gap-6">
|
||||
<div
|
||||
className="steps md:grid md:grid-cols-3 md:gap-2 h-full gap-2 flex flex-col justify-between"
|
||||
// style={{
|
||||
// gridTemplateColumns: 'repeat(3, 1fr) auto',
|
||||
// }}
|
||||
>
|
||||
{stepNames.map((value, index) => (
|
||||
<div className="h-full flex flex-col items-center gap-2" key={v4()}>
|
||||
<Step last={index === 2} name={value} number={(index + 1) as 1 | 2 | 3} key={v4()} />
|
||||
{index !== 2 ? (
|
||||
index !== step ? (
|
||||
<div className="min-h-[100px] md:hidden block h-full w-1 rounded bg-[#08CB9C]"></div>
|
||||
) : (
|
||||
<div className="min-h-[100px] md:hidden block h-full w-1 rounded bg-[#b8c4b2]"></div>
|
||||
)
|
||||
) : null}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="windows overflow-hidden">
|
||||
<AnimatePresence mode="wait">
|
||||
{step === 1 && <WindowOne />}
|
||||
{step === 2 && <WindowTwo />}
|
||||
{step === 3 && <WindowThree />}
|
||||
</AnimatePresence>
|
||||
{calculatorContext.calculatorOpen ? <Calculator /> : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AdvertWindow;
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import { ButtonHTMLAttributes } from 'react';
|
||||
|
||||
interface IProps {
|
||||
onClick: () => void;
|
||||
name: string;
|
||||
type?: ButtonHTMLAttributes<HTMLButtonElement>['type'];
|
||||
disabled?: boolean;
|
||||
}
|
||||
const ButtonPrimary = ({ name, onClick, type, disabled }: IProps) => {
|
||||
return (
|
||||
<button
|
||||
disabled={disabled}
|
||||
type={type}
|
||||
onClick={onClick}
|
||||
className="text-center bg-[#37ABE1] text-white text-lg font-bold font-roboto p-3 rounded-[4px] border border-solid border-[#37ABE1] w-full">
|
||||
{name}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
export default ButtonPrimary;
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
import { ButtonHTMLAttributes } from 'react';
|
||||
|
||||
interface IProps {
|
||||
onClick: () => void;
|
||||
name: string;
|
||||
type?: ButtonHTMLAttributes<HTMLButtonElement>['type'];
|
||||
}
|
||||
|
||||
const ButtonSecondary = ({ name, onClick, type = 'button' }: IProps) => {
|
||||
return (
|
||||
<button
|
||||
type={type}
|
||||
onClick={onClick}
|
||||
className="text-center text-white text-xs font-bold font-roboto p-3 w-[150px] rounded-[4px] border border-solid border-white">
|
||||
{name}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
export default ButtonSecondary;
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
'use client';
|
||||
import CustomInput from '../CustomInput';
|
||||
import { Dispatch, FormEvent, SetStateAction, useContext, useEffect } from 'react';
|
||||
import StepsContext from '@/context/StepsContext';
|
||||
import ButtonPrimary from './ButtonPrimary';
|
||||
import { FormActionType } from '@/providers/StepsProvider';
|
||||
import Loader from '../Loader';
|
||||
import { Validator } from '@/utils/validator';
|
||||
import Image from 'next/image';
|
||||
import closeIcon from '@/public/close-white.svg';
|
||||
|
||||
const Calculator = () => {
|
||||
const { data, stepContext, calculatorContext } = useContext(StepsContext);
|
||||
const { plans } = data.plans;
|
||||
const { form, dispatch } = useContext(StepsContext).addPostContext;
|
||||
|
||||
const validateForm = () => {
|
||||
if (Validator.number(form.second) && Validator.number(form.day) && form.folder_id.length > 0)
|
||||
return true;
|
||||
else return false;
|
||||
};
|
||||
|
||||
const calculate = () => {
|
||||
const price = plans?.data.find((item) => item.id === Number(form.folder_id))?.total_price;
|
||||
if (price)
|
||||
dispatch({
|
||||
type: FormActionType.TOTAL,
|
||||
payload: String(Math.ceil(Number(price) * (Number(form.second) / 60) * Number(form.day))),
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (validateForm()) {
|
||||
calculate();
|
||||
}
|
||||
}, [form.folder_id, form.day, form.second]);
|
||||
|
||||
return (
|
||||
<div className="fixed top-0 left-0 w-screen px-5 h-screen bg-[#0000004f] flex items-center justify-center z-50">
|
||||
{plans ? (
|
||||
<form
|
||||
className="bg-[#1a1a1a] px-4 py-6 rounded-lg flex flex-col gap-8 max-w-[480px] h-fit"
|
||||
onSubmit={(e: FormEvent) => e.preventDefault()}>
|
||||
<div className="flex justify-between items-start">
|
||||
<div className="flex flex-col gap-2 font-mw_sans text-white font-[500]">
|
||||
<h3 className="text-[20px]">Calculator</h3>
|
||||
<p className="text-base">Sargyt etmek üçin aşakdaky maglumatlary dolduryň</p>
|
||||
</div>
|
||||
<div
|
||||
className="w-[24px] h-[24px] relative p-1 rounded-full flex items-center justify-center cursor-pointer"
|
||||
onClick={() => calculatorContext.setCalculatorOpen(false)}>
|
||||
<Image src={closeIcon} fill alt="close" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
<CustomInput
|
||||
value={{
|
||||
value: form.second,
|
||||
setValue: (newValue) =>
|
||||
dispatch({ type: FormActionType.SECOND, payload: newValue }),
|
||||
}}
|
||||
name="second"
|
||||
label="Näçe sekunt"
|
||||
type="text"
|
||||
isBlack
|
||||
validate={{
|
||||
errMsg: 'This field must a be number',
|
||||
isValid: Validator.number(form.second),
|
||||
}}
|
||||
/>
|
||||
<CustomInput
|
||||
value={{
|
||||
value: form.day,
|
||||
setValue: (newValue) => dispatch({ type: FormActionType.DAY, payload: newValue }),
|
||||
}}
|
||||
name="day"
|
||||
label="Näçe gün"
|
||||
type="text"
|
||||
isBlack
|
||||
validate={{
|
||||
errMsg: 'This field must a be number',
|
||||
isValid: Validator.number(form.day),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="text-white text-[22px] flex flex-col">
|
||||
<p className="font-mw_sans font-[500]">Jemi baha: </p>
|
||||
<span className="font-redhat w-fit" style={validateForm() ? { background: 'red' } : {}}>
|
||||
{form.total || 0} TMT
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex gap-3 justify-end">
|
||||
<ButtonPrimary
|
||||
disabled={!validateForm()}
|
||||
name="Indiki"
|
||||
onClick={() => {
|
||||
stepContext.setStep(3);
|
||||
calculatorContext.setCalculatorOpen(false);
|
||||
}}
|
||||
type="button"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
) : (
|
||||
<Loader height={200} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default Calculator;
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
'use client';
|
||||
|
||||
import StepsContext from '@/context/StepsContext';
|
||||
import { useContext } from 'react';
|
||||
|
||||
interface IOption {
|
||||
title: string;
|
||||
description?: string | null;
|
||||
id: number;
|
||||
activeId: number | null;
|
||||
onClick: (id: number) => void;
|
||||
}
|
||||
|
||||
const Option = ({ title, description, id, onClick, activeId }: IOption) => {
|
||||
console.log(title);
|
||||
return (
|
||||
<div
|
||||
onClick={() => onClick(id)}
|
||||
style={id === activeId ? { background: '#121268', color: 'white' } : {}}
|
||||
className="cursor-pointer overflow-hidden font-roboto leading-[22px] flex flex-col justify-center items-center gap-[12px] max-w-[400px] bg-[#E7E7E7] border-[1px] border-[#BBBBBB] rounded-[5px] px-[12px] py-[24px] hover:border-[#37ABE1] transition-all">
|
||||
<h2
|
||||
className="md:min-h-[68px] font-bold text-[22px] md:pb-[22px] pb-4 border-b-[#BBBBBB] border-b-[1px]"
|
||||
style={
|
||||
!description
|
||||
? {
|
||||
border: 'none',
|
||||
paddingBottom: 0,
|
||||
padding: '34px 0',
|
||||
minHeight: 'unset',
|
||||
textAlign: 'center',
|
||||
}
|
||||
: {}
|
||||
}>
|
||||
{title}
|
||||
</h2>
|
||||
<p
|
||||
className="text-[18px] w-full"
|
||||
style={!description ? { display: 'none' } : {}}
|
||||
dangerouslySetInnerHTML={{ __html: description || '' }}></p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Option;
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
const SelectForm = () => {
|
||||
return <div></div>;
|
||||
};
|
||||
|
||||
export default SelectForm;
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
'use client';
|
||||
|
||||
import StepsContext from '@/context/StepsContext';
|
||||
import { useCallback, useContext, useState } from 'react';
|
||||
|
||||
interface IStep {
|
||||
number: 1 | 2 | 3;
|
||||
name: string;
|
||||
last: boolean;
|
||||
}
|
||||
|
||||
const Step = ({ number, name, last }: IStep) => {
|
||||
const { stepContext } = useContext(StepsContext);
|
||||
const { step, setStep } = stepContext;
|
||||
return (
|
||||
<div
|
||||
className="step flex md:flex-row md:w-full flex-col items-center h-fit w-fit justify-start"
|
||||
style={last ? { gridTemplateColumns: '51px 130px' } : {}}>
|
||||
<div
|
||||
className="step-circle w-[45px] h-[45px] rounded-full bg-[#131369] relative transition-all flex items-center justify-center"
|
||||
style={step === number ? { background: ' #131369' } : {}}>
|
||||
<div
|
||||
className="step-tick absolute top-0 left-0 bg-step-tick w-full h-full rounded-full opacity-0 transition-all z-10"
|
||||
style={step !== number ? { opacity: 1 } : {}}></div>
|
||||
<div
|
||||
className="absolute top-[50%] left-[50%] -translate-x-[50%] -translate-y-[50%] rounded-full w-full h-full border border-solid border-[#131369] transition-all pointer-events-none"
|
||||
style={
|
||||
step === number ? { width: '51px', height: '51px', opacity: 1 } : { opacity: 0 }
|
||||
}></div>
|
||||
<span className="font-roboto font-bold flex items-center justify-center text-2xl text-center text-white transition-all w-[45px] h-[45px]">
|
||||
{number}
|
||||
</span>
|
||||
</div>
|
||||
<p
|
||||
className="step-name md:block hidden font-mw_sans font-light text-black ml-2 max-w-[130px]"
|
||||
onClick={() => setStep(number)}>
|
||||
{name}
|
||||
</p>
|
||||
{!last ? (
|
||||
<div className="step-line bg-step_line bg-repeat md:block hidden md:w-full h-full w-1 md:h-0.5 overflow-hidden">
|
||||
<div
|
||||
className="h-full z-10 w-0 bg-[#08CB9C] transition-all"
|
||||
style={step !== number ? { width: '100%' } : {}}></div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Step;
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
'use client';
|
||||
|
||||
import { FolderTable, PlansModel } from '@/models/plans.model';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
export interface IColumn {
|
||||
name: string;
|
||||
propertyId: string;
|
||||
}
|
||||
|
||||
interface IProps {
|
||||
columns: FolderTable;
|
||||
data: FolderTable[];
|
||||
plan: number;
|
||||
}
|
||||
|
||||
const columnName = {
|
||||
time: 'Wagt aralygy',
|
||||
price: 'Bahasy',
|
||||
set_tv: 'TV',
|
||||
set_aydym: 'aydym.com',
|
||||
set_radio: 'Radio',
|
||||
set_belet: 'Belet',
|
||||
set_sub: 'Subtitle',
|
||||
set_web: 'Website',
|
||||
set_outside_monitors: 'LED',
|
||||
};
|
||||
|
||||
const Table = ({ columns, data, plan }: IProps) => {
|
||||
const cols = Object.keys(columns);
|
||||
const gridCols = cols.length;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="advert-table max-w-[700px] md:w-fit w-full flex flex-col gap-8 rounded-[5px] overflow-hidden border border-solid border-gray-400 pt-6"
|
||||
style={{
|
||||
boxShadow: '0px 4.176356792449951px 3.341085195541382px 0px rgba(0, 0, 0, 0.15)',
|
||||
}}>
|
||||
<div className="table-inner px-2 pb-2 overflow-auto ">
|
||||
<div
|
||||
className="table-cols w-full grid gap-4 border-b border-solid border-black pb-2 content-center items-center"
|
||||
style={{ gridTemplateColumns: `repeat(${gridCols}, 1fr)` }}>
|
||||
{cols.map((col, id) => (
|
||||
<span
|
||||
className={`font-mw_sans text-[14px] font-bold text-black flex justify-center items-center ${
|
||||
id === 0 ? 'w-[100px]' : ''
|
||||
}`}
|
||||
key={v4()}>
|
||||
{plan == 3 && columnName[col as keyof typeof columnName] === 'Subtitle'
|
||||
? 'Flyer'
|
||||
: columnName[col as keyof typeof columnName]}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex flex-col w-full gap-2 max-h-[480px] overflow-y-auto">
|
||||
{data.map((item, index) => {
|
||||
const keys = Object.keys(item);
|
||||
return index % 2 === 0 ? (
|
||||
<div
|
||||
key={v4()}
|
||||
className="table-data grid gap-4 pt-2 border-b border-solid border-[#DAD8D8] pb-2 content-center items-center bg-[#f9f9f9]"
|
||||
style={{ gridTemplateColumns: `repeat(${gridCols}, 1fr)` }}>
|
||||
{keys.map((key, id) => (
|
||||
<span
|
||||
className={`font-mw_sans text-[14px] font-light text-black flex justify-center items-center ${
|
||||
id === 0 ? 'w-[100px]' : ''
|
||||
}`}
|
||||
key={v4()}>
|
||||
{item[key as keyof typeof item]}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
key={v4()}
|
||||
className="table-data grid gap-4 pt-2 border-b border-solid border-[#DAD8D8] pb-2 content-center items-center"
|
||||
style={{ gridTemplateColumns: `repeat(${gridCols}, 1fr)` }}>
|
||||
{keys.map((key, id) => (
|
||||
<span
|
||||
className={`font-mw_sans text-[14px] font-light text-black flex justify-center items-center ${
|
||||
id === 0 ? 'w-[100px]' : ''
|
||||
}`}
|
||||
key={v4()}>
|
||||
{item[key as keyof typeof item]}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Table;
|
||||
|
|
@ -0,0 +1,157 @@
|
|||
'use client';
|
||||
import Table from '../Table';
|
||||
import { v4 } from 'uuid';
|
||||
import PresenceAnimator from '../../hox/PresenceAnimator';
|
||||
import Option from '../Option';
|
||||
import { useContext, useEffect, useState } from 'react';
|
||||
import StepsContext from '@/context/StepsContext';
|
||||
import { AnimatePresence } from 'framer-motion';
|
||||
import { FormActionType } from '@/providers/StepsProvider';
|
||||
import { PlansModel } from '@/models/plans.model';
|
||||
import Button from '@/components/Button';
|
||||
import Link from 'next/link';
|
||||
|
||||
const Plan = () => {
|
||||
const { data, plansContext, addPostContext, calculatorContext } = useContext(StepsContext);
|
||||
const { plan } = plansContext;
|
||||
const { plans } = data.plans;
|
||||
const { dispatch, form } = addPostContext;
|
||||
const [folder, setFolder] = useState<PlansModel['data'][any]>();
|
||||
|
||||
useEffect(() => {
|
||||
if (form.folder_id !== '' && plans) {
|
||||
const newFolder = plans.data.find((item) => String(item.id) === form.folder_id);
|
||||
if (newFolder) {
|
||||
setFolder(newFolder);
|
||||
}
|
||||
}
|
||||
}, [form.folder_id, plans]);
|
||||
|
||||
// useEffect(() => {
|
||||
// calculatorOpen
|
||||
// ? (window.document.body.style.overflowY = 'hidden')
|
||||
// : (window.document.body.style.overflowY = 'auto');
|
||||
// }, [calculatorOpen]);
|
||||
|
||||
const definePlan = (plan: number | null) => {
|
||||
if (!plan) return;
|
||||
switch (plan) {
|
||||
case 1:
|
||||
return 'on_tv';
|
||||
case 2:
|
||||
return 'on_radio';
|
||||
case 3:
|
||||
return 'on_subtitle';
|
||||
case 4:
|
||||
return 'on_web';
|
||||
case 5:
|
||||
return 'on_outside_monitors';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
if (!plans) return null;
|
||||
if (!plan) return null;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-6">
|
||||
<PresenceAnimator
|
||||
initial={{ x: '100%', opacity: '0%' }}
|
||||
animate={{
|
||||
x: '0%',
|
||||
opacity: '100%',
|
||||
transition: { duration: 0.5 },
|
||||
}}
|
||||
exit={{ x: '-100%', opacity: '0%', transition: { duration: 0.5 } }}>
|
||||
<div className="flex flex-col gap-6">
|
||||
<h4 className="text-black text-[22px] font-redhat font-bold pt-6 border-t border-solid border-[#E7E7E7]">
|
||||
Mahabat görnüşini we bukjasyny saýlaň!
|
||||
</h4>
|
||||
<div className="md:grid md:grid-cols-4 flex flex-col gap-4">
|
||||
{/* {plans.data.map((item) =>
|
||||
item[definePlan(plan) as keyof (typeof plans)['data'][any]] ? (
|
||||
<Option
|
||||
activeId={Number(form.folder_id)}
|
||||
id={item.id}
|
||||
onClick={(id) =>
|
||||
dispatch({
|
||||
type: FormActionType.FOLDER,
|
||||
payload: String(id),
|
||||
})
|
||||
}
|
||||
title={item.title}
|
||||
description={item.description}
|
||||
key={v4()}
|
||||
/>
|
||||
) : null,
|
||||
)} */}
|
||||
|
||||
{plans.data.map((item) =>
|
||||
item.usage_name === definePlan(plan) ? (
|
||||
<Option
|
||||
activeId={Number(form.folder_id)}
|
||||
id={item.id}
|
||||
onClick={(id) =>
|
||||
dispatch({
|
||||
type: FormActionType.FOLDER,
|
||||
payload: String(id),
|
||||
})
|
||||
}
|
||||
title={item.title}
|
||||
description={item.description}
|
||||
key={v4()}
|
||||
/>
|
||||
) : null,
|
||||
)}
|
||||
</div>
|
||||
|
||||
{definePlan(plan) === 'on_outside_monitors' ? (
|
||||
<Link
|
||||
className="text-lg text-blue-500 font-bold underline"
|
||||
href={
|
||||
'https://www.google.com/maps/d/viewer?mid=1h-OonWMZ0EPNKYHMNvC5IPLMTERL_Gc&ll=37.94468794917295%2C58.377513099999994&z=12'
|
||||
}
|
||||
target="_blank">
|
||||
LED monitorlaryň kartasy
|
||||
</Link>
|
||||
) : null}
|
||||
|
||||
{form.folder_id !== '' && folder ? (
|
||||
<PresenceAnimator
|
||||
initial={{ x: '100%', opacity: '0%' }}
|
||||
animate={{
|
||||
x: '0%',
|
||||
opacity: '100%',
|
||||
transition: { duration: 0.5 },
|
||||
}}
|
||||
exit={{
|
||||
x: '-100%',
|
||||
opacity: '0%',
|
||||
transition: { duration: 0.5 },
|
||||
}}>
|
||||
<div className="flex flex-col gap-10">
|
||||
<div className="flex flex-col gap-6">
|
||||
<Table
|
||||
columns={folder.folder_table[0]}
|
||||
plan={plan}
|
||||
data={folder.folder_table}
|
||||
key={v4()}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
onClick={() => calculatorContext.setCalculatorOpen(true)}
|
||||
name="Indiki"
|
||||
type="button"
|
||||
disabled={calculatorContext.calculatorOpen ? true : false}
|
||||
/>
|
||||
</div>
|
||||
</PresenceAnimator>
|
||||
) : null}
|
||||
</div>
|
||||
</PresenceAnimator>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Plan;
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
'use client';
|
||||
import { PlansModel } from '@/models/plans.model';
|
||||
import Table from '../Table';
|
||||
import { v4 } from 'uuid';
|
||||
import PresenceAnimator from '../../hox/PresenceAnimator';
|
||||
import Option from '../Option';
|
||||
import { useState } from 'react';
|
||||
|
||||
interface IProps {
|
||||
plans: PlansModel;
|
||||
}
|
||||
|
||||
const PlanFour = ({ plans }: IProps) => {
|
||||
const [planItem, setPlanItem] = useState<number | null>(null);
|
||||
return (
|
||||
<div className="flex flex-col gap-6">
|
||||
<PresenceAnimator
|
||||
initial={{ x: '100%', opacity: '0%' }}
|
||||
animate={{
|
||||
x: '0%',
|
||||
opacity: '100%',
|
||||
transition: { duration: 0.5 },
|
||||
}}
|
||||
exit={{ x: '-100%', opacity: '0%', transition: { duration: 0.5 } }}>
|
||||
<h4 className="text-black text-[22px] font-roboto font-bold pt-6 border-t border-solid border-[#E7E7E7]">
|
||||
Mahabat görnüşini we bukjasyny saýlaň!
|
||||
</h4>
|
||||
<div className="grid grid-cols-4 gap-4">
|
||||
{plans.data.map((item) =>
|
||||
item.on_web ? (
|
||||
<Option
|
||||
activeId={planItem}
|
||||
id={item.id}
|
||||
onClick={(id) => setPlanItem(id)}
|
||||
title={item.title}
|
||||
description={item.description}
|
||||
key={v4()}
|
||||
/>
|
||||
) : null,
|
||||
)}
|
||||
</div>
|
||||
{/* {plans.data.map((plan) =>
|
||||
plan.on_web ? (
|
||||
<Table
|
||||
columns={plan.folder_table[0]}
|
||||
data={plan.folder_table}
|
||||
description={plan.description}
|
||||
title={plan.title}
|
||||
key={v4()}
|
||||
/>
|
||||
) : null
|
||||
)} */}
|
||||
</PresenceAnimator>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PlanFour;
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
'use client';
|
||||
import { PlansModel } from '@/models/plans.model';
|
||||
import Table from '../Table';
|
||||
import { v4 } from 'uuid';
|
||||
import PresenceAnimator from '../../hox/PresenceAnimator';
|
||||
import Option from '../Option';
|
||||
import { useState } from 'react';
|
||||
|
||||
interface IProps {
|
||||
plans: PlansModel;
|
||||
}
|
||||
|
||||
const PlanThree = ({ plans }: IProps) => {
|
||||
const [planItem, setPlanItem] = useState<number | null>(null);
|
||||
return (
|
||||
<div className="flex flex-col gap-6">
|
||||
<PresenceAnimator
|
||||
initial={{ x: '100%', opacity: '0%' }}
|
||||
animate={{
|
||||
x: '0%',
|
||||
opacity: '100%',
|
||||
transition: { duration: 0.5 },
|
||||
}}
|
||||
exit={{ x: '-100%', opacity: '0%', transition: { duration: 0.5 } }}>
|
||||
<h4 className="text-black text-[22px] font-roboto font-bold pt-6 border-t border-solid border-[#E7E7E7]">
|
||||
Mahabat görnüşini we bukjasyny saýlaň!
|
||||
</h4>
|
||||
<div className="grid grid-cols-4 gap-4">
|
||||
{plans.data.map((item) =>
|
||||
item.on_subtitle ? (
|
||||
<Option
|
||||
activeId={planItem}
|
||||
id={item.id}
|
||||
onClick={(id) => setPlanItem(id)}
|
||||
title={item.title}
|
||||
description={item.description}
|
||||
key={v4()}
|
||||
/>
|
||||
) : null,
|
||||
)}
|
||||
</div>
|
||||
{/* {plans.data.map((plan) =>
|
||||
plan.on_subtitle ? (
|
||||
<Table
|
||||
columns={plan.folder_table[0]}
|
||||
data={plan.folder_table}
|
||||
description={plan.description}
|
||||
title={plan.title}
|
||||
key={v4()}
|
||||
/>
|
||||
) : null
|
||||
)} */}
|
||||
</PresenceAnimator>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PlanThree;
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
'use client';
|
||||
import { PlansModel } from '@/models/plans.model';
|
||||
import Table from '../Table';
|
||||
import { v4 } from 'uuid';
|
||||
import PresenceAnimator from '../../hox/PresenceAnimator';
|
||||
import Option from '../Option';
|
||||
import { useState } from 'react';
|
||||
|
||||
interface IProps {
|
||||
plans: PlansModel;
|
||||
}
|
||||
|
||||
const PlanTwo = ({ plans }: IProps) => {
|
||||
const [planItem, setPlanItem] = useState<number | null>(null);
|
||||
return (
|
||||
<div className="flex flex-col gap-6">
|
||||
<PresenceAnimator
|
||||
initial={{ x: '100%', opacity: '0%' }}
|
||||
animate={{
|
||||
x: '0%',
|
||||
opacity: '100%',
|
||||
transition: { duration: 0.5 },
|
||||
}}
|
||||
exit={{ x: '-100%', opacity: '0%', transition: { duration: 0.5 } }}>
|
||||
<h4 className="text-black text-[22px] font-roboto font-bold pt-6 border-t border-solid border-[#E7E7E7]">
|
||||
Mahabat görnüşini we bukjasyny saýlaň!
|
||||
</h4>
|
||||
<div className="grid grid-cols-4 gap-4">
|
||||
{plans.data.map((item) =>
|
||||
item.on_radio ? (
|
||||
<Option
|
||||
activeId={planItem}
|
||||
id={item.id}
|
||||
onClick={(id) => setPlanItem(id)}
|
||||
title={item.title}
|
||||
description={item.description}
|
||||
key={v4()}
|
||||
/>
|
||||
) : null,
|
||||
)}
|
||||
</div>
|
||||
{/* {plans.data.map((plan) =>
|
||||
plan.on_radio ? (
|
||||
<Table
|
||||
columns={plan.folder_table[0]}
|
||||
data={plan.folder_table}
|
||||
description={plan.description}
|
||||
title={plan.title}
|
||||
key={v4()}
|
||||
/>
|
||||
) : null
|
||||
)} */}
|
||||
</PresenceAnimator>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PlanTwo;
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
'use client';
|
||||
import { Queries } from '@/api/queries';
|
||||
import Loader from '@/components/Loader';
|
||||
import { useContext, useEffect } from 'react';
|
||||
import Option from '../Option';
|
||||
import { v4 } from 'uuid';
|
||||
import Button from '@/components/Button';
|
||||
import StepsContext from '@/context/StepsContext';
|
||||
import PresenceAnimator from '@/components/hox/PresenceAnimator';
|
||||
|
||||
const WindowOne = () => {
|
||||
const ctx = useContext(StepsContext);
|
||||
const { stepContext, propertyContext, data } = ctx;
|
||||
const { properties } = data;
|
||||
|
||||
useEffect(() => {
|
||||
Queries.getProperties().then((data) => properties.setProperties(data));
|
||||
}, []);
|
||||
|
||||
if (!properties.properties) return <Loader height={200} />;
|
||||
return (
|
||||
<PresenceAnimator
|
||||
initial={{ x: '100%', opacity: '0%' }}
|
||||
animate={{ x: '0%', opacity: '100%', transition: { duration: 0.5 } }}
|
||||
exit={{ x: '-100%', opacity: '0%', transition: { duration: 0.5 } }}>
|
||||
<div className="flex flex-col gap-10">
|
||||
<div className="flex flex-col gap-[24px] mt-[36px]">
|
||||
<h2 className="font-mw_sans font-bold md:text-[22px] text-lg leading-[24px]">
|
||||
Eýeçiligiň görnüşini saýlaň!
|
||||
</h2>
|
||||
<div className="flex md:flex-row flex-col gap-[16px]">
|
||||
{properties.properties.data.map((option) => (
|
||||
<Option
|
||||
onClick={(id) => propertyContext.setProperty(id)}
|
||||
activeId={propertyContext.property}
|
||||
id={option.id}
|
||||
title={option.title}
|
||||
description={option.description}
|
||||
key={v4()}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
onClick={() => stepContext.setStep(2)}
|
||||
name="Indiki"
|
||||
type="button"
|
||||
disabled={!propertyContext.property ? true : false}
|
||||
/>
|
||||
</div>
|
||||
</PresenceAnimator>
|
||||
);
|
||||
};
|
||||
|
||||
export default WindowOne;
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
'use client';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import PresenceAnimator from '@/components/hox/PresenceAnimator';
|
||||
import CustomInput from '@/components/CustomInput';
|
||||
import { FormEvent, useContext, useState } from 'react';
|
||||
import StepsContext from '@/context/StepsContext';
|
||||
import { FormActionType } from '@/providers/StepsProvider';
|
||||
import { Queries } from '@/api/queries';
|
||||
import Button from '@/components/Button';
|
||||
import Loader from '@/components/Loader';
|
||||
import { Validator } from '@/utils/validator';
|
||||
|
||||
const WindowThree = () => {
|
||||
const { form, dispatch } = useContext(StepsContext).addPostContext;
|
||||
const [loading, setLoading] = useState<boolean | 'error'>(false);
|
||||
const router = useRouter();
|
||||
|
||||
const validateForm = () => {
|
||||
if (
|
||||
Validator.email(form.customer_email) &&
|
||||
Validator.isNotEmpty(form.customer_name) &&
|
||||
Validator.phone(form.customer_phone)
|
||||
)
|
||||
return true;
|
||||
else return false;
|
||||
};
|
||||
|
||||
if (loading == true) return <Loader height={400} />;
|
||||
|
||||
return (
|
||||
<>
|
||||
<PresenceAnimator
|
||||
initial={{ x: '100%', opacity: '0%' }}
|
||||
animate={{ x: '0%', opacity: '100%', transition: { duration: 0.5 } }}
|
||||
exit={{ x: '-100%', opacity: '0%', transition: { duration: 0.5 } }}>
|
||||
<div className="flex flex-col gap-10">
|
||||
<form
|
||||
className="flex flex-col gap-6"
|
||||
onSubmit={async (e: FormEvent) => {
|
||||
e.preventDefault();
|
||||
await Queries.postAdvert(form);
|
||||
}}>
|
||||
<h2 className="font-mw_sans font-[500] leading-[100%] md:text-[22px] text-lg text-black">
|
||||
Bukjany sargyt etmek üçin aşakdaky maglumatlary dolduryň
|
||||
</h2>
|
||||
<CustomInput
|
||||
value={{
|
||||
value: form.customer_name,
|
||||
setValue: (newValue) => dispatch({ type: FormActionType.NAME, payload: newValue }),
|
||||
}}
|
||||
name="name"
|
||||
label="Ady"
|
||||
placeholder="Sargyt edijiniň ady ýa-da familiýasy"
|
||||
type="text"
|
||||
validate={{
|
||||
errMsg: 'Name is required',
|
||||
isValid: Validator.isNotEmpty(form.customer_name),
|
||||
}}
|
||||
/>
|
||||
<CustomInput
|
||||
value={{
|
||||
value: form.customer_phone,
|
||||
setValue: (newValue) => dispatch({ type: FormActionType.PHONE, payload: newValue }),
|
||||
}}
|
||||
name="phone"
|
||||
label="Telefon"
|
||||
placeholder="+993 61234567"
|
||||
type="text"
|
||||
validate={{
|
||||
errMsg: 'Invalid phone number',
|
||||
isValid: Validator.phone(form.customer_phone),
|
||||
}}
|
||||
/>
|
||||
<CustomInput
|
||||
value={{
|
||||
value: form.customer_phone,
|
||||
setValue: (newValue) => dispatch({ type: FormActionType.EMAIL, payload: newValue }),
|
||||
}}
|
||||
name="email"
|
||||
label="E-mail"
|
||||
placeholder="meselem@gmail.com"
|
||||
type="email"
|
||||
validate={{
|
||||
errMsg: 'Invalid email address',
|
||||
isValid: Validator.email(form.customer_email),
|
||||
}}
|
||||
/>
|
||||
<CustomInput
|
||||
value={{
|
||||
value: form.customer_notes,
|
||||
setValue: (newValue) => dispatch({ type: FormActionType.NOTES, payload: newValue }),
|
||||
}}
|
||||
isTextArea
|
||||
name="notes"
|
||||
label="Goşmaça bellikler"
|
||||
placeholder="Mahabat etmak isleäýän zadyňyzy barada şu ýerik ýazyň..."
|
||||
type="text"
|
||||
/>
|
||||
<p className="text-lg font-mw_sans leading-[100%] text-mlightblue">
|
||||
Jikme-jiklikleri bize{' '}
|
||||
<a href="mailto:mahabat@turkmen.gov.tm" className="underline">
|
||||
mahabat@turkmen.gov.tm
|
||||
</a>{' '}
|
||||
adrese iberiň
|
||||
</p>
|
||||
</form>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setLoading(true);
|
||||
Queries.postAdvert(form)
|
||||
.then((res) => {
|
||||
if (res.status === 201) {
|
||||
router.push('/success');
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
router.push('/failed');
|
||||
});
|
||||
}}
|
||||
name="Indiki"
|
||||
type="button"
|
||||
disabled={!validateForm()}
|
||||
/>
|
||||
</div>
|
||||
</PresenceAnimator>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default WindowThree;
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
'use client';
|
||||
|
||||
import { Queries } from '@/api/queries';
|
||||
import Loader from '@/components/Loader';
|
||||
import { useContext, useEffect, useState } from 'react';
|
||||
import Option from '../Option';
|
||||
import { v4 } from 'uuid';
|
||||
import Button from '@/components/Button';
|
||||
import StepsContext from '@/context/StepsContext';
|
||||
import PresenceAnimator from '@/components/hox/PresenceAnimator';
|
||||
import { PlansModel } from '@/models/plans.model';
|
||||
// import PlanOne from "../plans/PlanOne";
|
||||
// import PlanTwo from "../plans/PlanTwo";
|
||||
// import PlanThree from "../plans/PlanThree";
|
||||
// import PlanFour from "../plans/PlanFour";
|
||||
import { AnimatePresence } from 'framer-motion';
|
||||
import Plan from '../plans/Plan';
|
||||
|
||||
const WindowTwo = () => {
|
||||
const planTypes = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'TV',
|
||||
property: 'on_tv',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Radio',
|
||||
property: 'on_radio',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'Air ticket flyer',
|
||||
property: 'on_sub',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: 'Website',
|
||||
property: 'on_web',
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: 'Outdoor LED',
|
||||
property: 'on_outside_monitors',
|
||||
},
|
||||
];
|
||||
const ctx = useContext(StepsContext);
|
||||
const { stepContext, propertyContext, data } = ctx;
|
||||
const { plans } = data;
|
||||
const { plan, setPlan } = useContext(StepsContext).plansContext;
|
||||
|
||||
useEffect(() => {
|
||||
Queries.getPlans(propertyContext.property!).then((data) => plans.setPlans(data));
|
||||
}, [propertyContext.property]);
|
||||
|
||||
useEffect(() => {}, []);
|
||||
|
||||
// console.log('Plans', plans);
|
||||
// console.log('Plan', plan);
|
||||
|
||||
if (!plans.plans) return <Loader height={200} />;
|
||||
return (
|
||||
<PresenceAnimator
|
||||
initial={{ x: '100%', opacity: '0%' }}
|
||||
animate={{
|
||||
x: '0%',
|
||||
opacity: '100%',
|
||||
transition: { duration: 0.5, delay: 0.6 },
|
||||
}}
|
||||
exit={{ x: '-100%', opacity: '0%', transition: { duration: 0.5 } }}>
|
||||
<div className="flex flex-col gap-10">
|
||||
<div className="flex flex-col gap-[24px] mt-[36px]">
|
||||
<h2 className="font-mw_sans font-bold md:text-[22px] text-lg leading-[24px]">
|
||||
Mahabat görnüşini we bukjasyny saýlaň!
|
||||
</h2>
|
||||
<div className="md:grid md:grid-cols-5 flex flex-col gap-[8px]">
|
||||
{planTypes.map((option) => (
|
||||
<Option
|
||||
onClick={(id) => setPlan(id)}
|
||||
activeId={plan}
|
||||
id={option.id}
|
||||
title={option.name}
|
||||
key={v4()}
|
||||
/>
|
||||
// <Option
|
||||
// onClick={() => setPlan(option.id)}
|
||||
// activeId={plan}
|
||||
// id={option.id}
|
||||
// title={option.name}
|
||||
// key={v4()}
|
||||
// />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<Plan />
|
||||
</div>
|
||||
</PresenceAnimator>
|
||||
);
|
||||
};
|
||||
|
||||
export default WindowTwo;
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
'use client';
|
||||
import { AiTwotoneEyeInvisible } from 'react-icons/ai';
|
||||
import { AiTwotoneEye } from 'react-icons/ai';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { IAuthInput } from '@/typings/auth-input.type';
|
||||
|
||||
const AuthInput = ({ id, placeholder, type, required, label }: IAuthInput) => {
|
||||
const [isVisible, setIsVisible] = useState<boolean>(false);
|
||||
return (
|
||||
<div className="auth-input relative w-full">
|
||||
{label ? <label htmlFor={id}>{label}</label> : null}
|
||||
<input
|
||||
type={type === 'password' ? (isVisible ? 'text' : type) : type}
|
||||
required={required}
|
||||
name={id}
|
||||
id={id}
|
||||
placeholder={placeholder}
|
||||
className="text-[#474747] font-mw_sans font-normal text-base bg-[#F5F5F5] rounded-[5px] p-4 w-full outline-none"
|
||||
/>
|
||||
{type === 'password' ? (
|
||||
<>
|
||||
<AiTwotoneEye
|
||||
color="#474747"
|
||||
width={20}
|
||||
height={20}
|
||||
className="w-5 h-5 absolute right-5 top-[50%] -translate-y-[50%] transition-all opacity-100 cursor-pointer"
|
||||
style={
|
||||
isVisible
|
||||
? { opacity: 1, pointerEvents: 'all' }
|
||||
: { opacity: 0, pointerEvents: 'none' }
|
||||
}
|
||||
onClick={() => setIsVisible(!isVisible)}
|
||||
/>
|
||||
<AiTwotoneEyeInvisible
|
||||
color="#474747"
|
||||
width={20}
|
||||
height={20}
|
||||
className="w-5 h-5 absolute right-5 top-[50%] -translate-y-[50%] transition-all opacity-100 cursor-pointer"
|
||||
style={
|
||||
!isVisible
|
||||
? { opacity: 1, pointerEvents: 'all' }
|
||||
: { opacity: 0, pointerEvents: 'none' }
|
||||
}
|
||||
onClick={() => setIsVisible(!isVisible)}
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AuthInput;
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { FormEvent } from 'react';
|
||||
import AuthInput from './AuthInput';
|
||||
import Button from '../Button';
|
||||
const LoginForm = () => {
|
||||
return (
|
||||
<div className="py-10">
|
||||
<div className="login max-w-[455px] w-full h-fit rounded-[15px] shadow-form bg-white py-12 px-11 flex flex-col gap-10">
|
||||
<h1 className="text-[32px] font-lighter text-black font-mw_sans text-center uppercase">
|
||||
Hasabyňyza giriň
|
||||
</h1>
|
||||
<form onSubmit={(e: FormEvent) => e.preventDefault()} className="flex flex-col gap-4">
|
||||
<AuthInput id="tel_mail" placeholder="Telefon ýa-da Email" type="text" required />
|
||||
<AuthInput id="password" placeholder="Açar söz" type="password" required />
|
||||
<Button name="Hasap aç" type="submit" width="100%" onClick={() => null} />
|
||||
</form>
|
||||
|
||||
<Link
|
||||
href={'/auth/recovery'}
|
||||
className="font-roboto text-base text-black flex justify-center">
|
||||
<span className="border-b-2 border-black border-solid">Açar sözi unytdym!</span>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoginForm;
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { FormEvent } from 'react';
|
||||
import AuthInput from './AuthInput';
|
||||
import Button from '../Button';
|
||||
const LoginForm = () => {
|
||||
return (
|
||||
<div className="py-10">
|
||||
<div className="login max-w-[455px] w-full h-fit rounded-[15px] shadow-form bg-white py-12 px-11 flex flex-col gap-10">
|
||||
<h1 className="text-[32px] font-lighter text-black font-mw_sans text-center uppercase">
|
||||
Hasabyňyza giriň
|
||||
</h1>
|
||||
<form onSubmit={(e: FormEvent) => e.preventDefault()} className="flex flex-col gap-4">
|
||||
<AuthInput id="email" placeholder="Emailiňizi ýazyň" type="email" required />
|
||||
<Button name="Hasap aç" type="submit" width="100%" onClick={() => null} />
|
||||
</form>
|
||||
|
||||
<Link
|
||||
href={'/auth/signup'}
|
||||
className="font-roboto text-base text-black flex justify-center">
|
||||
<span className="border-b-2 border-black border-solid">Saýtda ilk gezek girýäňizmi?</span>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoginForm;
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { FormEvent } from 'react';
|
||||
import AuthInput from './AuthInput';
|
||||
import Button from '../Button';
|
||||
const LoginForm = () => {
|
||||
return (
|
||||
<div className="py-10">
|
||||
<div className="login max-w-[455px] w-full h-fit rounded-[15px] shadow-form bg-white py-12 px-11 flex flex-col gap-10">
|
||||
<h1 className="text-[32px] font-lighter text-black font-mw_sans text-center uppercase">
|
||||
Hasabyňyza giriň
|
||||
</h1>
|
||||
<form onSubmit={(e: FormEvent) => e.preventDefault()} className="flex flex-col gap-4">
|
||||
<AuthInput id="name" placeholder="Ady" type="text" required />
|
||||
<AuthInput id="lastname" placeholder="Familiýasy" type="text" required />
|
||||
<AuthInput id="email" placeholder="Email" type="email" required />
|
||||
<AuthInput id="phone" placeholder="Telefon" type="text" required />
|
||||
<AuthInput id="password" placeholder="Açar söz" type="password" required />
|
||||
<AuthInput
|
||||
id="password_confirmation"
|
||||
placeholder="Açar söz gaýtala"
|
||||
type="password"
|
||||
required
|
||||
/>
|
||||
<Button name="Ýazyl" type="submit" width="100%" onClick={() => null} />
|
||||
</form>
|
||||
|
||||
<Link href={'/auth/login'} className="font-roboto text-base text-black flex justify-center">
|
||||
<span className="border-b-2 border-black border-solid">Hasabyňyz barmy? Içeri gir.</span>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoginForm;
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
'use client';
|
||||
import Button from '../Button';
|
||||
|
||||
interface Props {
|
||||
folder: number;
|
||||
days: number;
|
||||
price: number;
|
||||
}
|
||||
|
||||
const TarifWindow = ({ folder, days, price }: Props) => {
|
||||
return (
|
||||
<div className="min-w-[394px] pb-[16px] pt-[56px] px-[16px] flex flex-col items-center bg-white text-black gap-[62px] rounded-2xl">
|
||||
<div className="flex items-center flex-col gap-[32px] pb-[22px] border-b border-b-[#CAD2DA]">
|
||||
<h3 className="text-3xl">{`BUKJA ${folder}`}</h3>
|
||||
<h2 className="text-[#FFAB48] font-bold text-4xl">{`${days} GÜN - ${price} TMT`}</h2>
|
||||
</div>
|
||||
<Button name="Satyn al" type="button" onClick={() => null} width="100%" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TarifWindow;
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
'use client';
|
||||
import CustomInput from '@/components/CustomInput';
|
||||
import Button from '../Button';
|
||||
import { FormEvent, useState } from 'react';
|
||||
|
||||
const ContactForm = () => {
|
||||
const [input, setInput] = useState({
|
||||
name: '',
|
||||
email: '',
|
||||
message: '',
|
||||
topic: '',
|
||||
});
|
||||
return (
|
||||
<form
|
||||
className="custom-input bg-white md:py-12 py-6 md:px-3 px-6 rounded-[10px] border border-solid transition-all border-[#D1D1D1] flex flex-col md:gap-9 gap-6 shadow-form dark:border-0 dark:shadow-none dark:bg-dark"
|
||||
onSubmit={(e: FormEvent) => e.preventDefault()}>
|
||||
<CustomInput
|
||||
value={{
|
||||
value: input.name,
|
||||
setValue: (value) => setInput({ ...input, name: value }),
|
||||
}}
|
||||
name="name"
|
||||
label="Ady"
|
||||
placeholder="Aman Amanow"
|
||||
type="text"
|
||||
/>
|
||||
<CustomInput
|
||||
value={{
|
||||
value: input.email,
|
||||
setValue: (value) => setInput({ ...input, email: value }),
|
||||
}}
|
||||
name="email"
|
||||
label="E-mail"
|
||||
placeholder="meselem@gmail.com"
|
||||
type="email"
|
||||
/>
|
||||
<CustomInput
|
||||
value={{
|
||||
value: input.message,
|
||||
setValue: (value) => setInput({ ...input, message: value }),
|
||||
}}
|
||||
name="message"
|
||||
label="Hat"
|
||||
placeholder="Hat"
|
||||
isTextArea
|
||||
/>
|
||||
<CustomInput
|
||||
value={{
|
||||
value: input.topic,
|
||||
setValue: (value) => setInput({ ...input, topic: value }),
|
||||
}}
|
||||
name="topic"
|
||||
label="Mowzuk"
|
||||
placeholder="Mowzuk"
|
||||
type="text"
|
||||
/>
|
||||
{/* <CustomInput name="img" label="Surat" type="file" accept="image/*" /> */}
|
||||
<Button name="Ugrat" onClick={() => console.log('click')} type="submit" />
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export default ContactForm;
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
import ContactDetails from '../ContactDetails';
|
||||
import Map from '../Map';
|
||||
|
||||
const ContactMap = () => {
|
||||
return (
|
||||
<div className="contact-map flex md:flex-row flex-col gap-9 border-t border-t-[#BBBBBB] pt-11 mt-14 dark:border-dark transition-all">
|
||||
<ContactDetails />
|
||||
<Map />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ContactMap;
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
'use client';
|
||||
|
||||
import { useContext, useEffect, useState } from 'react';
|
||||
import { FitlerNumber } from '@/components/table/FitlerNumber';
|
||||
import { Search } from '@/components/table/Search';
|
||||
import { Sort } from '@/components/table/Sort';
|
||||
import { Inter } from 'next/font/google';
|
||||
import { AuthContext } from '@/context/AuthContext';
|
||||
import ProtectedRoute from '@/components/sms/ProtectedRoute';
|
||||
import { setDate } from 'date-fns';
|
||||
import { Calendar } from 'lucide-react';
|
||||
import { SmsContext } from '@/context/SmsContext';
|
||||
import SmsTable from '@/components/sms/smsTable/SmsTable';
|
||||
import Loader from '@/components/Loader';
|
||||
import GradientTitle from '@/components/vote/GradientTitle';
|
||||
import FilterTable from './FilterTable';
|
||||
// import FilterTable from './FilterTable';
|
||||
|
||||
const inter = Inter({
|
||||
weight: ['400', '600', '700', '500'],
|
||||
subsets: ['cyrillic', 'latin'],
|
||||
});
|
||||
|
||||
const Dashboard = () => {
|
||||
const authContext = useContext(AuthContext);
|
||||
|
||||
if (!authContext) {
|
||||
throw new Error('AuthContext must be used within an AuthProvider');
|
||||
}
|
||||
|
||||
const { logout, userIsLoading } = authContext;
|
||||
|
||||
const smsContext = useContext(SmsContext);
|
||||
if (!smsContext) {
|
||||
throw new Error('smsContext must be used within an AuthProvider');
|
||||
}
|
||||
const { setCurrentPage, isError, smsTableData, currentPage } = smsContext;
|
||||
|
||||
useEffect(() => {
|
||||
window.scrollTo(0, 0);
|
||||
}, [smsTableData, currentPage]);
|
||||
|
||||
if (isError) {
|
||||
return (
|
||||
<main>
|
||||
<div className="container flex flex-col justify-center items-center h-screen">
|
||||
<h1
|
||||
className={`text-[44px] sm:text-[80px] leading-[100%] font-bold bg-fancyTitle bg-clip-text text-transparent text-center `}>
|
||||
Gysga belgi birikdirilmedi
|
||||
</h1>
|
||||
<button
|
||||
onClick={() => {
|
||||
logout();
|
||||
setCurrentPage(1);
|
||||
}}
|
||||
type="submit"
|
||||
className="p-3 bg-[#7A7ACC] text-[18px] leading-[150%] font-medium text-white w-[200px] rounded-xl mt-[30px]">
|
||||
Yza
|
||||
</button>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
if (userIsLoading) {
|
||||
return (
|
||||
<main>
|
||||
<div className="container flex justify-center items-center h-screen">
|
||||
<Loader />
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ProtectedRoute>
|
||||
<div className={`pt-[100px] pb-[200px] ${inter.className}`}>
|
||||
<div className="container">
|
||||
<div className="flex gap-[40px]">
|
||||
<div className="flex flex-col gap-[32px]">
|
||||
<FitlerNumber />
|
||||
<span
|
||||
onClick={() => {
|
||||
logout();
|
||||
setCurrentPage(1);
|
||||
}}
|
||||
className="text-textLight text-[16px] leading-[140%] font-semibold cursor-pointer w-full py-2">
|
||||
Ulgamdan çykmak
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col w-full gap-5">
|
||||
<FilterTable />
|
||||
<SmsTable />
|
||||
</div>
|
||||
{/* <div className="flex flex-col gap-5 w-full">
|
||||
<div className="flex gap-8 items-end justify-end w-full">
|
||||
<Sort />
|
||||
<Search />
|
||||
</div>
|
||||
<Calendar
|
||||
mode="single"
|
||||
// selected={date}
|
||||
// onSelect={setDate}
|
||||
className="rounded-md border"
|
||||
/>
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ProtectedRoute>
|
||||
);
|
||||
};
|
||||
|
||||
export default Dashboard;
|
||||
|
|
@ -0,0 +1,214 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { MouseEvent, useContext, useEffect, useRef, useState } from 'react';
|
||||
import { Calendar } from '../ui/calendar';
|
||||
import { format } from 'date-fns';
|
||||
import { SmsContext } from '@/context/SmsContext';
|
||||
import Image from 'next/image';
|
||||
import { useOnClickOutside } from 'usehooks-ts';
|
||||
import { SlSocialGithub } from 'react-icons/sl';
|
||||
|
||||
export const sortData = [
|
||||
{
|
||||
title: 'Newest',
|
||||
id: 'desc',
|
||||
},
|
||||
{
|
||||
title: 'Oldest',
|
||||
id: 'asc',
|
||||
},
|
||||
];
|
||||
|
||||
const FilterTable = () => {
|
||||
const smsContext = useContext(SmsContext);
|
||||
if (!smsContext) {
|
||||
throw new Error('smsContext must be used within an AuthProvider');
|
||||
}
|
||||
|
||||
const {
|
||||
datee,
|
||||
setDatee,
|
||||
setActiveSort,
|
||||
activeSort,
|
||||
searchValue,
|
||||
setSearchValue,
|
||||
setSearchFecth,
|
||||
searchFecth,
|
||||
timeDate,
|
||||
setTimeDate,
|
||||
smsTableData,
|
||||
} = smsContext;
|
||||
|
||||
const calendarRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const [calendar, setCalendar] = useState(false);
|
||||
|
||||
const [filterActive, setFilterActive] = useState(false);
|
||||
|
||||
const handleClickOutside = () => {
|
||||
setCalendar(false);
|
||||
};
|
||||
|
||||
useOnClickOutside(calendarRef, handleClickOutside);
|
||||
|
||||
const checkSortActive = () => {
|
||||
if (activeSort === 'asc' || datee || searchFecth) {
|
||||
setFilterActive(true);
|
||||
} else {
|
||||
setFilterActive(false);
|
||||
}
|
||||
};
|
||||
|
||||
const resetFilters = () => {
|
||||
setActiveSort('desc');
|
||||
setDatee(undefined);
|
||||
setSearchFecth('');
|
||||
setSearchValue('');
|
||||
};
|
||||
const resetSearch = () => {
|
||||
setSearchFecth('');
|
||||
setSearchValue('');
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
checkSortActive();
|
||||
}, [activeSort, datee, searchFecth]);
|
||||
|
||||
return (
|
||||
<div className="">
|
||||
<div className="flex gap-6 w-full justify-between items-start">
|
||||
<div className="flex flex-col gap-[16px]">
|
||||
<div className="bg-[#F0F0FA] rounded-full flex gap-6 shadow-tableShadow">
|
||||
<div className="flex items-center pr-[24px] w-fit gap-[24px]">
|
||||
<div
|
||||
className={clsx(
|
||||
'leading-[115%] text-[14px] py-[16px] px-[24px] bg-[#E1E1F5] text-black font-semibold rounded-l-full',
|
||||
{},
|
||||
)}>
|
||||
Filtr
|
||||
</div>
|
||||
{sortData.map((item, i) => (
|
||||
<div
|
||||
key={i}
|
||||
onClick={() => {
|
||||
setActiveSort(item.id);
|
||||
}}
|
||||
className={clsx('leading-[115%] text-[14px] cursor-pointer', {
|
||||
'text-fillButtonAccentDefault font-[600]': activeSort === item.id,
|
||||
'text-textDarkt': activeSort !== item.id,
|
||||
})}>
|
||||
{item.title}
|
||||
</div>
|
||||
))}
|
||||
|
||||
<div className="relative">
|
||||
<div
|
||||
onClick={() => setCalendar((prev) => !prev)}
|
||||
className={clsx(
|
||||
'leading-[115%] text-[14px] text-textDarkt cursor-pointer w-fit ',
|
||||
{},
|
||||
)}>
|
||||
Kalendar
|
||||
</div>
|
||||
<AnimatePresence>
|
||||
{calendar && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -20, pointerEvents: 'none' }}
|
||||
animate={{ opacity: 1, y: 0, pointerEvents: 'all' }}
|
||||
exit={{ opacity: 0, y: -20, pointerEvents: 'none' }}
|
||||
transition={{ ease: 'easeInOut' }}
|
||||
ref={calendarRef}
|
||||
className="absolute top-5">
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={datee}
|
||||
onSelect={setDatee}
|
||||
initialFocus
|
||||
onDayClick={() => setCalendar(false)}
|
||||
className={clsx(
|
||||
'my-20 bg-[#F5F5FA] w-fit rounded-[8px] -translate-y-[50px] -translate-x-[60px] shadow-[0_2px_32px_rgba(0,0,0,0.3)] transition-all',
|
||||
)}
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{datee && smsTableData?.data.length !== 0 && (
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Sagat boýunça gözle... 18:1"
|
||||
onChange={(e) => setTimeDate(e.target.value)}
|
||||
value={timeDate}
|
||||
className="w-full outline-none px-6 py-3 shadow-tableShadow bg-[#F0F0FA] rounded-full"
|
||||
/>
|
||||
)}
|
||||
|
||||
{filterActive ? (
|
||||
<div
|
||||
className="cursor-pointer h-fit flex items-center gap-[8px] stroke-textGray hover:stroke-fillNavyBlue text-textDarkt hover:text-fillNavyBlue"
|
||||
onClick={resetFilters}>
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="transition-all ease-out duration-[0.3s]">
|
||||
<path
|
||||
d="M16 16L12 12M12 12L8 8M12 12L16 8M12 12L8 16"
|
||||
stroke="current"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
)
|
||||
</svg>
|
||||
<h1 className=" text-[14px] font-medium w-fit">Filteri aýyr</h1>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div className="flex shadow-tableShadow rounded-full w-fit">
|
||||
<div className="flex items-center justify-between bg-[#F0F0FA] w-fit py-3 px-4 rounded-[9999px_0_0_9999px] ">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Gözle..."
|
||||
onChange={(e) => setSearchValue(e.target.value)}
|
||||
value={searchValue}
|
||||
className="w-full bg-transparent outline-none"
|
||||
/>
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="cursor-pointer stroke-textGray hover:stroke-fillNavyBlue transition-all ease-out duration-[0.3s]"
|
||||
onClick={resetSearch}>
|
||||
{searchFecth ? (
|
||||
<path
|
||||
d="M16 16L12 12M12 12L8 8M12 12L16 8M12 12L8 16"
|
||||
stroke="current"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
) : null}
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
onClick={() => setSearchFecth(searchValue)}
|
||||
className="bg-fillButtonAccentDefault rounded-[0_9999px_9999px_0] px-4 py-[12px] cursor-pointer">
|
||||
<Image src={'/search.svg'} alt="search" width={24} height={24} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FilterTable;
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import Link from 'next/link';
|
||||
|
||||
interface IProps {
|
||||
href: string;
|
||||
color: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
const LinkBlock = ({ color, href, content }: IProps) => {
|
||||
return (
|
||||
<Link
|
||||
href={href}
|
||||
className="flex items-center justify-center p-4 w-full lg:h-full h-[200px] font-aeroport font-bold md:text-[34px] text-[26px] text-white text-center"
|
||||
style={{ backgroundColor: color }}>
|
||||
{content}
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
export default LinkBlock;
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
'use client';
|
||||
import Image from 'next/image';
|
||||
import { Swiper, SwiperSlide } from 'swiper/react';
|
||||
import { Autoplay, Navigation, Pagination } from 'swiper';
|
||||
import { Queries } from '@/api/queries';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import Loader from '../Loader';
|
||||
import { v4 } from 'uuid';
|
||||
import Link from 'next/link';
|
||||
|
||||
const MainSwiper = () => {
|
||||
const { data, isFetching, error } = useQuery({
|
||||
queryKey: ['home'],
|
||||
queryFn: () => Queries.getHome(),
|
||||
});
|
||||
|
||||
if (isFetching) return <Loader height={'100%'} />;
|
||||
if (error) return <h1>{JSON.stringify(error)}</h1>;
|
||||
|
||||
return (
|
||||
<div className="big-swiper ">
|
||||
<Swiper
|
||||
modules={[Navigation, Pagination, Autoplay]}
|
||||
slidesPerView={1}
|
||||
navigation
|
||||
autoplay={{ delay: 3200 }}
|
||||
speed={700}
|
||||
loop
|
||||
pagination={{ clickable: true }}
|
||||
className="h-full w-full">
|
||||
{data!.data.map((item) => (
|
||||
<SwiperSlide key={v4()} className="w-full h-full">
|
||||
<Link
|
||||
href={item.url ? item.url : ''}
|
||||
className="flex justify-center items-center relative w-full lg:h-full h-[400px]">
|
||||
<Image
|
||||
src={item.image}
|
||||
alt={item.title}
|
||||
fill
|
||||
unoptimized
|
||||
unselectable="off"
|
||||
className="pointer-events-none "
|
||||
/>
|
||||
</Link>
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MainSwiper;
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
'use client';
|
||||
|
||||
import { Queries } from '@/api/queries';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import Marquee from 'react-fast-marquee';
|
||||
import Loader from '../Loader';
|
||||
import { v4 } from 'uuid';
|
||||
import channels from '@/channels';
|
||||
|
||||
const Marque = () => {
|
||||
const { data, isFetching, error } = useQuery({
|
||||
queryKey: ['marquee'],
|
||||
queryFn: () => Queries.getMarquee(),
|
||||
});
|
||||
|
||||
if (isFetching) return <Loader height={'100%'} />;
|
||||
if (error) return <h1>{JSON.stringify(error)}</h1>;
|
||||
|
||||
return data?.data ? (
|
||||
<div className="flex w-full items-center z-50 lg:fixed lg:bottom-0 lg:left-0">
|
||||
{data.data.length === 0 ? null : (
|
||||
<div className="relative w-full flex">
|
||||
<div className="w-fit absolute top-[50%] translate-y-[-50%] left-0 bg-mred font-roboto text-white font-bold text-base z-20">
|
||||
<span className=" py-1 px-4 block">Gysga Habarlar</span>
|
||||
</div>
|
||||
<Marquee
|
||||
direction="left"
|
||||
loop={0}
|
||||
speed={30}
|
||||
pauseOnHover
|
||||
className="w-full flex gap-4 bg-[#BBBB] z-10 lg:my-[11px]"
|
||||
gradient={false}>
|
||||
{data?.data.map((item) => (
|
||||
<div className="mr-10 py-1" key={v4()}>
|
||||
{item.content}
|
||||
</div>
|
||||
))}
|
||||
</Marquee>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : null;
|
||||
};
|
||||
|
||||
export default Marque;
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
'use client';
|
||||
import Image from 'next/image';
|
||||
import { Swiper, SwiperSlide } from 'swiper/react';
|
||||
import { Navigation, Pagination, Scrollbar, A11y, Autoplay } from 'swiper';
|
||||
import { v4 } from 'uuid';
|
||||
import Link from 'next/link';
|
||||
import { Queries } from '@/api/queries';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import Loader from '../Loader';
|
||||
|
||||
interface IProps {
|
||||
sliderNumber: number;
|
||||
}
|
||||
|
||||
const SmallSwiperAdvert = ({ sliderNumber }: IProps) => {
|
||||
const { data, isFetching, error } = useQuery({
|
||||
queryKey: [sliderNumber === 3 ? 'small_slider3' : sliderNumber === 4 ? 'small_slider4' : ''],
|
||||
queryFn: () =>
|
||||
sliderNumber === 3
|
||||
? Queries.getSmallSlider3()
|
||||
: sliderNumber === 4
|
||||
? Queries.getSmallSlider4()
|
||||
: null,
|
||||
});
|
||||
|
||||
if (isFetching) return <Loader height={'100%'} />;
|
||||
if (error) return <h1>{JSON.stringify(error)}</h1>;
|
||||
|
||||
return (
|
||||
<div className="small-swiper overflow-hidden">
|
||||
<Swiper
|
||||
className="h-full overflow-hidden"
|
||||
modules={[Navigation, Pagination, Scrollbar, A11y, Autoplay]}
|
||||
slidesPerView={1}
|
||||
spaceBetween={0}
|
||||
navigation
|
||||
autoplay={{ delay: 4200 }}
|
||||
speed={700}
|
||||
loop
|
||||
pagination={{
|
||||
clickable: true,
|
||||
}}>
|
||||
{/* PAY ATTENTION [data] is wrapped in an array */}
|
||||
{data?.data.map((item) => (
|
||||
<SwiperSlide key={v4()} className="overflow-hidden">
|
||||
{item.url || item.page_id ? (
|
||||
<Link
|
||||
// href={item.url ? item.url : `advertisment_page/${item.page_id}`}
|
||||
href={item.url ? item.url : item.page_id ? `/${item.page_id}` : ''}
|
||||
className="overflow-hidden w-full lg:h-full h-[200px] flex">
|
||||
<Image
|
||||
src={item.image}
|
||||
alt="small_banner"
|
||||
fill
|
||||
unoptimized
|
||||
unselectable="off"
|
||||
className={` pointer-events-none overflow-hidden`}
|
||||
/>
|
||||
</Link>
|
||||
) : (
|
||||
<div className="overflow-hidden w-full h-full">
|
||||
<Image
|
||||
src={item.image}
|
||||
alt="small_banner"
|
||||
fill
|
||||
unoptimized
|
||||
unselectable="off"
|
||||
className={` pointer-events-none overflow-hidden`}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SmallSwiperAdvert;
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
'use client';
|
||||
import Image from 'next/image';
|
||||
import { Swiper, SwiperSlide } from 'swiper/react';
|
||||
import { Navigation, Pagination, Scrollbar, A11y, Autoplay } from 'swiper';
|
||||
import { v4 } from 'uuid';
|
||||
import Link from 'next/link';
|
||||
import { HomeModel } from '@/models/home.model';
|
||||
import { Queries } from '@/api/queries';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import Loader from '../Loader';
|
||||
import LinkBlock from './LinkBlock';
|
||||
|
||||
const SmallSwiperNews = () => {
|
||||
const { data, isFetching, error } = useQuery({
|
||||
queryKey: ['small_slider_news'],
|
||||
queryFn: () => Queries.getlastNews(),
|
||||
});
|
||||
|
||||
if (isFetching) return <Loader height={'100%'} />;
|
||||
if (error) return <h1>{JSON.stringify(error)}</h1>;
|
||||
|
||||
return (
|
||||
<div className="small-swiper flex-1">
|
||||
<Swiper
|
||||
className="h-full"
|
||||
modules={[Navigation, Pagination, Scrollbar, A11y, Autoplay]}
|
||||
slidesPerView={1}
|
||||
spaceBetween={0}
|
||||
navigation
|
||||
autoplay={{ delay: 4100 }}
|
||||
speed={700}
|
||||
loop
|
||||
pagination={{
|
||||
clickable: true,
|
||||
}}>
|
||||
{/* PAY ATTENTION [data] is wrapped in an array */}
|
||||
<SwiperSlide>
|
||||
<LinkBlock href="/news" content="Habarlar" color="#a554f0" />
|
||||
</SwiperSlide>
|
||||
{data?.data.map((item, index) => (
|
||||
<SwiperSlide key={v4()} className="">
|
||||
<Link href={item ? `news/${item.id}` : ''} className="relative ">
|
||||
<div className="relative w-full h-full">
|
||||
<Image
|
||||
src={item.featured_images[0].path}
|
||||
alt="small_banner"
|
||||
fill
|
||||
unoptimized
|
||||
unselectable="off"
|
||||
className={`w-full object-fill pointer-events-none h-full absolute top-0 left-0`}
|
||||
/>
|
||||
<div className="w-full h-full flex justify-start items-end p-4 pb-8 absolute top-0 left-0 z-30">
|
||||
<h2 className="text-lg text-white font-bold">{item.title}</h2>
|
||||
</div>
|
||||
<div className="w-full h-full z-20 absolute top-0 left-0 bg-black opacity-40"></div>
|
||||
</div>
|
||||
</Link>
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SmallSwiperNews;
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
'use client';
|
||||
import Image from 'next/image';
|
||||
import { Swiper, SwiperSlide } from 'swiper/react';
|
||||
import { Navigation, Pagination, Scrollbar, A11y, Autoplay } from 'swiper';
|
||||
import { v4 } from 'uuid';
|
||||
import Link from 'next/link';
|
||||
import { Queries } from '@/api/queries';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import Loader from '../Loader';
|
||||
import LinkBlock from './LinkBlock';
|
||||
import { VideoItemModel } from '@/models/videoItem.model';
|
||||
|
||||
const SmallSwiperVideos = () => {
|
||||
const { data, isFetching, error } = useQuery({
|
||||
queryKey: ['small_slider_videos'],
|
||||
queryFn: () => Queries.getLastVideos(),
|
||||
});
|
||||
|
||||
if (isFetching) return <Loader height={'100%'} />;
|
||||
if (error) return <h1>{JSON.stringify(error)}</h1>;
|
||||
|
||||
function chooseRandomItems(inputArray: VideoItemModel[]) {
|
||||
// Check if the input array has at least 5 items
|
||||
if (inputArray.length < 5) {
|
||||
throw new Error('Input array must have at least 5 items');
|
||||
}
|
||||
|
||||
// Use the Fisher-Yates (Knuth) shuffle algorithm to shuffle the array
|
||||
for (let i = inputArray.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[inputArray[i], inputArray[j]] = [inputArray[j], inputArray[i]];
|
||||
}
|
||||
|
||||
// Return the first 5 items from the shuffled array
|
||||
return inputArray.slice(0, 5);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="small-swiper ">
|
||||
<Swiper
|
||||
className="h-full"
|
||||
modules={[Navigation, Pagination, Scrollbar, A11y, Autoplay]}
|
||||
slidesPerView={1}
|
||||
spaceBetween={0}
|
||||
navigation
|
||||
autoplay={{ delay: 4000 }}
|
||||
speed={700}
|
||||
loop
|
||||
pagination={{
|
||||
clickable: true,
|
||||
}}>
|
||||
{/* PAY ATTENTION [data] is wrapped in an array */}
|
||||
<SwiperSlide>
|
||||
<LinkBlock href="/treasury" content="Hazyna" color="#c56540" />
|
||||
</SwiperSlide>
|
||||
{data?.data
|
||||
? chooseRandomItems(data.data).map((item, index) => (
|
||||
<SwiperSlide key={v4()}>
|
||||
<Link href={item ? `treasury/${item.id}` : ''}>
|
||||
{item.banner_url ? (
|
||||
<div className="relative w-full h-full">
|
||||
<Image
|
||||
src={item.banner_url}
|
||||
alt="small_banner"
|
||||
fill
|
||||
unoptimized
|
||||
unselectable="off"
|
||||
className={`w-full object-fill pointer-events-none h-full absolute top-0 left-0`}
|
||||
/>
|
||||
<div className="w-full h-full flex justify-start items-end p-4 pb-8 absolute top-0 left-0 z-30">
|
||||
<h2 className="text-lg text-white font-bold">{item.title}</h2>
|
||||
</div>
|
||||
<div className="w-full h-full z-20 absolute top-0 left-0 bg-black opacity-40"></div>
|
||||
</div>
|
||||
) : null}
|
||||
</Link>
|
||||
</SwiperSlide>
|
||||
))
|
||||
: null}
|
||||
</Swiper>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SmallSwiperVideos;
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
import SmallSwiper from './SmallSwiperVideos';
|
||||
import LinkBlock from './LinkBlock';
|
||||
import { HomeModel } from '@/models/home.model';
|
||||
import { Queries } from '@/api/queries';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import Loader from '../Loader';
|
||||
import SmallSwiperNews from './SmallSwiperNews';
|
||||
import SmallSwiperVideos from './SmallSwiperVideos';
|
||||
import SmallSwiperAdvert from './SmallSwiperAdvert';
|
||||
|
||||
const SmallSwipers = () => {
|
||||
return (
|
||||
<div className="grid grid-cols-2 lg:grid-rows-full3 lg:h-home h-max overflow-hidden">
|
||||
<LinkBlock href="/advert" content="Mahabat" color="#5461F0" />
|
||||
<LinkBlock href="/live" content="Göni Ýaýlym" color="#EB4765" />
|
||||
{/* Is not yet in API, static for now */}
|
||||
<SmallSwiperNews />
|
||||
<SmallSwiperVideos />
|
||||
<SmallSwiperAdvert sliderNumber={3} />
|
||||
<SmallSwiperAdvert sliderNumber={4} />
|
||||
|
||||
{/* <SmallSwiper data={data} /> */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SmallSwipers;
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
'use client';
|
||||
|
||||
import GlobalContext from '@/context/GlobalContext';
|
||||
import { useContext } from 'react';
|
||||
import { RiContrastLine } from 'react-icons/ri';
|
||||
|
||||
const ThemeSwitch = () => {
|
||||
const { theme, setTheme } = useContext(GlobalContext).themeContext;
|
||||
return (
|
||||
<div className="theme-switch flex items-center gap-2">
|
||||
<span
|
||||
className="font-roboto font-normal text-lg transition-all text-[#D9D9D9] cursor-pointer"
|
||||
onClick={() => setTheme('light')}
|
||||
style={{ color: theme === 'light' ? '#121268' : '#D9D9D9' }}>
|
||||
Light
|
||||
</span>
|
||||
<RiContrastLine
|
||||
color={theme === 'dark' ? '#37ABE1' : '#121268'}
|
||||
width={23}
|
||||
height={23}
|
||||
className="rotate-180 w-[23px] h-[23px] transition-all"
|
||||
/>
|
||||
<span
|
||||
className="font-roboto font-normal text-lg transition-all text-[#D9D9D9] cursor-pointer"
|
||||
onClick={() => setTheme('dark')}
|
||||
style={{ color: theme === 'dark' ? '#37ABE1' : '#D9D9D9' }}>
|
||||
Dark
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ThemeSwitch;
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
"use client";
|
||||
import Link from "next/link";
|
||||
import { FiSearch } from "react-icons/fi";
|
||||
import { AiOutlineUser } from "react-icons/ai";
|
||||
|
||||
const Toolbar = () => {
|
||||
return (
|
||||
<div className="bg-[#E2E2E2] rounded-[20px] py-[10px] px-3 flex items-center gap-5 fixed top-5 right-5 w-32 justify-center">
|
||||
<Link href={"/"}>
|
||||
<FiSearch
|
||||
height={20}
|
||||
width={20}
|
||||
color="#121268"
|
||||
className="block w-5 h-5"
|
||||
/>
|
||||
</Link>
|
||||
<Link href={"/"}>
|
||||
<AiOutlineUser
|
||||
height={20}
|
||||
width={20}
|
||||
color="#121268"
|
||||
className="block w-[22px] h-[22px]"
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Toolbar;
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
"use client";
|
||||
import { PropsWithChildren } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import {
|
||||
VariantLabels,
|
||||
AnimationControls,
|
||||
TargetAndTransition,
|
||||
} from "framer-motion";
|
||||
|
||||
interface IProps extends PropsWithChildren {
|
||||
initial?: any;
|
||||
animate?: VariantLabels | AnimationControls | TargetAndTransition | undefined;
|
||||
exit?: VariantLabels | TargetAndTransition | undefined;
|
||||
}
|
||||
|
||||
const PresenceAnimator = ({ children, initial, animate, exit }: IProps) => {
|
||||
return (
|
||||
<motion.div
|
||||
className="flex flex-col justify-between min-h-[500px]"
|
||||
initial={initial}
|
||||
animate={animate}
|
||||
exit={exit}
|
||||
>
|
||||
{children}
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PresenceAnimator;
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
import React from 'react';
|
||||
import Image from 'next/image';
|
||||
import { Queries } from '@/api/queries';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import Loader from '../Loader';
|
||||
import Link from 'next/link';
|
||||
import { Swiper, SwiperSlide } from 'swiper/react';
|
||||
import { Autoplay, Navigation, Pagination } from 'swiper';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
const Banner = () => {
|
||||
const { data, isFetching, error } = useQuery({
|
||||
queryKey: ['channel_description'],
|
||||
queryFn: () => Queries.getBanner(),
|
||||
});
|
||||
|
||||
// if (isFetching) return <Loader height={'100%'} />;
|
||||
if (error) return <h1>{JSON.stringify(error)}</h1>;
|
||||
|
||||
return (
|
||||
<aside className="live-aside max-w-[200px] w-full lg:block hidden relative">
|
||||
{data?.data ? (
|
||||
<Swiper
|
||||
modules={[Pagination, Autoplay]}
|
||||
slidesPerView={1}
|
||||
autoplay={{ delay: 3200 }}
|
||||
speed={700}
|
||||
loop
|
||||
pagination={{ clickable: true }}
|
||||
className="w-full">
|
||||
{data!.data.map((item) => (
|
||||
<SwiperSlide key={v4()} className="w-[200px] h-[400px]">
|
||||
<Link href={item.url} className="w-full h-full" target="_blank">
|
||||
<Image src={item.img_url} unoptimized alt={item.alt} fill priority className="" />
|
||||
</Link>
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
) : null}
|
||||
</aside>
|
||||
);
|
||||
};
|
||||
|
||||
export default Banner;
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
"use client";
|
||||
import { Queries } from "@/api/queries";
|
||||
import { useInfiniteQuery } from "@tanstack/react-query";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import Loader from "../Loader";
|
||||
import baseUrl from "@/baseUrl";
|
||||
|
||||
const MainNews = () => {
|
||||
const { data, isLoading, error } = useInfiniteQuery({
|
||||
queryKey: ["news", "infinite"],
|
||||
queryFn: ({ pageParam = 1 }) => Queries.getNews(pageParam, {}),
|
||||
getNextPageParam: (prevData) =>
|
||||
prevData.meta.last_page > prevData.meta.current_page
|
||||
? prevData.meta.current_page + 1
|
||||
: null,
|
||||
});
|
||||
|
||||
const news = data!.pages.flatMap((data) => data.data)[0];
|
||||
|
||||
if (isLoading) return <Loader />;
|
||||
if (error) return <h1>{JSON.stringify(error)}</h1>;
|
||||
|
||||
return (
|
||||
<Link href={`/news/${news.id}`} className="main-news flex flex-col gap-6">
|
||||
<div className="w-full max-h-[250px] h-full">
|
||||
<Image
|
||||
src={news.featured_images[0].path}
|
||||
alt={news.title}
|
||||
unoptimized
|
||||
unselectable="off"
|
||||
width={1000}
|
||||
height={250}
|
||||
className="w-full object-cover h-[250px]"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-3 text-black text-lg">
|
||||
<h2 className="font-mw_sans font-bold">{news.title}</h2>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
export default MainNews;
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
import baseUrl from '@/baseUrl';
|
||||
import { NewsModel } from '@/models/news.model';
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
|
||||
interface IProps {
|
||||
news: NewsModel['data'][0];
|
||||
}
|
||||
|
||||
const News = ({ news }: IProps) => {
|
||||
return (
|
||||
<Link
|
||||
href={`/news/${news.id}`}
|
||||
className="news flex flex-col gap-2 h-fit lg:max-w-[350px] md:max-w-[300px] sm:max-w-[200px] max-w-[300px] w-full">
|
||||
<div className=" w-full md:h-[200px] sm:h-[150px] h-[200px] rounded-five overflow-hidden relative">
|
||||
{news.featured_images && news.featured_images[0].path ? (
|
||||
<Image
|
||||
src={news.featured_images[0].path}
|
||||
alt={news.slug ? news.slug : news.title}
|
||||
unoptimized
|
||||
unselectable="off"
|
||||
fill
|
||||
className="w-full object-cover h-full"
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
<p className="font-roboto text-sm text-black font-normal clamped">{news.published_at}</p>
|
||||
<p className="font-roboto text-lg leading-6 text-black font-bold clamped">{news.title}</p>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
export default News;
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
'use client';
|
||||
import { v4 } from 'uuid';
|
||||
import News from './News';
|
||||
import MoreBtn from '../MoreBtn';
|
||||
import SectionTitle from '../SectionTitle';
|
||||
import { Queries } from '@/api/queries';
|
||||
import { useInfiniteQuery } from '@tanstack/react-query';
|
||||
import NewsSlider from './NewsSlider';
|
||||
import Loader from '../Loader';
|
||||
|
||||
interface IProps {
|
||||
title?: string;
|
||||
isExtendable?: boolean;
|
||||
isSlides?: boolean;
|
||||
perPage?: number;
|
||||
}
|
||||
const NewsGrid = ({ title, isExtendable, isSlides, perPage = 8 }: IProps) => {
|
||||
const { data, isLoading, isFetchingNextPage, error, hasNextPage, fetchNextPage } =
|
||||
useInfiniteQuery({
|
||||
queryKey: ['news', 'infinite'],
|
||||
queryFn: ({ pageParam = 1 }) => Queries.getNews(pageParam, { perPage }),
|
||||
getNextPageParam: (prevData) =>
|
||||
prevData.meta.last_page > prevData.meta.current_page
|
||||
? prevData.meta.current_page + 1
|
||||
: null,
|
||||
keepPreviousData: true,
|
||||
});
|
||||
// const { data, isLoading, isFetchingNextPage, error, hasNextPage, fetchNextPage } =
|
||||
// useInfiniteQuery({
|
||||
// queryKey: ['news', 'infinite'],
|
||||
// queryFn: ({ pageParam = 1 }) => Queries.getNews(pageParam, { perPage }),
|
||||
|
||||
// keepPreviousData: true,
|
||||
// });
|
||||
|
||||
if (isLoading) return <Loader />;
|
||||
if (error) return <h1>{JSON.stringify(error)}</h1>;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-8">
|
||||
{title ? <SectionTitle title={title} /> : null}
|
||||
{isSlides ? (
|
||||
<NewsSlider data={data} />
|
||||
) : (
|
||||
<div className="news-grid sm:grid gap-[10px] sm:grid-rows-1 sm:items-start lg:grid-cols-4 sm:grid-cols-3 flex flex-col items-center content-center sm gap-y-6">
|
||||
{data!.pages
|
||||
.flatMap((data) => data.data)
|
||||
.map((novelty) => (
|
||||
<News news={novelty} key={v4()} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isExtendable ? (
|
||||
<div className="flex justify-center">
|
||||
<MoreBtn
|
||||
onClick={() => fetchNextPage()}
|
||||
disabled={!hasNextPage}
|
||||
isFetching={isFetchingNextPage}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default NewsGrid;
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
'use client';
|
||||
import { Queries } from '@/api/queries';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import Image from 'next/image';
|
||||
import Loader from '../Loader';
|
||||
import baseUrl from '@/baseUrl';
|
||||
import PageTitle from '../PageTitle';
|
||||
import { NextSeo } from 'next-seo';
|
||||
|
||||
interface IProps {
|
||||
id: string;
|
||||
}
|
||||
|
||||
const Item = ({ id }: IProps) => {
|
||||
const { data, error, isFetching } = useQuery({
|
||||
queryKey: ['news_item', id],
|
||||
queryFn: () => Queries.getNewsItem(id),
|
||||
});
|
||||
|
||||
if (isFetching) return <Loader />;
|
||||
if (error) return <h1>{JSON.stringify(error)}</h1>;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-8">
|
||||
<NextSeo title={data!.data.title} description={data!.data.excerpt} />
|
||||
<div className="flex flex-col gap-2">
|
||||
<PageTitle title={data!.data.title} />
|
||||
<p className="text-lg">{data?.data.published_at}</p>
|
||||
</div>
|
||||
|
||||
<div className="main-news flex flex-col gap-6">
|
||||
<div className="w-full lg:h-[600px] md:h-[400px] h-[250px] relative">
|
||||
{data?.data.featured_images && data.data.featured_images[0].path ? (
|
||||
<Image
|
||||
src={data!.data.featured_images[0].path}
|
||||
alt={data!.data.slug ? data!.data.slug : data!.data.title}
|
||||
unoptimized
|
||||
unselectable="off"
|
||||
fill
|
||||
priority
|
||||
className="w-full object-contain h-[600px]"
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="flex flex-col gap-3 text-black text-lg">
|
||||
{/* <h2 className="font-mw_sans font-bold">{data!.data.title}</h2> */}
|
||||
<p
|
||||
className="font-roboto font-normal flex flex-col gap-4"
|
||||
dangerouslySetInnerHTML={{ __html: data!.data.content_html }}></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Item;
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
'use client';
|
||||
import { Swiper, SwiperSlide } from 'swiper/react';
|
||||
import { Navigation, Autoplay } from 'swiper';
|
||||
import { InfiniteData } from '@tanstack/react-query';
|
||||
import { NewsModel } from '@/models/news.model';
|
||||
import { v4 } from 'uuid';
|
||||
import News from './News';
|
||||
|
||||
interface IProps {
|
||||
data: InfiniteData<NewsModel> | undefined;
|
||||
}
|
||||
|
||||
const NewsSlider = ({ data }: IProps) => {
|
||||
return (
|
||||
<div className="big-swiper w-full">
|
||||
<Swiper
|
||||
speed={1000}
|
||||
modules={[Navigation, Autoplay]}
|
||||
spaceBetween={10}
|
||||
slidesPerGroup={1}
|
||||
slidesPerGroupAuto
|
||||
slidesPerView={'auto'}
|
||||
navigation
|
||||
autoplay={{ delay: 3000 }}
|
||||
loop>
|
||||
{data!.pages
|
||||
.flatMap((data) => data.data)
|
||||
.map((novelty) => (
|
||||
<SwiperSlide key={v4()} className="w-fit">
|
||||
<News news={novelty} key={v4()} />
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default NewsSlider;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue