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