From 26d56477826483ad8780c3c8073fb2eeaabdbabf Mon Sep 17 00:00:00 2001 From: Kakabay <2kakabayashyrberdyew@gmail.com> Date: Mon, 19 Aug 2024 17:44:56 +0500 Subject: [PATCH] reinit --- .gitignore | 37 + api/queries.ts | 222 + app/(main)/[page_id]/page.tsx | 92 + app/(main)/about_us/page.tsx | 68 + app/(main)/advert/page.tsx | 18 + app/(main)/auth/layout.tsx | 15 + app/(main)/auth/login/page.tsx | 11 + app/(main)/auth/recovery/page.tsx | 11 + app/(main)/auth/signup/page.tsx | 11 + app/(main)/auth/tarif/page.tsx | 12 + app/(main)/contact_us/page.tsx | 17 + app/(main)/layout.tsx | 30 + app/(main)/live/page.tsx | 105 + app/(main)/news/343/page.tsx | 86 + app/(main)/news/[slug]/page.tsx | 53 + app/(main)/news/page.tsx | 29 + app/(main)/quiz/[quiz_id]/page.tsx | 160 + app/(main)/quiz/active/page.tsx | 120 + app/(main)/sms/dashboard/page.tsx | 12 + app/(main)/sms/layout.tsx | 15 + app/(main)/sms/sign_up/page.tsx | 81 + app/(main)/test/page.tsx | 64 + app/(main)/treasury/[video_id]/page.tsx | 73 + app/(main)/treasury/layout.tsx | 15 + app/(main)/treasury/page.tsx | 66 + app/(main)/vote/[vote_id]/page.tsx | 25 + app/(main)/vote/active/page.tsx | 19 + app/failed/page.tsx | 34 + app/globals.css | 209 + app/layout.tsx | 71 + app/page.tsx | 32 + app/success/page.tsx | 36 + baseUrl.ts | 22 + channels.ts | 63 + components.json | 17 + components/Aside.tsx | 83 + components/AsideAdd.tsx | 19 + components/Buble.tsx | 10 + components/Button.tsx | 24 + components/Categories.tsx | 29 + components/Category.tsx | 46 + components/ContactDetails.tsx | 12 + components/CustomInput.tsx | 80 + components/CustomSelect.tsx | 90 + components/Footer.tsx | 120 + components/Icon.tsx | 24 + components/InfoBlock.tsx | 187 + components/Loader.tsx | 23 + components/Map.tsx | 21 + components/MobileMenu.tsx | 192 + components/MoreBtn.tsx | 29 + components/Nav.tsx | 198 + components/PageTitle.tsx | 8 + components/PickDate.tsx | 20 + components/Premium.tsx | 9 + components/SearchBar.tsx | 52 + components/SectionTitle.tsx | 39 + components/VideoItem.tsx | 48 + components/VideoList.tsx | 111 + components/VideoPlayer.tsx | 90 + components/advert/AdvertWindow.tsx | 67 + components/advert/ButtonPrimary.tsx | 20 + components/advert/ButtonSecondary.tsx | 19 + components/advert/Calculator.tsx | 110 + components/advert/Option.tsx | 44 + components/advert/SelectForm.tsx | 5 + components/advert/Step.tsx | 50 + components/advert/Table.tsx | 96 + components/advert/plans/Plan.tsx | 157 + components/advert/plans/PlanFour.tsx | 58 + components/advert/plans/PlanThree.tsx | 58 + components/advert/plans/PlanTwo.tsx | 58 + components/advert/windows/WindowOne.tsx | 55 + components/advert/windows/WindowThree.tsx | 130 + components/advert/windows/WindowTwo.tsx | 101 + components/auth/AuthInput.tsx | 53 + components/auth/LoginForm.tsx | 30 + components/auth/RecoveryForm.tsx | 29 + components/auth/SignUpForm.tsx | 37 + components/auth/TarifWindow.tsx | 22 + components/contact_us/ContactForm.tsx | 63 + components/contact_us/ContactMap.tsx | 13 + components/dashboard/Dashboard.tsx | 115 + components/dashboard/FilterTable.tsx | 214 + components/home/LinkBlock.tsx | 20 + components/home/MainSwiper.tsx | 52 + components/home/Marque.tsx | 45 + components/home/SmallSwiperAdvert.tsx | 79 + components/home/SmallSwiperNews.tsx | 65 + components/home/SmallSwiperVideos.tsx | 85 + components/home/SmallSwipers.tsx | 27 + components/home/ThemeSwitch.tsx | 33 + components/home/Toolbar.tsx | 29 + components/hox/PresenceAnimator.tsx | 29 + components/live/Banner.tsx | 44 + components/news/MainNews.tsx | 44 + components/news/News.tsx | 33 + components/news/NewsGrid.tsx | 67 + components/news/NewsItem.tsx | 56 + components/news/NewsSlider.tsx | 38 + components/quiz/QuizAccordion.tsx | 247 + components/quiz/QuizQuestion.tsx | 177 + components/quiz/QuizQuestionList.tsx | 140 + components/quiz/QuizSearch.tsx | 122 + components/quiz/QuizTable.tsx | 77 + components/quiz/QuizWinnerTable.tsx | 298 + components/shared/SharedButton.tsx | 11 + components/shop/ShopTable.tsx | 97 + components/sms/ProtectedRoute.tsx | 34 + components/sms/smsTable/SmsPagination.tsx | 183 + components/sms/smsTable/SmsTable.tsx | 87 + components/sms/smsTable/SmsTableBody.tsx | 35 + components/sms/smsTable/SmsTableHead.tsx | 22 + components/sms/smsTable/SmsTableRow.tsx | 47 + components/table/FitlerNumber.tsx | 92 + components/table/Search.tsx | 26 + components/table/Sort.tsx | 31 + components/ui/button.tsx | 56 + components/ui/calendar.tsx | 59 + components/ui/pagination.tsx | 93 + components/ui/popover.tsx | 31 + components/updatedAuth/Input.tsx | 78 + components/vote/Countdown.tsx | 81 + components/vote/GradientTitle.tsx | 20 + components/vote/PageBage.tsx | 14 + components/vote/ParticipantCard.tsx | 173 + components/vote/ParticipantsList.tsx | 196 + config/tailwind.config.js | 110 + config/tailwind.config.ts | 80 + context/AuthContext.tsx | 157 + context/GlobalContext.ts | 7 + context/MaterialsContext.ts | 9 + context/QuizContext.ts | 7 + context/SmsContext.tsx | 148 + context/StepsContext.ts | 7 + context/VoteContext.ts | 7 + fonts/Aeroport.otf | Bin 0 -> 167184 bytes lib/utils.ts | 6 + models/add.post.model.ts | 10 + models/allVotes.model.ts | 25 + models/banner.model.ts | 12 + models/categories.model.ts | 30 + models/channels.model.ts | 9 + models/home.model.ts | 16 + models/liveDescription.model.ts | 16 + models/marquee.model.ts | 16 + models/news.model.ts | 73 + models/newsItem.model.ts | 37 + models/pageItem.model.ts | 11 + models/plans.model.ts | 39 + models/properties.model.ts | 12 + models/quizQuestionHistory.model.ts | 11 + models/quizQuestions.model.ts | 30 + models/quizQuestionsWinners.model.ts | 24 + models/quizSearchData.model.ts | 17 + models/sms/messagesByTvAdmis.model.ts | 36 + models/sms/my.tv.admins.model.ts | 8 + models/video.model.ts | 21 + models/videoItem.model.ts | 13 + models/videos.model.ts | 43 + models/vote.model.ts | 25 + next.config.js | 28 + package-lock.json | 10652 ++++++++++++++++++++ package.json | 55 + postcss.config.js | 6 + providers/MainProvider.tsx | 56 + providers/MaterialsProvider.tsx | 22 + providers/QueryProvider.tsx | 19 + providers/QuizProvider.tsx | 38 + providers/StepsProvider.tsx | 147 + providers/VoteProvider.tsx | 19 + public/App Store Button Black.svg | 7 + public/Google Play Button Black.svg | 47 + public/arrow-left-big.svg | 17 + public/arrow-left-small.svg | 17 + public/arrow-left.svg | 3 + public/arrow-right-big.svg | 17 + public/arrow-right-small.svg | 17 + public/aside-add.jpg | Bin 0 -> 142008 bytes public/aydym-com.webp | Bin 0 -> 15126 bytes public/belet.jpg | Bin 0 -> 64790 bytes public/chevron-up.svg | 5 + public/close-outline.svg | 1 + public/close-white.svg | 4 + public/close.svg | 1 + public/error-icon.svg | 24 + public/horjun.png | Bin 0 -> 90480 bytes public/logo.jpg | Bin 0 -> 7604 bytes public/logo.png | Bin 0 -> 4335 bytes public/mahabat-map.png | Bin 0 -> 153508 bytes public/map.jpg | Bin 0 -> 303875 bytes public/menu-outline.svg | 1 + public/person placeholder.svg | 4 + public/play.svg | 4 + public/search.svg | 3 + public/singer.png | Bin 0 -> 295858 bytes public/slider.png | Bin 0 -> 724381 bytes public/spin-blue.svg | 31 + public/spin.svg | 31 + public/splitter.svg | 3 + public/staticPageImage.jpg | Bin 0 -> 1257820 bytes public/step_line.svg | 3 + public/step_tick.svg | 4 + public/success.svg | 19 + public/tv-mobile.png | Bin 0 -> 44705 bytes public/tv-mobile2.png | Bin 0 -> 56345 bytes public/video_altyn_asyr.jpg | Bin 0 -> 10265 bytes public/video_arkadag.jpg | Bin 0 -> 25976 bytes public/video_ashgabat.jpg | Bin 0 -> 6748 bytes public/video_miras.jpg | Bin 0 -> 7010 bytes public/video_turkmen_owaz.jpg | Bin 0 -> 8247 bytes public/video_turkmen_sport.jpg | Bin 0 -> 109748 bytes public/video_turkmenistan.jpg | Bin 0 -> 5523 bytes public/video_yaslyk.jpg | Bin 0 -> 6816 bytes routes.ts | 44 + services/theme.service.ts | 11 + tailwind.config.js | 111 + tailwind.config.ts | 80 + tsconfig.json | 28 + typings/advert.type.ts | 58 + typings/advertisment.type.ts | 12 + typings/auth-input.type.ts | 7 + typings/burger.type.ts | 6 + typings/category.type.ts | 11 + typings/channels.type.ts | 11 + typings/context.type.ts | 53 + typings/params.type.ts | 11 + typings/quizId.type.ts | 6 + typings/quizQuestions.type.ts | 7 + typings/quizSearch.type.ts | 7 + typings/quizSearchActive.type.ts | 6 + typings/source.type.ts | 5 + typings/state.ts | 3 + typings/theme.type.ts | 6 + typings/video.type.ts | 11 + typings/vote/voteDescription.ts | 6 + utils/HydrateClient.tsx | 9 + utils/getQueryClient.ts | 5 + utils/paramParser.ts | 10 + utils/parseString.ts | 5 + utils/validator.ts | 32 + 241 files changed, 21330 insertions(+) create mode 100644 .gitignore create mode 100644 api/queries.ts create mode 100644 app/(main)/[page_id]/page.tsx create mode 100644 app/(main)/about_us/page.tsx create mode 100644 app/(main)/advert/page.tsx create mode 100644 app/(main)/auth/layout.tsx create mode 100644 app/(main)/auth/login/page.tsx create mode 100644 app/(main)/auth/recovery/page.tsx create mode 100644 app/(main)/auth/signup/page.tsx create mode 100644 app/(main)/auth/tarif/page.tsx create mode 100644 app/(main)/contact_us/page.tsx create mode 100644 app/(main)/layout.tsx create mode 100644 app/(main)/live/page.tsx create mode 100644 app/(main)/news/343/page.tsx create mode 100644 app/(main)/news/[slug]/page.tsx create mode 100644 app/(main)/news/page.tsx create mode 100644 app/(main)/quiz/[quiz_id]/page.tsx create mode 100644 app/(main)/quiz/active/page.tsx create mode 100644 app/(main)/sms/dashboard/page.tsx create mode 100644 app/(main)/sms/layout.tsx create mode 100644 app/(main)/sms/sign_up/page.tsx create mode 100644 app/(main)/test/page.tsx create mode 100644 app/(main)/treasury/[video_id]/page.tsx create mode 100644 app/(main)/treasury/layout.tsx create mode 100644 app/(main)/treasury/page.tsx create mode 100644 app/(main)/vote/[vote_id]/page.tsx create mode 100644 app/(main)/vote/active/page.tsx create mode 100644 app/failed/page.tsx create mode 100644 app/globals.css create mode 100644 app/layout.tsx create mode 100644 app/page.tsx create mode 100644 app/success/page.tsx create mode 100644 baseUrl.ts create mode 100644 channels.ts create mode 100644 components.json create mode 100644 components/Aside.tsx create mode 100644 components/AsideAdd.tsx create mode 100644 components/Buble.tsx create mode 100644 components/Button.tsx create mode 100644 components/Categories.tsx create mode 100644 components/Category.tsx create mode 100644 components/ContactDetails.tsx create mode 100644 components/CustomInput.tsx create mode 100644 components/CustomSelect.tsx create mode 100644 components/Footer.tsx create mode 100644 components/Icon.tsx create mode 100644 components/InfoBlock.tsx create mode 100644 components/Loader.tsx create mode 100644 components/Map.tsx create mode 100644 components/MobileMenu.tsx create mode 100644 components/MoreBtn.tsx create mode 100644 components/Nav.tsx create mode 100644 components/PageTitle.tsx create mode 100644 components/PickDate.tsx create mode 100644 components/Premium.tsx create mode 100644 components/SearchBar.tsx create mode 100644 components/SectionTitle.tsx create mode 100644 components/VideoItem.tsx create mode 100644 components/VideoList.tsx create mode 100644 components/VideoPlayer.tsx create mode 100644 components/advert/AdvertWindow.tsx create mode 100644 components/advert/ButtonPrimary.tsx create mode 100644 components/advert/ButtonSecondary.tsx create mode 100644 components/advert/Calculator.tsx create mode 100644 components/advert/Option.tsx create mode 100644 components/advert/SelectForm.tsx create mode 100644 components/advert/Step.tsx create mode 100644 components/advert/Table.tsx create mode 100644 components/advert/plans/Plan.tsx create mode 100644 components/advert/plans/PlanFour.tsx create mode 100644 components/advert/plans/PlanThree.tsx create mode 100644 components/advert/plans/PlanTwo.tsx create mode 100644 components/advert/windows/WindowOne.tsx create mode 100644 components/advert/windows/WindowThree.tsx create mode 100644 components/advert/windows/WindowTwo.tsx create mode 100644 components/auth/AuthInput.tsx create mode 100644 components/auth/LoginForm.tsx create mode 100644 components/auth/RecoveryForm.tsx create mode 100644 components/auth/SignUpForm.tsx create mode 100644 components/auth/TarifWindow.tsx create mode 100644 components/contact_us/ContactForm.tsx create mode 100644 components/contact_us/ContactMap.tsx create mode 100644 components/dashboard/Dashboard.tsx create mode 100644 components/dashboard/FilterTable.tsx create mode 100644 components/home/LinkBlock.tsx create mode 100644 components/home/MainSwiper.tsx create mode 100644 components/home/Marque.tsx create mode 100644 components/home/SmallSwiperAdvert.tsx create mode 100644 components/home/SmallSwiperNews.tsx create mode 100644 components/home/SmallSwiperVideos.tsx create mode 100644 components/home/SmallSwipers.tsx create mode 100644 components/home/ThemeSwitch.tsx create mode 100644 components/home/Toolbar.tsx create mode 100644 components/hox/PresenceAnimator.tsx create mode 100644 components/live/Banner.tsx create mode 100644 components/news/MainNews.tsx create mode 100644 components/news/News.tsx create mode 100644 components/news/NewsGrid.tsx create mode 100644 components/news/NewsItem.tsx create mode 100644 components/news/NewsSlider.tsx create mode 100644 components/quiz/QuizAccordion.tsx create mode 100644 components/quiz/QuizQuestion.tsx create mode 100644 components/quiz/QuizQuestionList.tsx create mode 100644 components/quiz/QuizSearch.tsx create mode 100644 components/quiz/QuizTable.tsx create mode 100644 components/quiz/QuizWinnerTable.tsx create mode 100644 components/shared/SharedButton.tsx create mode 100644 components/shop/ShopTable.tsx create mode 100644 components/sms/ProtectedRoute.tsx create mode 100644 components/sms/smsTable/SmsPagination.tsx create mode 100644 components/sms/smsTable/SmsTable.tsx create mode 100644 components/sms/smsTable/SmsTableBody.tsx create mode 100644 components/sms/smsTable/SmsTableHead.tsx create mode 100644 components/sms/smsTable/SmsTableRow.tsx create mode 100644 components/table/FitlerNumber.tsx create mode 100644 components/table/Search.tsx create mode 100644 components/table/Sort.tsx create mode 100644 components/ui/button.tsx create mode 100644 components/ui/calendar.tsx create mode 100644 components/ui/pagination.tsx create mode 100644 components/ui/popover.tsx create mode 100644 components/updatedAuth/Input.tsx create mode 100644 components/vote/Countdown.tsx create mode 100644 components/vote/GradientTitle.tsx create mode 100644 components/vote/PageBage.tsx create mode 100644 components/vote/ParticipantCard.tsx create mode 100644 components/vote/ParticipantsList.tsx create mode 100644 config/tailwind.config.js create mode 100644 config/tailwind.config.ts create mode 100644 context/AuthContext.tsx create mode 100644 context/GlobalContext.ts create mode 100644 context/MaterialsContext.ts create mode 100644 context/QuizContext.ts create mode 100644 context/SmsContext.tsx create mode 100644 context/StepsContext.ts create mode 100644 context/VoteContext.ts create mode 100644 fonts/Aeroport.otf create mode 100644 lib/utils.ts create mode 100644 models/add.post.model.ts create mode 100644 models/allVotes.model.ts create mode 100644 models/banner.model.ts create mode 100644 models/categories.model.ts create mode 100644 models/channels.model.ts create mode 100644 models/home.model.ts create mode 100644 models/liveDescription.model.ts create mode 100644 models/marquee.model.ts create mode 100644 models/news.model.ts create mode 100644 models/newsItem.model.ts create mode 100644 models/pageItem.model.ts create mode 100644 models/plans.model.ts create mode 100644 models/properties.model.ts create mode 100644 models/quizQuestionHistory.model.ts create mode 100644 models/quizQuestions.model.ts create mode 100644 models/quizQuestionsWinners.model.ts create mode 100644 models/quizSearchData.model.ts create mode 100644 models/sms/messagesByTvAdmis.model.ts create mode 100644 models/sms/my.tv.admins.model.ts create mode 100644 models/video.model.ts create mode 100644 models/videoItem.model.ts create mode 100644 models/videos.model.ts create mode 100644 models/vote.model.ts create mode 100644 next.config.js create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 postcss.config.js create mode 100644 providers/MainProvider.tsx create mode 100644 providers/MaterialsProvider.tsx create mode 100644 providers/QueryProvider.tsx create mode 100644 providers/QuizProvider.tsx create mode 100644 providers/StepsProvider.tsx create mode 100644 providers/VoteProvider.tsx create mode 100644 public/App Store Button Black.svg create mode 100644 public/Google Play Button Black.svg create mode 100644 public/arrow-left-big.svg create mode 100644 public/arrow-left-small.svg create mode 100644 public/arrow-left.svg create mode 100644 public/arrow-right-big.svg create mode 100644 public/arrow-right-small.svg create mode 100644 public/aside-add.jpg create mode 100644 public/aydym-com.webp create mode 100644 public/belet.jpg create mode 100644 public/chevron-up.svg create mode 100644 public/close-outline.svg create mode 100644 public/close-white.svg create mode 100644 public/close.svg create mode 100644 public/error-icon.svg create mode 100644 public/horjun.png create mode 100644 public/logo.jpg create mode 100644 public/logo.png create mode 100644 public/mahabat-map.png create mode 100644 public/map.jpg create mode 100644 public/menu-outline.svg create mode 100644 public/person placeholder.svg create mode 100644 public/play.svg create mode 100644 public/search.svg create mode 100644 public/singer.png create mode 100644 public/slider.png create mode 100644 public/spin-blue.svg create mode 100644 public/spin.svg create mode 100644 public/splitter.svg create mode 100644 public/staticPageImage.jpg create mode 100644 public/step_line.svg create mode 100644 public/step_tick.svg create mode 100644 public/success.svg create mode 100644 public/tv-mobile.png create mode 100644 public/tv-mobile2.png create mode 100644 public/video_altyn_asyr.jpg create mode 100644 public/video_arkadag.jpg create mode 100644 public/video_ashgabat.jpg create mode 100644 public/video_miras.jpg create mode 100644 public/video_turkmen_owaz.jpg create mode 100644 public/video_turkmen_sport.jpg create mode 100644 public/video_turkmenistan.jpg create mode 100644 public/video_yaslyk.jpg create mode 100644 routes.ts create mode 100644 services/theme.service.ts create mode 100644 tailwind.config.js create mode 100644 tailwind.config.ts create mode 100644 tsconfig.json create mode 100644 typings/advert.type.ts create mode 100644 typings/advertisment.type.ts create mode 100644 typings/auth-input.type.ts create mode 100644 typings/burger.type.ts create mode 100644 typings/category.type.ts create mode 100644 typings/channels.type.ts create mode 100644 typings/context.type.ts create mode 100644 typings/params.type.ts create mode 100644 typings/quizId.type.ts create mode 100644 typings/quizQuestions.type.ts create mode 100644 typings/quizSearch.type.ts create mode 100644 typings/quizSearchActive.type.ts create mode 100644 typings/source.type.ts create mode 100644 typings/state.ts create mode 100644 typings/theme.type.ts create mode 100644 typings/video.type.ts create mode 100644 typings/vote/voteDescription.ts create mode 100644 utils/HydrateClient.tsx create mode 100644 utils/getQueryClient.ts create mode 100644 utils/paramParser.ts create mode 100644 utils/parseString.ts create mode 100644 utils/validator.ts diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6cb12cd --- /dev/null +++ b/.gitignore @@ -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 \ No newline at end of file diff --git a/api/queries.ts b/api/queries.ts new file mode 100644 index 0000000..87810ad --- /dev/null +++ b/api/queries.ts @@ -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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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)); + } +} diff --git a/app/(main)/[page_id]/page.tsx b/app/(main)/[page_id]/page.tsx new file mode 100644 index 0000000..f4fe11e --- /dev/null +++ b/app/(main)/[page_id]/page.tsx @@ -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 ; + if (error) return

{JSON.stringify(error)}

; + + return ( +
+
+ +
+ {data?.data.title ? : } +
+ +
+ {data?.data.image && data.data.title ? ( + responsive && data.data.mobile_image ? ( +
+ {data?.data.title} +
+ ) : ( +
+ {data?.data.title} +
+ ) + ) : ( + + )} +
+ {data?.data.content ? ( +

+ ) : ( + + )} + {/*

*/} +
+
+
+
+ ); +}; + +export default PageItem; diff --git a/app/(main)/about_us/page.tsx b/app/(main)/about_us/page.tsx new file mode 100644 index 0000000..ef7e4a4 --- /dev/null +++ b/app/(main)/about_us/page.tsx @@ -0,0 +1,68 @@ +const AboutUs = () => { + return ( +
+

About us

+
+
+
+
+

+ Biz Barada +

+

+ 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. +

+
+
+
+ There are 8 TV channels within the Committee: +
+
    +
  • Arkadag;
  • +
  • Altyn Asyr: Turkmenistan;
  • +
  • Yashlyk;
  • +
  • Miras;
  • +
  • Turkmenistan;
  • +
  • Turkmen Owazy;
  • +
  • Ashgabat;
  • +
  • Sport.
  • +
+
+
+
+ 4 radio channels: +
+
    +
  • Watan;
  • +
  • Chartarapdan;
  • +
  • Miras;
  • +
  • Owaz.
  • +
+
+ {/*
*/} +
+

+ 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; +

+
+ There are also 4 economic settlements under the State Committee: +
+
    +
  • Oguzhan “Turkmenfilm” Association
  • +
  • “Teleradiomerkezi” State Enterprise
  • +
  • “Mahabat” advertising company
  • +
  • School of production
  • +
+
+
+
+
+
+ ); +}; + +export default AboutUs; diff --git a/app/(main)/advert/page.tsx b/app/(main)/advert/page.tsx new file mode 100644 index 0000000..80dd56a --- /dev/null +++ b/app/(main)/advert/page.tsx @@ -0,0 +1,18 @@ +import AdvertWindow from '@/components/advert/AdvertWindow'; +import StepsProvider from '@/providers/StepsProvider'; + +const Advert = () => { + return ( +
+
+
+ + + +
+
+
+ ); +}; + +export default Advert; diff --git a/app/(main)/auth/layout.tsx b/app/(main)/auth/layout.tsx new file mode 100644 index 0000000..408e85d --- /dev/null +++ b/app/(main)/auth/layout.tsx @@ -0,0 +1,15 @@ +interface IProps { + children: React.ReactNode; +} + +function RootLayout({ children }: IProps) { + return ( +
+
+
{children}
+
+
+ ); +} + +export default RootLayout; diff --git a/app/(main)/auth/login/page.tsx b/app/(main)/auth/login/page.tsx new file mode 100644 index 0000000..2c8ba36 --- /dev/null +++ b/app/(main)/auth/login/page.tsx @@ -0,0 +1,11 @@ +import LoginForm from "@/components/auth/LoginForm"; + +const Login = () => { + return ( +
+ +
+ ); +}; + +export default Login; diff --git a/app/(main)/auth/recovery/page.tsx b/app/(main)/auth/recovery/page.tsx new file mode 100644 index 0000000..3c53e8c --- /dev/null +++ b/app/(main)/auth/recovery/page.tsx @@ -0,0 +1,11 @@ +import RecoveryForm from "@/components/auth/RecoveryForm"; + +const Recovery = () => { + return ( +
+ +
+ ); +}; + +export default Recovery; diff --git a/app/(main)/auth/signup/page.tsx b/app/(main)/auth/signup/page.tsx new file mode 100644 index 0000000..d3af4d7 --- /dev/null +++ b/app/(main)/auth/signup/page.tsx @@ -0,0 +1,11 @@ +import SignUpForm from "@/components/auth/SignUpForm"; + +const SignUp = () => { + return ( +
+ +
+ ); +}; + +export default SignUp; diff --git a/app/(main)/auth/tarif/page.tsx b/app/(main)/auth/tarif/page.tsx new file mode 100644 index 0000000..679352e --- /dev/null +++ b/app/(main)/auth/tarif/page.tsx @@ -0,0 +1,12 @@ +import TarifWindow from '@/components/auth/TarifWindow'; + +const Login = () => { + return ( +
+ + +
+ ); +}; + +export default Login; diff --git a/app/(main)/contact_us/page.tsx b/app/(main)/contact_us/page.tsx new file mode 100644 index 0000000..1c7897f --- /dev/null +++ b/app/(main)/contact_us/page.tsx @@ -0,0 +1,17 @@ +import ContactForm from '@/components/contact_us/ContactForm'; +import ContactMap from '@/components/contact_us/ContactMap'; + +const Contact = () => { + return ( +
+
+
+ + +
+
+
+ ); +}; + +export default Contact; diff --git a/app/(main)/layout.tsx b/app/(main)/layout.tsx new file mode 100644 index 0000000..8039a16 --- /dev/null +++ b/app/(main)/layout.tsx @@ -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 ( +
+ + +
+

Turkmen TV

+
+
+
+ ); +}; + +export default RootLayout; diff --git a/app/(main)/live/page.tsx b/app/(main)/live/page.tsx new file mode 100644 index 0000000..0289411 --- /dev/null +++ b/app/(main)/live/page.tsx @@ -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 ; + // if (error) return

{JSON.stringify(error)}

; + + return ( +
+
+
+
+
+ {channels.map((channel) => + channel.name === activeChannel.channelName ? ( + + ) : null, + )} +
+ +
+
+ {channels + ? channels.map((channel) => { + return ( + + 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={''}> +
+ {channel.name} +
+

{channel.name}

+ + ); + }) + : null} +
+ + {data?.data.length ? ( +
+ {/*

+ {`${ParseString.parseTime(data.data[0].start)} - ${ParseString.parseTime( + data.data[0].end, + )}`} +

*/} +

{data.data[0].content}

+
+ ) : null} +
+
+ + +
+
+
+ ); +}; + +export default page; diff --git a/app/(main)/news/343/page.tsx b/app/(main)/news/343/page.tsx new file mode 100644 index 0000000..73ff9eb --- /dev/null +++ b/app/(main)/news/343/page.tsx @@ -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 ( +
+
+ +
+
+ {/* */} +
+ + {/*

{data?.data.published_at}

*/} +
+ +
+
+ {'kitaplar'} +
+
+ {/*

{data!.data.title}

*/} +

+ Mahabat müdirliginiň neşir önümleri: +
1. 3+ we 5+ ýaşly çagalar üçin Zehin soraglary. Bahasy 23 manat; +
2. Çagalar üçin reňkleme kitaplary. Bahasy: 13 manat; +
3. Ulylar üçin "Sözýetim" güýmenjesi. Bahasy: 10 manat; +
4. Çagalara kompýuter programirleme diline giriş "Başarjaň". Bahasy: 38 + manat; +
5. Elwan depderim reňkleme kitaby. Bahasy: 28 manat; +
6. 7 ýaşdan ýokary çagalar üçin niýetlenen erteki kitaplary. Bahasy: 8 + manat; +
+
{' '} + + Satyn almak üçin şu düwmä basyň! + +

+
+
+
+
+
+
+
+ ); +}; + +export default NewsItemStatic; diff --git a/app/(main)/news/[slug]/page.tsx b/app/(main)/news/[slug]/page.tsx new file mode 100644 index 0000000..e4edc17 --- /dev/null +++ b/app/(main)/news/[slug]/page.tsx @@ -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 ( +
+
+ +
+ +
+ +
+
+
+
+
+ ); +}; + +export default NewsItem; diff --git a/app/(main)/news/page.tsx b/app/(main)/news/page.tsx new file mode 100644 index 0000000..9b7e80e --- /dev/null +++ b/app/(main)/news/page.tsx @@ -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 ( +
+
+
+ + + +
+
+
+ ); +}; + +export default News; diff --git a/app/(main)/quiz/[quiz_id]/page.tsx b/app/(main)/quiz/[quiz_id]/page.tsx new file mode 100644 index 0000000..c534ad7 --- /dev/null +++ b/app/(main)/quiz/[quiz_id]/page.tsx @@ -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(false); + const [data, setData] = useState(); + + // 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 ( +
+
+ +
+
+ ); + } + + return ( +
+ {typeof data !== 'string' ? ( +
+ +
+
+
+

+ {data ? Validator.reveseDate(data?.data.date) : null} +

+

+ {data?.data.title} +

+

+ {data?.data.description} +

+
+ {data?.data.banner ? ( +
+ {mobile ? ( + {'banner'} + ) : ( + {'banner'} + )} +
+ ) : null} +
+ + {data?.data.rules && data.data.notes ? ( + + ) : null} +
+ + {data?.data.id && quizFinished ? : null} + +
+ {data?.data ? ( + + ) : null} + + {data?.data.id && quizFinished ? ( + + ) : null} +
+
+
+ ) : ( +
+ Непредвиденная ошибка. Нет активной викторины. +
+ )} +
+ ); + } else { + return ( +
+
+ +
+
+ ); + } +}; + +export default page; diff --git a/app/(main)/quiz/active/page.tsx b/app/(main)/quiz/active/page.tsx new file mode 100644 index 0000000..fc1dd76 --- /dev/null +++ b/app/(main)/quiz/active/page.tsx @@ -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(false); + const [data, setData] = useState(); + + // 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 ( +
+ {typeof data !== 'string' ? ( +
+ +
+
+
+

+ {data ? Validator.reveseDate(data?.data.date) : null} +

+

+ {data?.data.title} +

+

+ {data?.data.description} +

+
+ {data?.data.banner ? ( +
+ {mobile ? ( + {'banner'} + ) : ( + {'banner'} + )} +
+ ) : null} +
+ + {data?.data.rules && data.data.notes ? ( + + ) : null} +
+ + {data?.data.id && quizFinished ? : null} + +
+ {data?.data ? ( + + ) : null} + + {data?.data.id && quizFinished ? ( + + ) : null} +
+
+
+ ) : ( +
+ Непредвиденная ошибка. Нет активной викторины. +
+ )} +
+ ); +}; + +export default page; diff --git a/app/(main)/sms/dashboard/page.tsx b/app/(main)/sms/dashboard/page.tsx new file mode 100644 index 0000000..739c5b1 --- /dev/null +++ b/app/(main)/sms/dashboard/page.tsx @@ -0,0 +1,12 @@ +import Dashboard from '@/components/dashboard/Dashboard'; +import { SmsProvider } from '@/context/SmsContext'; + +const page = () => { + return ( + + + + ); +}; + +export default page; diff --git a/app/(main)/sms/layout.tsx b/app/(main)/sms/layout.tsx new file mode 100644 index 0000000..4e28d97 --- /dev/null +++ b/app/(main)/sms/layout.tsx @@ -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 ( +
+
+ {children} +
+
+ ); +} diff --git a/app/(main)/sms/sign_up/page.tsx b/app/(main)/sms/sign_up/page.tsx new file mode 100644 index 0000000..2dda97d --- /dev/null +++ b/app/(main)/sms/sign_up/page.tsx @@ -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 ( +
+
+
+

SMS ulgamy

+
+ + setLogin(e.target.value)} + placeholder="Login giriziň" + /> +
+
+ + setPassword(e.target.value)} + placeholder="Açar sözi giriziň" + /> +
+ + + + {userLogedIn === false ? ( +

+ Login ýa-da açar sözi ýalňyş +

+ ) : null} +
+
+
+ ); +}; + +export default Page; diff --git a/app/(main)/test/page.tsx b/app/(main)/test/page.tsx new file mode 100644 index 0000000..b498d71 --- /dev/null +++ b/app/(main)/test/page.tsx @@ -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(); + + const calendarRef = useRef(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 ( +
+
+ 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 }, + )} + /> +
+
+ ); +}; + +export default page; diff --git a/app/(main)/treasury/[video_id]/page.tsx b/app/(main)/treasury/[video_id]/page.tsx new file mode 100644 index 0000000..f8d42bb --- /dev/null +++ b/app/(main)/treasury/[video_id]/page.tsx @@ -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 ( +
+
+ +
+
+ + +
+ + +
+
+
+
+
+
+ ); +}; + +export default VideoItem; diff --git a/app/(main)/treasury/layout.tsx b/app/(main)/treasury/layout.tsx new file mode 100644 index 0000000..2f1ddfb --- /dev/null +++ b/app/(main)/treasury/layout.tsx @@ -0,0 +1,15 @@ +import MaterialsProvider from "@/providers/MaterialsProvider"; + +interface IProps { + children: React.ReactNode; +} + +function RootLayout({ children }: IProps) { + return ( +
+ {children} +
+ ); +} + +export default RootLayout; diff --git a/app/(main)/treasury/page.tsx b/app/(main)/treasury/page.tsx new file mode 100644 index 0000000..e162ae7 --- /dev/null +++ b/app/(main)/treasury/page.tsx @@ -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 ( +
+
+
+

Treasury

+ +
+
+
+
+
+
+ ); +}; + +export default Treasury; diff --git a/app/(main)/vote/[vote_id]/page.tsx b/app/(main)/vote/[vote_id]/page.tsx new file mode 100644 index 0000000..0d1f9e4 --- /dev/null +++ b/app/(main)/vote/[vote_id]/page.tsx @@ -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 ( +
+
+ +
+ +
+
+
+
+ ); +}; + +export default page; diff --git a/app/(main)/vote/active/page.tsx b/app/(main)/vote/active/page.tsx new file mode 100644 index 0000000..77b5f24 --- /dev/null +++ b/app/(main)/vote/active/page.tsx @@ -0,0 +1,19 @@ +import PageBage from '@/components/vote/PageBage'; +import ParticipantsList from '@/components/vote/ParticipantsList'; +import VoteProvider from '@/providers/VoteProvider'; + +const page = () => { + return ( +
+
+ +
+ +
+
+
+
+ ); +}; + +export default page; diff --git a/app/failed/page.tsx b/app/failed/page.tsx new file mode 100644 index 0000000..27a1875 --- /dev/null +++ b/app/failed/page.tsx @@ -0,0 +1,34 @@ +import Image from 'next/image'; +import Link from 'next/link'; +import errorIcon from '@/public/error-icon.svg'; + +const Failed = () => { + return ( +
+
+ close icon +

Näsazlyk ýüze çykdy

+ {/*

Something went wrong.

*/} + + arrow +

Yza geç

+ +
+
+ ); +}; +export default Failed; diff --git a/app/globals.css b/app/globals.css new file mode 100644 index 0000000..64e4faf --- /dev/null +++ b/app/globals.css @@ -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]; + } +} diff --git a/app/layout.tsx b/app/layout.tsx new file mode 100644 index 0000000..58a33b7 --- /dev/null +++ b/app/layout.tsx @@ -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 ( + + + + + + {children} + + + + + + ); +} diff --git a/app/page.tsx b/app/page.tsx new file mode 100644 index 0000000..9c4d6a6 --- /dev/null +++ b/app/page.tsx @@ -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 ( +
+ +
+ {/* */} + + + +
+
+
+ ); +}; + +export default Home; diff --git a/app/success/page.tsx b/app/success/page.tsx new file mode 100644 index 0000000..9d5fa57 --- /dev/null +++ b/app/success/page.tsx @@ -0,0 +1,36 @@ +import Image from 'next/image'; +import Link from 'next/link'; + +const Success = () => { + return ( +
+
+ success icon +

Gutlaýarys!

+

+ Siziň sargydyňyz kabul edildi, “mahabat@turkmentv.gov.tm” elektron poçtasyna + rekwizitleriňizi ugratmagyňyzy haýyş edýäris! +

+ + arrow +

Yza geç

+ +
+
+ ); +}; +export default Success; diff --git a/baseUrl.ts b/baseUrl.ts new file mode 100644 index 0000000..c585d5f --- /dev/null +++ b/baseUrl.ts @@ -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', +// }; diff --git a/channels.ts b/channels.ts new file mode 100644 index 0000000..bf2ed50 --- /dev/null +++ b/channels.ts @@ -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; diff --git a/components.json b/components.json new file mode 100644 index 0000000..15f2b02 --- /dev/null +++ b/components.json @@ -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" + } +} \ No newline at end of file diff --git a/components/Aside.tsx b/components/Aside.tsx new file mode 100644 index 0000000..a04b02e --- /dev/null +++ b/components/Aside.tsx @@ -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 ; + if (error) return

{JSON.stringify(error)}

; + + return ( + + ); +}; + +export default Aside; +// export default Aside; diff --git a/components/AsideAdd.tsx b/components/AsideAdd.tsx new file mode 100644 index 0000000..7687af7 --- /dev/null +++ b/components/AsideAdd.tsx @@ -0,0 +1,19 @@ +import Image from "next/image"; + +const AsideAdd = () => { + return ( + + ); +}; + +export default AsideAdd; diff --git a/components/Buble.tsx b/components/Buble.tsx new file mode 100644 index 0000000..8268546 --- /dev/null +++ b/components/Buble.tsx @@ -0,0 +1,10 @@ +const Buble = () => { + return ( +
+
+
+
+ ); +}; + +export default Buble; diff --git a/components/Button.tsx b/components/Button.tsx new file mode 100644 index 0000000..a3c46b6 --- /dev/null +++ b/components/Button.tsx @@ -0,0 +1,24 @@ +interface IProps { + name: string; + width?: string; + type: 'submit' | 'button' | 'reset'; + onClick?: () => void; + disabled?: boolean; +} + +const Button = (props: IProps) => { + return ( +
+ +
+ ); +}; + +export default Button; diff --git a/components/Categories.tsx b/components/Categories.tsx new file mode 100644 index 0000000..d367199 --- /dev/null +++ b/components/Categories.tsx @@ -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 ; + if (error) return

{JSON.stringify(error)}

; + + return ( +
    + + {data + ? data.data.map((category) => ( + + )) + : null} +
+ ); +}; + +export default Categories; diff --git a/components/Category.tsx b/components/Category.tsx new file mode 100644 index 0000000..ea1722b --- /dev/null +++ b/components/Category.tsx @@ -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 ( +
  • +
    { + 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} +
    +
  • + ); +}; + +export default Category; diff --git a/components/ContactDetails.tsx b/components/ContactDetails.tsx new file mode 100644 index 0000000..71ef9ef --- /dev/null +++ b/components/ContactDetails.tsx @@ -0,0 +1,12 @@ +const ContactDetails = () => { + return ( +
    +

    MAHABAT MÜDIRLIGI

    + Phone: +993 12 493705 + Aşgabat şäheri Oguzhan 13 + mahabat@turkmentv.gov.tm +
    + ); +}; + +export default ContactDetails; diff --git a/components/CustomInput.tsx b/components/CustomInput.tsx new file mode 100644 index 0000000..eae7ab5 --- /dev/null +++ b/components/CustomInput.tsx @@ -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(false); + return ( +
    + {label ? ( + + ) : null} + {!isTextArea ? ( +
    + ) => { + if (!isTouched) { + setIsTouched(true); + } + value.setValue(e.target.value); + }} + /> + {isTouched && validate && !validate.isValid ? ( + {validate.errMsg} + ) : null} +
    + ) : ( + + )} +
    + ); +}; + +export default CustomInput; diff --git a/components/CustomSelect.tsx b/components/CustomSelect.tsx new file mode 100644 index 0000000..6944ea1 --- /dev/null +++ b/components/CustomSelect.tsx @@ -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(false); + const ref = useRef(null); + const [input, setInput] = useState(); + + useOnClickOutside(ref, () => setOpen(false)); + return ( +
    setOpen(!open)}> + {label ? ( + + ) : null} + + + + + + {items.map((item) => ( + { + setValue(item); + setInput(item); + }} + key={v4()}> + {item} + + ))} + +
    + ); +}; +export default CustomSelect; diff --git a/components/Footer.tsx b/components/Footer.tsx new file mode 100644 index 0000000..1223833 --- /dev/null +++ b/components/Footer.tsx @@ -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 ( +
    +
    +
    +
    +
      +
    • + + Habarlar + +
    • +
    • + + Mahabat + +
    • +
    • + + Hazyna + +
    • +
    • + + Biz barada + +
    • +
    • + + Göni Ýaýlym + +
    • +
    • + + Habarlaşmak üçin + +
    • +
    +
    +
    +
    + asf +
    +
    + + app store + + + google play + +
    +
    +
    +
    + asf +
    +
    + + app store + + + google play + +
    +
    +
    +

    + {year} © TurkmenTV. All rights reserved. +

    +
    +
    +

    + Phone: +993 12 493705 + Aşgabat şäheri Oguzhan 13 + mahabat@turkmentv.gov.tm + +

    +
    +

    + {year} © TurkmenTV. All rights reserved. +

    +
    +
    +
    + ); +}; + +export default Footer; diff --git a/components/Icon.tsx b/components/Icon.tsx new file mode 100644 index 0000000..0e91ad4 --- /dev/null +++ b/components/Icon.tsx @@ -0,0 +1,24 @@ +import Image from "next/image"; + +interface IProps { + width: number; + height: number; + element: string; +} + +const Icon = ({ width, height, element }: IProps) => { + return ( +
    + {`icon_${element}`} +
    + ); +}; + +export default Icon; diff --git a/components/InfoBlock.tsx b/components/InfoBlock.tsx new file mode 100644 index 0000000..67529b8 --- /dev/null +++ b/components/InfoBlock.tsx @@ -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('isLiked', []) || []; + const [isDisLikedId, setIsDisLikedId] = useLocalStorage('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 ; + if (error) return

    {JSON.stringify(error)}

    ; + + return ( +
    +
    +
    +
    +
    + +
    + + + {data!.data.view} + +
    +
    +
    +
    +
    + +
    + +
    + {data?.data.desc ? ( +

    {data!.data.desc}

    + ) : null} +
    +
    onLikeHandler()}> + + {data?.data.likes} +
    +
    onDisLikeHandler()}> + + {data?.data.dislikes} +
    +
    + {data?.data.aydym_com_url || + data?.data.horjun_content_url || + data?.data.belet_url ? ( +
    +

    Beýleki platformalarda seret:

    +
    + {data?.data.aydym_com_url ? ( + +
    + Aydym.com +
    +

    Aydym.com

    + + ) : null} + {data?.data.horjun_content_url ? ( + +
    + HorjunTv +
    +

    HorjunTv

    + + ) : null} + {data?.data.belet_url ? ( + +
    + BeletTv +
    +

    BeletFilm

    + + ) : null} +
    +
    + ) : null} +
    +
    +
    +
    +
    +
    +
    + ); +}; + +export default InfoBlock; diff --git a/components/Loader.tsx b/components/Loader.tsx new file mode 100644 index 0000000..269c8ab --- /dev/null +++ b/components/Loader.tsx @@ -0,0 +1,23 @@ +import Image from 'next/image'; +import { CSSProperties } from 'react'; + +interface IProps { + height?: CSSProperties['height']; +} + +const Loader = ({ height = 'auto' }: IProps) => { + return ( +
    + loader +
    + ); +}; + +export default Loader; diff --git a/components/Map.tsx b/components/Map.tsx new file mode 100644 index 0000000..ee76a86 --- /dev/null +++ b/components/Map.tsx @@ -0,0 +1,21 @@ +import Image from 'next/image'; + +import map from '@/public/mahabat-map.png'; + +const Map = () => { + return ( +
    + map +
    + ); +}; + +export default Map; diff --git a/components/MobileMenu.tsx b/components/MobileMenu.tsx new file mode 100644 index 0000000..f842cf8 --- /dev/null +++ b/components/MobileMenu.tsx @@ -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(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 && ( +
    +
    +
    +
    + + logo + +
    setBurgerOpen(false)}> + menu +
    +
    +
      +
    • + onClickCloseBurgerHandler()} + href={'/news'} + style={path.includes('news') ? { color: '#FFAB48' } : {}}> + Habarlar + +
    • +
    • + 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 + +
    • +
    • + 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 + +
    • +
    • + onClickCloseBurgerHandler()} + className="block text-3xl text-white transition-all font-roboto font-bold dark:text-white" + style={path.includes('advert') ? { color: '#FFAB48' } : {}}> + Mahabat + +
    • +
    • + 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 + +
    • +
    • +
      setDropDownOpened(!dropDownOpened)}> +
      + + Interaktiw + + + + +
      + +
      + { + setDropDownOpened(false); + onClickCloseBurgerHandler(); + }}> + Bäsleşik + + { + setDropDownOpened(false); + onClickCloseBurgerHandler(); + }}> + Ses bermek + + { + setDropDownOpened(false); + onClickCloseBurgerHandler(); + }}> + TV market + +
      +
      +
    • +
    • + 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 + +
    • +
    +
    +
    +
    + )} + + ); +}; + +export default MobileMenu; diff --git a/components/MoreBtn.tsx b/components/MoreBtn.tsx new file mode 100644 index 0000000..fd220cd --- /dev/null +++ b/components/MoreBtn.tsx @@ -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 ( + + ); +}; + +export default MoreBtn; diff --git a/components/Nav.tsx b/components/Nav.tsx new file mode 100644 index 0000000..e41552b --- /dev/null +++ b/components/Nav.tsx @@ -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(null); + const dropdownRef = useRef(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 ( + + ); +}; + +export default Nav; diff --git a/components/PageTitle.tsx b/components/PageTitle.tsx new file mode 100644 index 0000000..2199eae --- /dev/null +++ b/components/PageTitle.tsx @@ -0,0 +1,8 @@ +interface IProps { + title: string; +} +const PageTitle = ({ title }: IProps) => { + return

    {title}

    ; +}; + +export default PageTitle; diff --git a/components/PickDate.tsx b/components/PickDate.tsx new file mode 100644 index 0000000..6d0eebd --- /dev/null +++ b/components/PickDate.tsx @@ -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('2022-05-16')); + return ( +
    + (newValue ? setDate(newValue) : null)} + format="MMM DD, YYYY" + className="w-full bg-transparent" + /> +
    + ); +}; + +export default PickDate; diff --git a/components/Premium.tsx b/components/Premium.tsx new file mode 100644 index 0000000..fe8bcc9 --- /dev/null +++ b/components/Premium.tsx @@ -0,0 +1,9 @@ +const Premium = () => { + return ( +
    + Premium +
    + ); +}; + +export default Premium; diff --git a/components/SearchBar.tsx b/components/SearchBar.tsx new file mode 100644 index 0000000..8266d08 --- /dev/null +++ b/components/SearchBar.tsx @@ -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(''); + + return ( +
    e.preventDefault()}> +
    + { + 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" + /> +
    + +
    + ); +}; + +export default SearchBar; diff --git a/components/SectionTitle.tsx b/components/SectionTitle.tsx new file mode 100644 index 0000000..cd79607 --- /dev/null +++ b/components/SectionTitle.tsx @@ -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 ( +
    +

    + {title} +

    + {views ? ( +
    + + + {views} + +
    + ) : null} +
    + ); +}; + +export default SectionTitle; diff --git a/components/VideoItem.tsx b/components/VideoItem.tsx new file mode 100644 index 0000000..c5acefa --- /dev/null +++ b/components/VideoItem.tsx @@ -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 ( + + {img ? ( + <> +
    + {title} + {'play +
    + + ) : null} + + {/* {premium ? : null} */} +
    +
    +

    + {title} +

    + {/* {`${views} Views`} */} +
    +
    + + ); +}; + +export default VideoItem; diff --git a/components/VideoList.tsx b/components/VideoList.tsx new file mode 100644 index 0000000..e4f070e --- /dev/null +++ b/components/VideoList.tsx @@ -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 ; + if (error) return

    {JSON.stringify(error)}

    ; + + return ( +
    + {isSlides ? ( +
    + + {data ? ( + data.pages.flatMap((data) => data.data).length > 0 ? ( + data.pages + .flatMap((data) => data.data) + .map((item) => ( + + + + )) + ) : ( +

    No news for the given filters

    + ) + ) : null} +
    +
    + ) : ( +
    +
    + {data ? ( + data.pages.flatMap((data) => data.data).length > 0 ? ( + data.pages + .flatMap((data) => data.data) + .map((item) => ( + + )) + ) : ( +

    No news for the given filters

    + ) + ) : null} +
    +
    + fetchNextPage()} + disabled={!hasNextPage} + isFetching={isFetchingNextPage} + /> +
    +
    + )} +
    + ); +}; + +export default VideoList; diff --git a/components/VideoPlayer.tsx b/components/VideoPlayer.tsx new file mode 100644 index 0000000..64ef34c --- /dev/null +++ b/components/VideoPlayer.tsx @@ -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(false); + const [hasStartedPlaying, setHasStartedPlaying] = useState(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 ; + if (error) return

    {JSON.stringify(error)}

    ; + + return ( +
    + {hasWindow ? ( + data?.data.content_url.endsWith('.mp4') ? ( +
    + {/* onPlayHandler()} + /> */} + +
    + ) : ( +
    +
    + {data?.data.banner_url ? ( + {'image'} + ) : null} +
    + +
    + ) + ) : null} +
    + ); +}; + +export default VideoPlayer; diff --git a/components/advert/AdvertWindow.tsx b/components/advert/AdvertWindow.tsx new file mode 100644 index 0000000..7b340f2 --- /dev/null +++ b/components/advert/AdvertWindow.tsx @@ -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 ( +
    +

    + Mahabat ýerleşdirmek üçin 3 ädimi yzarlaň! +

    +
    +
    + {stepNames.map((value, index) => ( +
    + + {index !== 2 ? ( + index !== step ? ( +
    + ) : ( +
    + ) + ) : null} +
    + ))} +
    +
    + + {step === 1 && } + {step === 2 && } + {step === 3 && } + + {calculatorContext.calculatorOpen ? : null} +
    +
    +
    + ); +}; + +export default AdvertWindow; diff --git a/components/advert/ButtonPrimary.tsx b/components/advert/ButtonPrimary.tsx new file mode 100644 index 0000000..08e4c1e --- /dev/null +++ b/components/advert/ButtonPrimary.tsx @@ -0,0 +1,20 @@ +import { ButtonHTMLAttributes } from 'react'; + +interface IProps { + onClick: () => void; + name: string; + type?: ButtonHTMLAttributes['type']; + disabled?: boolean; +} +const ButtonPrimary = ({ name, onClick, type, disabled }: IProps) => { + return ( + + ); +}; +export default ButtonPrimary; diff --git a/components/advert/ButtonSecondary.tsx b/components/advert/ButtonSecondary.tsx new file mode 100644 index 0000000..c62a846 --- /dev/null +++ b/components/advert/ButtonSecondary.tsx @@ -0,0 +1,19 @@ +import { ButtonHTMLAttributes } from 'react'; + +interface IProps { + onClick: () => void; + name: string; + type?: ButtonHTMLAttributes['type']; +} + +const ButtonSecondary = ({ name, onClick, type = 'button' }: IProps) => { + return ( + + ); +}; +export default ButtonSecondary; diff --git a/components/advert/Calculator.tsx b/components/advert/Calculator.tsx new file mode 100644 index 0000000..770f7e7 --- /dev/null +++ b/components/advert/Calculator.tsx @@ -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 ( +
    + {plans ? ( +
    e.preventDefault()}> +
    +
    +

    Calculator

    +

    Sargyt etmek üçin aşakdaky maglumatlary dolduryň

    +
    +
    calculatorContext.setCalculatorOpen(false)}> + close +
    +
    +
    + + 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), + }} + /> + 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), + }} + /> +
    +
    +

    Jemi baha:

    + + {form.total || 0} TMT + +
    +
    + { + stepContext.setStep(3); + calculatorContext.setCalculatorOpen(false); + }} + type="button" + /> +
    +
    + ) : ( + + )} +
    + ); +}; +export default Calculator; diff --git a/components/advert/Option.tsx b/components/advert/Option.tsx new file mode 100644 index 0000000..74182d4 --- /dev/null +++ b/components/advert/Option.tsx @@ -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 ( +
    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"> +

    + {title} +

    +

    +
    + ); +}; + +export default Option; diff --git a/components/advert/SelectForm.tsx b/components/advert/SelectForm.tsx new file mode 100644 index 0000000..14612f3 --- /dev/null +++ b/components/advert/SelectForm.tsx @@ -0,0 +1,5 @@ +const SelectForm = () => { + return
    ; +}; + +export default SelectForm; diff --git a/components/advert/Step.tsx b/components/advert/Step.tsx new file mode 100644 index 0000000..e0f2f1d --- /dev/null +++ b/components/advert/Step.tsx @@ -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 ( +
    +
    +
    +
    + + {number} + +
    +

    setStep(number)}> + {name} +

    + {!last ? ( +
    +
    +
    + ) : null} +
    + ); +}; + +export default Step; diff --git a/components/advert/Table.tsx b/components/advert/Table.tsx new file mode 100644 index 0000000..2f23f80 --- /dev/null +++ b/components/advert/Table.tsx @@ -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 ( +
    +
    +
    + {cols.map((col, id) => ( + + {plan == 3 && columnName[col as keyof typeof columnName] === 'Subtitle' + ? 'Flyer' + : columnName[col as keyof typeof columnName]} + + ))} +
    +
    + {data.map((item, index) => { + const keys = Object.keys(item); + return index % 2 === 0 ? ( +
    + {keys.map((key, id) => ( + + {item[key as keyof typeof item]} + + ))} +
    + ) : ( +
    + {keys.map((key, id) => ( + + {item[key as keyof typeof item]} + + ))} +
    + ); + })} +
    +
    +
    + ); +}; + +export default Table; diff --git a/components/advert/plans/Plan.tsx b/components/advert/plans/Plan.tsx new file mode 100644 index 0000000..a021b85 --- /dev/null +++ b/components/advert/plans/Plan.tsx @@ -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(); + + 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 ( +
    + +
    +

    + Mahabat görnüşini we bukjasyny saýlaň! +

    +
    + {/* {plans.data.map((item) => + item[definePlan(plan) as keyof (typeof plans)['data'][any]] ? ( +
    + + {definePlan(plan) === 'on_outside_monitors' ? ( + + LED monitorlaryň kartasy + + ) : null} + + {form.folder_id !== '' && folder ? ( + +
    +
    + + +
    + ) : null + )} */} + + + ); +}; + +export default PlanFour; diff --git a/components/advert/plans/PlanThree.tsx b/components/advert/plans/PlanThree.tsx new file mode 100644 index 0000000..1dae4a0 --- /dev/null +++ b/components/advert/plans/PlanThree.tsx @@ -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(null); + return ( +
    + +

    + Mahabat görnüşini we bukjasyny saýlaň! +

    +
    + {plans.data.map((item) => + item.on_subtitle ? ( +
    + {/* {plans.data.map((plan) => + plan.on_subtitle ? ( +
    + ) : null + )} */} + + + ); +}; + +export default PlanThree; diff --git a/components/advert/plans/PlanTwo.tsx b/components/advert/plans/PlanTwo.tsx new file mode 100644 index 0000000..f9ddddb --- /dev/null +++ b/components/advert/plans/PlanTwo.tsx @@ -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(null); + return ( +
    + +

    + Mahabat görnüşini we bukjasyny saýlaň! +

    +
    + {plans.data.map((item) => + item.on_radio ? ( +
    + {/* {plans.data.map((plan) => + plan.on_radio ? ( +
    + ) : null + )} */} + + + ); +}; + +export default PlanTwo; diff --git a/components/advert/windows/WindowOne.tsx b/components/advert/windows/WindowOne.tsx new file mode 100644 index 0000000..a458d7f --- /dev/null +++ b/components/advert/windows/WindowOne.tsx @@ -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 ; + return ( + +
    +
    +

    + Eýeçiligiň görnüşini saýlaň! +

    +
    + {properties.properties.data.map((option) => ( +
    +
    +
    +
    + ); +}; + +export default WindowOne; diff --git a/components/advert/windows/WindowThree.tsx b/components/advert/windows/WindowThree.tsx new file mode 100644 index 0000000..edce4dc --- /dev/null +++ b/components/advert/windows/WindowThree.tsx @@ -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(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 ; + + return ( + <> + +
    +
    { + e.preventDefault(); + await Queries.postAdvert(form); + }}> +

    + Bukjany sargyt etmek üçin aşakdaky maglumatlary dolduryň +

    + 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), + }} + /> + 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), + }} + /> + 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), + }} + /> + 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" + /> +

    + Jikme-jiklikleri bize{' '} + + mahabat@turkmen.gov.tm + {' '} + adrese iberiň +

    + +
    +
    + + ); +}; + +export default WindowThree; diff --git a/components/advert/windows/WindowTwo.tsx b/components/advert/windows/WindowTwo.tsx new file mode 100644 index 0000000..92539b2 --- /dev/null +++ b/components/advert/windows/WindowTwo.tsx @@ -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 ; + return ( + +
    +
    +

    + Mahabat görnüşini we bukjasyny saýlaň! +

    +
    + {planTypes.map((option) => ( +
    +
    + +
    +
    + ); +}; + +export default WindowTwo; diff --git a/components/auth/AuthInput.tsx b/components/auth/AuthInput.tsx new file mode 100644 index 0000000..207a119 --- /dev/null +++ b/components/auth/AuthInput.tsx @@ -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(false); + return ( +
    + {label ? : null} + + {type === 'password' ? ( + <> + setIsVisible(!isVisible)} + /> + setIsVisible(!isVisible)} + /> + + ) : null} +
    + ); +}; + +export default AuthInput; diff --git a/components/auth/LoginForm.tsx b/components/auth/LoginForm.tsx new file mode 100644 index 0000000..c6a07a7 --- /dev/null +++ b/components/auth/LoginForm.tsx @@ -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 ( +
    +
    +

    + Hasabyňyza giriň +

    +
    e.preventDefault()} className="flex flex-col gap-4"> + + +
    +
    + ); +}; + +export default LoginForm; diff --git a/components/auth/RecoveryForm.tsx b/components/auth/RecoveryForm.tsx new file mode 100644 index 0000000..1ef36d8 --- /dev/null +++ b/components/auth/RecoveryForm.tsx @@ -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 ( +
    +
    +

    + Hasabyňyza giriň +

    +
    e.preventDefault()} className="flex flex-col gap-4"> + +
    +
    + ); +}; + +export default LoginForm; diff --git a/components/auth/SignUpForm.tsx b/components/auth/SignUpForm.tsx new file mode 100644 index 0000000..50a94b3 --- /dev/null +++ b/components/auth/SignUpForm.tsx @@ -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 ( +
    +
    +

    + Hasabyňyza giriň +

    +
    e.preventDefault()} className="flex flex-col gap-4"> + + + + + + +
    +
    + ); +}; + +export default LoginForm; diff --git a/components/auth/TarifWindow.tsx b/components/auth/TarifWindow.tsx new file mode 100644 index 0000000..538c1bc --- /dev/null +++ b/components/auth/TarifWindow.tsx @@ -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 ( +
    +
    +

    {`BUKJA ${folder}`}

    +

    {`${days} GÜN - ${price} TMT`}

    +
    +
    + ); +}; + +export default TarifWindow; diff --git a/components/contact_us/ContactForm.tsx b/components/contact_us/ContactForm.tsx new file mode 100644 index 0000000..25628ea --- /dev/null +++ b/components/contact_us/ContactForm.tsx @@ -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 ( +
    e.preventDefault()}> + setInput({ ...input, name: value }), + }} + name="name" + label="Ady" + placeholder="Aman Amanow" + type="text" + /> + setInput({ ...input, email: value }), + }} + name="email" + label="E-mail" + placeholder="meselem@gmail.com" + type="email" + /> + setInput({ ...input, message: value }), + }} + name="message" + label="Hat" + placeholder="Hat" + isTextArea + /> + setInput({ ...input, topic: value }), + }} + name="topic" + label="Mowzuk" + placeholder="Mowzuk" + type="text" + /> + {/* */} + + + + ); + } + + if (userIsLoading) { + return ( +
    +
    + +
    +
    + ); + } + + return ( + +
    +
    +
    +
    + + { + logout(); + setCurrentPage(1); + }} + className="text-textLight text-[16px] leading-[140%] font-semibold cursor-pointer w-full py-2"> + Ulgamdan çykmak + +
    + +
    + + +
    + {/*
    +
    + + +
    + +
    */} +
    +
    +
    +
    + ); +}; + +export default Dashboard; diff --git a/components/dashboard/FilterTable.tsx b/components/dashboard/FilterTable.tsx new file mode 100644 index 0000000..36509b1 --- /dev/null +++ b/components/dashboard/FilterTable.tsx @@ -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(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 ( +
    +
    +
    +
    +
    +
    + Filtr +
    + {sortData.map((item, i) => ( +
    { + 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} +
    + ))} + +
    +
    setCalendar((prev) => !prev)} + className={clsx( + 'leading-[115%] text-[14px] text-textDarkt cursor-pointer w-fit ', + {}, + )}> + Kalendar +
    + + {calendar && ( + + 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', + )} + /> + + )} + +
    +
    +
    + {datee && smsTableData?.data.length !== 0 && ( + setTimeDate(e.target.value)} + value={timeDate} + className="w-full outline-none px-6 py-3 shadow-tableShadow bg-[#F0F0FA] rounded-full" + /> + )} + + {filterActive ? ( +
    + + + ) + +

    Filteri aýyr

    +
    + ) : null} +
    + +
    +
    + setSearchValue(e.target.value)} + value={searchValue} + className="w-full bg-transparent outline-none" + /> + + {searchFecth ? ( + + ) : null} + +
    +
    setSearchFecth(searchValue)} + className="bg-fillButtonAccentDefault rounded-[0_9999px_9999px_0] px-4 py-[12px] cursor-pointer"> + +
    +
    +
    +
    + ); +}; + +export default FilterTable; diff --git a/components/home/LinkBlock.tsx b/components/home/LinkBlock.tsx new file mode 100644 index 0000000..5cb979b --- /dev/null +++ b/components/home/LinkBlock.tsx @@ -0,0 +1,20 @@ +import Link from 'next/link'; + +interface IProps { + href: string; + color: string; + content: string; +} + +const LinkBlock = ({ color, href, content }: IProps) => { + return ( + + {content} + + ); +}; + +export default LinkBlock; diff --git a/components/home/MainSwiper.tsx b/components/home/MainSwiper.tsx new file mode 100644 index 0000000..f9d2efc --- /dev/null +++ b/components/home/MainSwiper.tsx @@ -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 ; + if (error) return

    {JSON.stringify(error)}

    ; + + return ( +
    + + {data!.data.map((item) => ( + + + + + + ))} + +
    + ); +}; + +export default MainSwiper; diff --git a/components/home/Marque.tsx b/components/home/Marque.tsx new file mode 100644 index 0000000..42337ce --- /dev/null +++ b/components/home/Marque.tsx @@ -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 ; + if (error) return

    {JSON.stringify(error)}

    ; + + return data?.data ? ( +
    + {data.data.length === 0 ? null : ( +
    +
    + Gysga Habarlar +
    + + {data?.data.map((item) => ( +
    + {item.content} +
    + ))} +
    +
    + )} +
    + ) : null; +}; + +export default Marque; diff --git a/components/home/SmallSwiperAdvert.tsx b/components/home/SmallSwiperAdvert.tsx new file mode 100644 index 0000000..4083841 --- /dev/null +++ b/components/home/SmallSwiperAdvert.tsx @@ -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 ; + if (error) return

    {JSON.stringify(error)}

    ; + + return ( +
    + + {/* PAY ATTENTION [data] is wrapped in an array */} + {data?.data.map((item) => ( + + {item.url || item.page_id ? ( + + + + ) : ( +
    + +
    + )} +
    + ))} +
    +
    + ); +}; + +export default SmallSwiperAdvert; diff --git a/components/home/SmallSwiperNews.tsx b/components/home/SmallSwiperNews.tsx new file mode 100644 index 0000000..ba029be --- /dev/null +++ b/components/home/SmallSwiperNews.tsx @@ -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 ; + if (error) return

    {JSON.stringify(error)}

    ; + + return ( +
    + + {/* PAY ATTENTION [data] is wrapped in an array */} + + + + {data?.data.map((item, index) => ( + + +
    + +
    +

    {item.title}

    +
    +
    +
    + +
    + ))} +
    +
    + ); +}; + +export default SmallSwiperNews; diff --git a/components/home/SmallSwiperVideos.tsx b/components/home/SmallSwiperVideos.tsx new file mode 100644 index 0000000..1f83c85 --- /dev/null +++ b/components/home/SmallSwiperVideos.tsx @@ -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 ; + if (error) return

    {JSON.stringify(error)}

    ; + + 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 ( +
    + + {/* PAY ATTENTION [data] is wrapped in an array */} + + + + {data?.data + ? chooseRandomItems(data.data).map((item, index) => ( + + + {item.banner_url ? ( +
    + +
    +

    {item.title}

    +
    +
    +
    + ) : null} + +
    + )) + : null} +
    +
    + ); +}; + +export default SmallSwiperVideos; diff --git a/components/home/SmallSwipers.tsx b/components/home/SmallSwipers.tsx new file mode 100644 index 0000000..dfee84b --- /dev/null +++ b/components/home/SmallSwipers.tsx @@ -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 ( +
    + + + {/* Is not yet in API, static for now */} + + + + + + {/* */} +
    + ); +}; + +export default SmallSwipers; diff --git a/components/home/ThemeSwitch.tsx b/components/home/ThemeSwitch.tsx new file mode 100644 index 0000000..3e9faf7 --- /dev/null +++ b/components/home/ThemeSwitch.tsx @@ -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 ( +
    + setTheme('light')} + style={{ color: theme === 'light' ? '#121268' : '#D9D9D9' }}> + Light + + + setTheme('dark')} + style={{ color: theme === 'dark' ? '#37ABE1' : '#D9D9D9' }}> + Dark + +
    + ); +}; + +export default ThemeSwitch; diff --git a/components/home/Toolbar.tsx b/components/home/Toolbar.tsx new file mode 100644 index 0000000..f102cd1 --- /dev/null +++ b/components/home/Toolbar.tsx @@ -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 ( +
    + + + + + + +
    + ); +}; + +export default Toolbar; diff --git a/components/hox/PresenceAnimator.tsx b/components/hox/PresenceAnimator.tsx new file mode 100644 index 0000000..890ad21 --- /dev/null +++ b/components/hox/PresenceAnimator.tsx @@ -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 ( + + {children} + + ); +}; + +export default PresenceAnimator; diff --git a/components/live/Banner.tsx b/components/live/Banner.tsx new file mode 100644 index 0000000..1991a99 --- /dev/null +++ b/components/live/Banner.tsx @@ -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 ; + if (error) return

    {JSON.stringify(error)}

    ; + + return ( + + ); +}; + +export default Banner; diff --git a/components/news/MainNews.tsx b/components/news/MainNews.tsx new file mode 100644 index 0000000..0c758ee --- /dev/null +++ b/components/news/MainNews.tsx @@ -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 ; + if (error) return

    {JSON.stringify(error)}

    ; + + return ( + +
    + +
    +
    +

    {news.title}

    +
    + + ); +}; + +export default MainNews; diff --git a/components/news/News.tsx b/components/news/News.tsx new file mode 100644 index 0000000..5308e96 --- /dev/null +++ b/components/news/News.tsx @@ -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 ( + +
    + {news.featured_images && news.featured_images[0].path ? ( + + ) : null} +
    +

    {news.published_at}

    +

    {news.title}

    + + ); +}; + +export default News; diff --git a/components/news/NewsGrid.tsx b/components/news/NewsGrid.tsx new file mode 100644 index 0000000..c5a287c --- /dev/null +++ b/components/news/NewsGrid.tsx @@ -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 ; + if (error) return

    {JSON.stringify(error)}

    ; + + return ( +
    + {title ? : null} + {isSlides ? ( + + ) : ( +
    + {data!.pages + .flatMap((data) => data.data) + .map((novelty) => ( + + ))} +
    + )} + + {isExtendable ? ( +
    + fetchNextPage()} + disabled={!hasNextPage} + isFetching={isFetchingNextPage} + /> +
    + ) : null} +
    + ); +}; + +export default NewsGrid; diff --git a/components/news/NewsItem.tsx b/components/news/NewsItem.tsx new file mode 100644 index 0000000..6234bfa --- /dev/null +++ b/components/news/NewsItem.tsx @@ -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 ; + if (error) return

    {JSON.stringify(error)}

    ; + + return ( +
    + +
    + +

    {data?.data.published_at}

    +
    + +
    +
    + {data?.data.featured_images && data.data.featured_images[0].path ? ( + + ) : null} +
    +
    + {/*

    {data!.data.title}

    */} +

    +
    +
    +
    + ); +}; + +export default Item; diff --git a/components/news/NewsSlider.tsx b/components/news/NewsSlider.tsx new file mode 100644 index 0000000..08b4e4f --- /dev/null +++ b/components/news/NewsSlider.tsx @@ -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 | undefined; +} + +const NewsSlider = ({ data }: IProps) => { + return ( +
    + + {data!.pages + .flatMap((data) => data.data) + .map((novelty) => ( + + + + ))} + +
    + ); +}; + +export default NewsSlider; diff --git a/components/quiz/QuizAccordion.tsx b/components/quiz/QuizAccordion.tsx new file mode 100644 index 0000000..98d8772 --- /dev/null +++ b/components/quiz/QuizAccordion.tsx @@ -0,0 +1,247 @@ +'use client'; +import { useContext, useEffect, useState } from 'react'; +import { motion, AnimatePresence } from 'framer-motion'; +import { Queries } from '@/api/queries'; +import Loader from '../Loader'; +import { IQuizQuestionsHistory } from '@/models/quizQuestionHistory.model'; +import { Validator } from '@/utils/validator'; +import { v4 } from 'uuid'; +import QuizContext from '@/context/QuizContext'; + +type TProps = { + finished: string; + questionId: number; +}; + +const QuizAccordion = ({ finished, questionId }: TProps) => { + const [data, setData] = useState(); + const [opened, setOpened] = useState(finished === 'closed' ? false : true); + + const { quizSearchData } = useContext(QuizContext).quizSearchContext; + const { searchActive } = useContext(QuizContext).quizSearchActiveContext; + + useEffect(() => { + if (quizSearchData && Object.values(quizSearchData.data).length != 0 && searchActive) { + setOpened(true); + } else { + setOpened(false); + } + }, [quizSearchData]); + + // useEffect(() => { + // if (finished === 'closed') { + // Queries.getQuizHistory(questionId).then((res) => setData(res)); + // // console.log('Accordion'); + // // const interval = setInterval(() => { + // // Queries.getQuizHistory(questionId).then((res) => setData(res)); + // // }, 60000); + // // return () => clearInterval(interval); + // } + // }, [finished]); + useEffect(() => { + if (opened && !searchActive) { + Queries.getQuizHistory(questionId).then((res) => setData(res)); + } + }, [opened, searchActive]); + + // if (!data) { + // return ; + // } + + return ( +
    + {data || quizSearchData ? ( + + {opened && ( + <> + +
    +
    + +
    +
    + Telefon belgisi +
    +
    + Wagt +
    +
    + Jogap +
    +
    + Ballar +
    +
    + + {data && data?.data.length != 0 ? ( + <> +
    + {data.data.map((user, id) => ( +
    +
    + {id + 1} +
    +
    + +{user.client} +
    +
    + {Validator.parseDate(user.dt)} +
    +
    + {user.msg} +
    +
    + + {user.score} + +
    +
    + ))} +
    + +
    + {data.data.map((user, id) => ( +
    +
    +
    + {id + 1} +
    +
    + +{user.client} +
    + +
    + {user.msg} +
    +
    + + {user.score} + +
    +
    +
    + Wagty: {Validator.parseDate(user.dt)} +
    +
    + ))} +
    + + ) : null} + + {quizSearchData + ? Object.values(quizSearchData.data).map((userQuestion, id) => + userQuestion.question_id === questionId + ? userQuestion.answers.map((answer, answerId) => ( +
    + + +
    +
    +
    + {answer.serial_number_for_correct != null ? ( + + {answer.serial_number_for_correct} + + ) : ( + X + )} +
    +
    + +{answer.client} +
    + +
    + {answer.msg} +
    +
    + + {answer.score} + +
    +
    +
    + Wagty: {Validator.parseDate(answer.dt)} +
    +
    +
    + )) + : null, + ) + : null} +
    + + )} +
    + ) : null} + {finished === 'closed' ? ( + + ) : null} +
    + ); +}; + +export default QuizAccordion; diff --git a/components/quiz/QuizQuestion.tsx b/components/quiz/QuizQuestion.tsx new file mode 100644 index 0000000..9c5f24a --- /dev/null +++ b/components/quiz/QuizQuestion.tsx @@ -0,0 +1,177 @@ +import React, { useContext } from 'react'; +import QuizAccordion from './QuizAccordion'; +import { Validator } from '@/utils/validator'; +import QuizContext from '@/context/QuizContext'; +import { v4 } from 'uuid'; + +type TProps = { + finished: string; + question: string; + startsAt: string; + endsAt: string; + questionId: number; + questionNumber: number; + score: number; +}; + +const numbers = [ + 'Birinji sorag', + 'Ikinji sorag', + 'Üçünji sorag', + 'Dördünji sorag', + 'Bäşinji sorag', + 'Altynjy sorag', + 'Ýedinji sorag', + 'Sekizinji sorag', + 'Dokuzynjy sorag', + 'Onunjy sorag', + 'On Birinji sorag', + 'On Ikinji sorag', + 'On Üçünji sorag', + 'On Dördünji sorag', + 'On Bäşinji sorag', + 'On Altynji sorag', + 'On Ýeddi sorag', + 'On Sekiznji sorag', + 'On Dokuzunji sorag', + 'Ýigrinci sorag', +]; + +const QuizQuestion = ({ + finished, + question, + startsAt, + endsAt, + questionId, + questionNumber, + score, +}: TProps) => { + const { quizSearchData } = useContext(QuizContext).quizSearchContext; + return quizSearchData ? ( + Object.values(quizSearchData.data).map((userQuestion, id) => + userQuestion.question_id === questionId ? ( +
    +
    +
    +

    + {numbers.map((number, id) => (id === questionNumber ? number : ''))}: +

    + +
    +
    +
    + + {finished === 'closed' ? 'ýapyk' : 'açyk'} + +
    +
    + + + + + {Validator.parseDate(startsAt)}-den {Validator.parseDate(endsAt)}-e çenli + +
    +
    + + + + + Dogry jogap üçin +{score} utuk + +
    +
    +
    +

    + «{question}» +

    +
    + + {finished === 'closed' ? ( + + ) : null} +
    + ) : null, + ) + ) : ( +
    +
    +
    +

    + {numbers.map((number, id) => (id === questionNumber ? number : ''))}: +

    + +
    +
    +
    + + {finished === 'closed' ? 'ýapyk' : 'açyk'} + +
    +
    + + + + + {Validator.parseDate(startsAt)}-den {Validator.parseDate(endsAt)}-e çenli + +
    +
    + + + + + Dogry jogap üçin +{score} utuk + +
    +
    +
    +

    + «{question}» +

    +
    + + {finished === 'closed' ? : null} +
    + ); +}; + +export default QuizQuestion; diff --git a/components/quiz/QuizQuestionList.tsx b/components/quiz/QuizQuestionList.tsx new file mode 100644 index 0000000..268f910 --- /dev/null +++ b/components/quiz/QuizQuestionList.tsx @@ -0,0 +1,140 @@ +'use client'; +import QuizQuestion from './QuizQuestion'; +import { Queries } from '@/api/queries'; +import { v4 } from 'uuid'; +import { Dispatch, useContext, useEffect, useState } from 'react'; +import { IQuizQuestions, Question } from '@/models/quizQuestions.model'; +import QuizContext from '@/context/QuizContext'; + +interface IProps { + setQuizFinished: Dispatch; + quizFinished: boolean; + initialQuestionsData: IQuizQuestions; + id: string; + dynamic?: boolean; +} + +const QuizQuestionList = ({ + setQuizFinished, + quizFinished, + initialQuestionsData, + id, + dynamic, +}: IProps) => { + const [data, setData] = useState(initialQuestionsData); + const { quizSearchData } = useContext(QuizContext).quizSearchContext; + const { setQuestionsData } = useContext(QuizContext).quizQuestionsContext; + + // useEffect(() => { + // Queries.getQuizQuestions().then((res) => setData(res)); + // data?.data.questions.map((question) => + // question.status === 'active' ? setQuizFinished(false) : setQuizFinished(true), + // ); + + // if (quizFinished === true) { + // const interval = setInterval(() => { + // Queries.getQuizQuestions().then((res) => { + // setData(res); + // }); + // data?.data.questions.map((question) => + // question.status === 'active' ? setQuizFinished(false) : setQuizFinished(true), + // ); + // }, 15000); + // return () => clearInterval(interval); + // } + // }, []); + + useEffect(() => { + // Queries.getQuizQuestions().then((res) => setData(res)); + + setQuestionsData(initialQuestionsData.data.questions); + + // data?.data.questions.map((question) => + // question.status === 'active' || question.status === 'new' + // ? setQuizFinished(false) + // : setQuizFinished(true), + // ); + + if (!dynamic && quizFinished === false) { + const interval = setInterval(() => { + Queries.getQuizQuestions().then((res) => { + setData(res); + setQuestionsData(res.data.questions); + + res.data.questions.map((question) => + question.status === 'active' || question.status === 'new' + ? setQuizFinished(false) + : setQuizFinished(true), + ); + }); + + // const isActive = data?.data.questions.some( + // (question) => question.status === 'active' || question.status === 'new', + // ); + + // data.data.questions.map((question) => + // question.status === 'active' || question.status === 'new' + // ? setQuizFinished(false) + // : setQuizFinished(true), + // ); + }, 60000); + return () => clearInterval(interval); + } else { + const interval = setInterval(() => { + Queries.getQuiz(id).then((res) => { + setData(res); + setQuestionsData(res.data.questions); + + res.data.questions.map((question) => + question.status === 'active' || question.status === 'new' + ? setQuizFinished(false) + : setQuizFinished(true), + ); + }); + }, 60000); + return () => clearInterval(interval); + } + }, [quizFinished]); + + return ( +
    + {data && !quizSearchData ? ( + data.data.questions.map((question, id) => + question.status != 'new' ? ( + + ) : null, + ) + ) : quizSearchData && Object.values(quizSearchData.data).length === 0 ? ( +

    + Вы не участвовали ни в одном вопросе +

    + ) : data ? ( + data.data.questions.map((question, id) => + question.status != 'new' ? ( + + ) : null, + ) + ) : null} +
    + ); +}; + +export default QuizQuestionList; diff --git a/components/quiz/QuizSearch.tsx b/components/quiz/QuizSearch.tsx new file mode 100644 index 0000000..5323f71 --- /dev/null +++ b/components/quiz/QuizSearch.tsx @@ -0,0 +1,122 @@ +'use client'; +import QuizContext from '@/context/QuizContext'; +import { AnimatePresence, motion } from 'framer-motion'; +import { ChangeEvent, FormEvent, useContext, useState } from 'react'; + +const QuizSearch = ({ quizId }: { quizId: number }) => { + const [phone, setPhone] = useState(''); + + const { setSearchActive } = useContext(QuizContext).quizSearchActiveContext; + + const { setQuizSearchData } = useContext(QuizContext).quizSearchContext; + + const handleCleanSearch = () => { + setQuizSearchData(undefined); + setPhone(''); + setSearchActive(false); + }; + + const handleSearchChange = (event: ChangeEvent) => { + setPhone(event.target.value.trim()); + }; + + const handleKeyPress = (event: React.KeyboardEvent) => { + if (event.key === 'Enter') { + // Prevent the default form submission behavior + event.preventDefault(); + // Call the function to handle the form submission + handleSearchSubmit; + } + }; + + const handleSearchSubmit = async (event: React.FormEvent) => { + event.preventDefault(); + + try { + const response = await fetch(`https://sms.turkmentv.gov.tm/api/quiz/${quizId}/search`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ phone }), + }); + + // Handle the response as needed + + const data = await response.json(); + setQuizSearchData(data); + setSearchActive(true); + } catch (error) {} + }; + + return ( +
    +

    + Öz jogaplaryňyzy görüň +

    +
    +
    +
    + {' '} + + {phone.length != 0 ? ( + + + + + + + + ) : null} +
    + + +

    + Her soragyň aşagynda siziň ugradan jogaplaryňyz görkeziler{' '} +

    +
    +
    + ); +}; + +export default QuizSearch; diff --git a/components/quiz/QuizTable.tsx b/components/quiz/QuizTable.tsx new file mode 100644 index 0000000..3971907 --- /dev/null +++ b/components/quiz/QuizTable.tsx @@ -0,0 +1,77 @@ +'use client'; +import { Queries } from '@/api/queries'; +import splitter from '@/public/splitter.svg'; +import { useQuery } from '@tanstack/react-query'; +import Image from 'next/image'; +import Loader from '../Loader'; +import { v4 } from 'uuid'; +import { Note } from '@/models/quizQuestions.model'; + +interface IProps { + rules: Note[]; + notes: Note[]; +} + +const QuizTable = ({ rules, notes }: IProps) => { + // const { data, error, isFetching } = useQuery({ + // queryKey: ['quiz_questions'], + // queryFn: () => Queries.getQuizQuestions(), + // keepPreviousData: true, + // }); + + // if (isFetching) return ; + // if (error) return

    {JSON.stringify(error)}

    ; + + return ( +
    + {/*
    */} +

    + SMS bäsleşiginiň düzgünleri: +

    +
    +
    + {/*

    + Umumy duzgunleri: +

    */} +
      + {rules.map((rule) => + rule.title ? ( +
    • +
      + +
      + + {rule.title} + +
    • + ) : null, + )} +
    +
    + +
    +

    + Üns beriň! +

    +
      + {notes.map((note) => + note.title ? ( +
    • +
      + +
      + + {note.title} + +
    • + ) : null, + )} +
    +
    +
    + {/*
    */} +
    + ); +}; + +export default QuizTable; diff --git a/components/quiz/QuizWinnerTable.tsx b/components/quiz/QuizWinnerTable.tsx new file mode 100644 index 0000000..3c0ce86 --- /dev/null +++ b/components/quiz/QuizWinnerTable.tsx @@ -0,0 +1,298 @@ +'use client'; +import { Queries } from '@/api/queries'; + +import { v4 } from 'uuid'; +import { useState, useEffect, useContext } from 'react'; +import { IQuizQuestionsWinners } from '@/models/quizQuestionsWinners.model'; +import QuizContext from '@/context/QuizContext'; + +interface IProps { + quizId: number; + quizFinished: boolean; +} + +const QuizWinnerTable = ({ quizId, quizFinished }: IProps) => { + // const [questionsData, setQuestionsData] = useState(); + const [winnersData, setWinnersData] = useState(); + const { questionsData } = useContext(QuizContext).quizQuestionsContext; + + useEffect(() => { + if (quizFinished) { + // Queries.getQuizQuestions().then((res) => { + // setQuestionsData(res); + // }); + Queries.getQuizWinners(quizId).then((res) => { + setWinnersData(res); + }); + } + }, []); + // const { data, error, isFetching } = useQuery({ + // queryKey: ['quiz_questions_winners'], + // queryFn: () => Queries.getQuizWinners(quizId), + // keepPreviousData: true, + // }); + + // if (isFetching) return ; + // if (error) return

    {JSON.stringify(error)}

    ; + + return winnersData?.data.length != 0 ? ( +
    +
    +

    + Bäsleşigiň jemi +

    +
    +
    + {winnersData?.data[0].client_id ? ( +
    + +
    + ) : null} + + {winnersData?.data[0].client.phone ? ( +
    + Gatnaşyjynyň tel. Beligisi +
    + ) : null} + + {winnersData?.data[0].client.answers.length != 0 ? ( +
    + Soraglara näçinji jogap berdi +
    + ) : null} + + {winnersData?.data[0].total_score_of_client ? ( +
    + Jogaplaryň jemi +
    + ) : null} + {winnersData?.data[0].total_score_of_client ? ( +
    + Utuklaryň jemi +
    + ) : null} +
    + +
    + {winnersData?.data.map((winner, id) => ( +
    +
    + {id + 1} +
    + {winnersData.data[0].client.phone ? ( +
    + +{winner.client.phone} +
    + ) : null} + {winnersData.data[0].client.answers.length != 0 ? ( +
    + {questionsData + ? questionsData.map((question) => { + const matchingAnswer = winner.client.answers.find( + (answer) => answer.question_id === question.id, + ); + return ( + + {matchingAnswer && matchingAnswer.serial_number_for_correct != null + ? matchingAnswer.serial_number_for_correct + : matchingAnswer && + matchingAnswer.serial_number_for_correct === null + ? 'X' + : '0'} + + ); + }) + : null} +
    + ) : null} + + {winnersData.data[0].total_score_of_client ? ( +
    + + {winner.correct_answers_time} + +
    + ) : null} + {winnersData.data[0].total_score_of_client ? ( +
    + + {winner.total_score_of_client} + +
    + ) : null} +
    + ))} +
    +
    + +
    +
    + {winnersData?.data[0].client_id ? ( +
    + +
    + ) : null} + + {winnersData?.data[0].client.phone ? ( +
    + Gatnaşyjynyň tel. Beligisi +
    + ) : null} + + {winnersData?.data[0].total_score_of_client ? ( +
    + Балы за быстроту +
    + ) : null} + {winnersData?.data[0].total_score_of_client ? ( +
    + Utuklaryň jemi +
    + ) : null} +
    + +
    + {winnersData?.data.map((winner, id) => ( +
    +
    + {id + 1} +
    + +
    +
    + {winnersData.data[0].client.phone ? ( +
    + +{winner.client.phone} +
    + ) : null} + + {winnersData.data[0].total_score_of_client ? ( +
    + + {winner.correct_answers_time} + +
    + ) : null} + {winnersData.data[0].total_score_of_client ? ( +
    + + {winner.total_score_of_client} + +
    + ) : null} +
    +
    + {winnersData?.data[0].client.answers.length != 0 ? ( +
    + Soraglara näçinji jogap berdi : +
    + ) : null} + {winnersData.data[0].client.answers.length != 0 ? ( +
    + {questionsData + ? questionsData.map((question) => { + const matchingAnswer = winner.client.answers.find( + (answer) => answer.question_id === question.id, + ); + return ( + + {matchingAnswer && + matchingAnswer.serial_number_for_correct != null + ? matchingAnswer.serial_number_for_correct + : matchingAnswer && + matchingAnswer.serial_number_for_correct === null + ? 'X' + : '0'} + + ); + }) + : null} +
    + ) : null} +
    +
    +
    + ))} +
    +
    +
    +
    +

    + Belgileriň düşündirilişi +

    +
    +
    +
    +
    + 100 +
    + + Bäsşeikde gazanylan utuklaryň jemi + +
    +
    +
    + 100 +
    + + + Soraga jogaplaryň tertip belgisiniň jemi + +
    +
    +
    + 1 +
    + + Dogry jogaplara näçinji bolup jogap berdi + +
    +
    +
    + X +
    + + Soraga nädogry jogap berdi + +
    +
    +
    +
    + 0 +
    + Soraga jogap ugratmady +
    +
    +
    +
    + ) : null; +}; + +export default QuizWinnerTable; diff --git a/components/shared/SharedButton.tsx b/components/shared/SharedButton.tsx new file mode 100644 index 0000000..8d24fbd --- /dev/null +++ b/components/shared/SharedButton.tsx @@ -0,0 +1,11 @@ +import React from 'react'; + +const SharedButton = ({ title }: { title: string }) => { + return ( + + ); +}; + +export default SharedButton; diff --git a/components/shop/ShopTable.tsx b/components/shop/ShopTable.tsx new file mode 100644 index 0000000..a4df7f8 --- /dev/null +++ b/components/shop/ShopTable.tsx @@ -0,0 +1,97 @@ +'use client'; +import { useState } from 'react'; + +const ShopTable = () => { + const [activePage, setActivePage] = useState(1); + return ( +
    +
    +
    +

    Показать по:

    + 10 + 50 + 100 +
    + +
    +
    + + № + + + Номер телефона + + + Сообщение + + + Время отправки + +
    +
    +
    + + 1 + + + +993 61 2***67 + + + Первое, второе слово + +
    + 26.01.24г. + 14:56:29 +
    +
    +
    +
    +
    + +
    +

    + Переход по страницам +

    +
    + +

    + 1 +

    + +
    +
    + +
    + ); +}; + +export default ShopTable; diff --git a/components/sms/ProtectedRoute.tsx b/components/sms/ProtectedRoute.tsx new file mode 100644 index 0000000..f4abce6 --- /dev/null +++ b/components/sms/ProtectedRoute.tsx @@ -0,0 +1,34 @@ +'use client'; +// src/components/ProtectedRoute.tsx +import { ReactNode, useEffect, useContext } from 'react'; +import { useRouter } from 'next/navigation'; +import { AuthContext } from '@/context/AuthContext'; + +interface ProtectedRouteProps { + children: ReactNode; +} + +const ProtectedRoute = ({ children }: ProtectedRouteProps) => { + const authContext = useContext(AuthContext); + const router = useRouter(); + + if (!authContext) { + throw new Error('AuthContext must be used within an AuthProvider'); + } + + const { user } = authContext; + + useEffect(() => { + if (!user) { + router.push('/sms/sign_up'); + } + }, [user, router]); + + if (!user) { + return null; + } + + return <>{children}; +}; + +export default ProtectedRoute; diff --git a/components/sms/smsTable/SmsPagination.tsx b/components/sms/smsTable/SmsPagination.tsx new file mode 100644 index 0000000..aabacfd --- /dev/null +++ b/components/sms/smsTable/SmsPagination.tsx @@ -0,0 +1,183 @@ +// Working pagination ========================================================================================================= + +// 'use client'; +// import Loader from '@/components/Loader'; +// import { SmsContext } from '@/context/SmsContext'; +// import clsx from 'clsx'; +// import React, { useContext } from 'react'; + +// const SmsPagination = () => { +// const smsContext = useContext(SmsContext); +// if (!smsContext) { +// throw new Error('smsContext must be used within an AuthProvider'); +// } +// const { setCurrentPage, smsTableData } = smsContext; + +// if (!smsTableData) { +// return ( +//
    +// +//
    +// ); +// } + +// return ( +//
    +//

    Sahypalar

    +//
    +// +// setCurrentPage( +// smsTableData?.meta.current_page !== 1 +// ? smsTableData.meta.current_page - 1 +// : smsTableData.meta.current_page, +// ) +// } +// width="24" +// height="24" +// viewBox="0 0 24 24" +// fill="none" +// xmlns="http://www.w3.org/2000/svg"> +// +// +// +// {smsTableData?.meta.current_page}/{smsTableData?.meta.last_page} +// +// +// setCurrentPage( +// smsTableData?.meta.current_page !== smsTableData.meta.last_page +// ? smsTableData.meta.current_page + 1 +// : smsTableData.meta.last_page, +// ) +// } +// className={clsx('cursor-pointer', { +// 'pointer-events-none cursor-default': +// smsTableData?.meta.current_page === smsTableData?.meta.last_page, +// })} +// width="24" +// height="24" +// viewBox="0 0 24 24" +// fill="none" +// xmlns="http://www.w3.org/2000/svg"> +// +// +//
    +//
    +// ); +// }; + +// export default SmsPagination; + +// Working pagination ================================================================================================= + +'use client'; +import Loader from '@/components/Loader'; +import { SmsContext } from '@/context/SmsContext'; +import clsx from 'clsx'; +import React, { useContext } from 'react'; + +const SmsPagination = () => { + const smsContext = useContext(SmsContext); + if (!smsContext) { + throw new Error('smsContext must be used within an AuthProvider'); + } + const { setCurrentPage, smsTableData } = smsContext; + + if (!smsTableData) { + return ( +
    + +
    + ); + } + + const { current_page, last_page } = smsTableData.meta; + + const generatePageNumbers = () => { + const pages = []; + if (last_page <= 7) { + for (let i = 1; i <= last_page; i++) { + pages.push(i); + } + } else { + pages.push(1); + if (current_page > 4) pages.push('...'); + const startPage = Math.max(2, current_page - 2); + const endPage = Math.min(last_page - 1, current_page + 2); + for (let i = startPage; i <= endPage; i++) { + pages.push(i); + } + if (current_page < last_page - 3) pages.push('...'); + pages.push(last_page); + } + return pages; + }; + + const pages = generatePageNumbers(); + + return ( +
    +

    Sahypalar

    +
    + setCurrentPage(current_page !== 1 ? current_page - 1 : current_page)} + width="24" + height="24" + viewBox="0 0 24 24" + fill="none" + xmlns="http://www.w3.org/2000/svg"> + + + {pages.map((page, index) => ( + typeof page === 'number' && setCurrentPage(page)}> + {page} + + ))} + setCurrentPage(current_page !== last_page ? current_page + 1 : last_page)} + className={clsx('cursor-pointer', { + 'pointer-events-none cursor-default': current_page === last_page, + })} + width="24" + height="24" + viewBox="0 0 24 24" + fill="none" + xmlns="http://www.w3.org/2000/svg"> + + +
    +
    + ); +}; + +export default SmsPagination; diff --git a/components/sms/smsTable/SmsTable.tsx b/components/sms/smsTable/SmsTable.tsx new file mode 100644 index 0000000..b405a94 --- /dev/null +++ b/components/sms/smsTable/SmsTable.tsx @@ -0,0 +1,87 @@ +'iuse client'; +import React, { useContext, useEffect } from 'react'; +import SmsTableHead from './SmsTableHead'; +import SmsTableBody from './SmsTableBody'; +import { SmsContext } from '@/context/SmsContext'; +import Loader from '@/components/Loader'; +import SmsPagination from './SmsPagination'; +import { Queries } from '@/api/queries'; + +const SmsTable = () => { + const smsContext = useContext(SmsContext); + if (!smsContext) { + throw new Error('smsContext must be used within an AuthProvider'); + } + const { + tableIsLoading, + smsData, + smsTableData, + setSmsTableData, + setTableIsLoading, + setCurrentPage, + setIsError, + currentPage, + activeNumber, + searchFecth, + activeSort, + dateValue, + timeDate, + } = smsContext; + + const getMessages = () => { + if (smsData && activeNumber) { + setTableIsLoading(true); + + try { + Queries.getMessages(activeNumber, currentPage, dateValue, activeSort, searchFecth).then( + (res) => { + setSmsTableData(res); + setTableIsLoading(false); + + if (!res.data) { + setTableIsLoading(true); + + setIsError(true); + } + }, + ); + } catch (error) { + setTableIsLoading(true); + + setIsError(true); + } + } + }; + + useEffect(() => { + getMessages(); + }, [smsData, currentPage, activeNumber, searchFecth, activeSort, dateValue]); + + useEffect(() => { + setCurrentPage(1); + }, [activeNumber]); + + if (tableIsLoading) { + return ( +
    + +
    + ); + } + + return smsTableData?.data.length ? ( +
    +
    + + +
    + {smsTableData.meta.last_page > 1 ? : null} +
    + ) : ( +

    + Нет результатов! +

    + ); +}; + +export default SmsTable; diff --git a/components/sms/smsTable/SmsTableBody.tsx b/components/sms/smsTable/SmsTableBody.tsx new file mode 100644 index 0000000..207311b --- /dev/null +++ b/components/sms/smsTable/SmsTableBody.tsx @@ -0,0 +1,35 @@ +'use client'; +import React, { useContext, useEffect, useState } from 'react'; +import SmsTableRow from './SmsTableRow'; + +import { SmsContext } from '@/context/SmsContext'; + +const SmsTableBody = () => { + const smsContext = useContext(SmsContext); + if (!smsContext) { + throw new Error('smsContext must be used within an AuthProvider'); + } + const { smsTableData, currentPage, timeDate } = smsContext; + + console.log(smsTableData?.data[0].dt.slice(11, 15)); + + return ( +
    +
    + {smsTableData?.data + .filter((item) => (timeDate ? item.dt.slice(11, 15).includes(timeDate) : item)) + .map((row, index) => ( + + ))} +
    +
    + ); +}; + +export default SmsTableBody; diff --git a/components/sms/smsTable/SmsTableHead.tsx b/components/sms/smsTable/SmsTableHead.tsx new file mode 100644 index 0000000..cc5fbeb --- /dev/null +++ b/components/sms/smsTable/SmsTableHead.tsx @@ -0,0 +1,22 @@ +import React from 'react'; + +const SmsTableHead = () => { + return ( +
    +
    + ID +
    +
    + Telefon belgi +
    +
    + SMS +
    +
    + Wagty +
    +
    + ); +}; + +export default SmsTableHead; diff --git a/components/sms/smsTable/SmsTableRow.tsx b/components/sms/smsTable/SmsTableRow.tsx new file mode 100644 index 0000000..d3e8601 --- /dev/null +++ b/components/sms/smsTable/SmsTableRow.tsx @@ -0,0 +1,47 @@ +import clsx from 'clsx'; + +interface IProps { + index: number; + number: string; + sms: string; + date: string; +} + +const SmsTableRow = ({ index, number, sms, date }: IProps) => { + const formateDate = (date: string) => { + const parsedDate = date.split(' ')[0].split('-').reverse().join('.'); + const parsedTime = date.split(' ')[1]; + return { + parsedDate, + parsedTime, + }; + }; + + const { parsedDate, parsedTime } = formateDate(date); + + return ( +
    +
    + {index} +
    +
    + +{number} +
    +
    + + {sms} + +
    +
    + {parsedDate}ý. + {parsedTime} +
    +
    + ); +}; + +export default SmsTableRow; diff --git a/components/table/FitlerNumber.tsx b/components/table/FitlerNumber.tsx new file mode 100644 index 0000000..09b22ef --- /dev/null +++ b/components/table/FitlerNumber.tsx @@ -0,0 +1,92 @@ +'use client'; + +import { Queries } from '@/api/queries'; +import Loader from '../Loader'; +import axios, { AxiosError, AxiosPromise } from 'axios'; +import baseUrl from '@/baseUrl'; +import routes from '@/routes'; +import { useContext, useEffect, useState } from 'react'; +import { IMyTvAdmins } from '@/models/sms/my.tv.admins.model'; +import { AuthContext } from '@/context/AuthContext'; +import { SmsContext } from '@/context/SmsContext'; +import clsx from 'clsx'; + +const numbers = [ + { + number: '0801', + }, + { + number: '0802', + }, + { + number: '0803', + }, + { + number: '0804', + }, + { + number: '0805', + }, + { + number: '0806', + }, +]; + +export const FitlerNumber = () => { + const smsContext = useContext(SmsContext); + if (!smsContext) { + throw new Error('smsContext must be used within an AuthProvider'); + } + const { activeNumber, setActiveNumber, smsData, tableIsLoading, setSmsData, setIsError } = + smsContext; + + const getAdmins = () => { + try { + Queries.getAdmins().then((res) => { + setSmsData(res); + setActiveNumber(res.data[0].id); + if (!res.data) { + setIsError(true); + } + }); + } catch (error) { + setIsError(true); + } + }; + + useEffect(() => { + getAdmins(); + }, []); + + if (!smsData) { + return ( +
    + +
    + ); + } + + return ( +
    +
    + Gysga belgi boýunça filtr +
    + +
    + {smsData?.data.map((item) => ( +
    setActiveNumber(item.id)}> + {item.login} +
    + ))} +
    +
    + ); +}; diff --git a/components/table/Search.tsx b/components/table/Search.tsx new file mode 100644 index 0000000..aca3af0 --- /dev/null +++ b/components/table/Search.tsx @@ -0,0 +1,26 @@ +import Image from 'next/image'; + +export const Search = () => { + return ( +
    + +
    + + + +
    +
    + ); +}; diff --git a/components/table/Sort.tsx b/components/table/Sort.tsx new file mode 100644 index 0000000..cf88a91 --- /dev/null +++ b/components/table/Sort.tsx @@ -0,0 +1,31 @@ +export const Sort = () => { + const sortItems = [ + { + view: 'Сначала новые', + }, + { + view: 'Сначала старые ', + }, + { + view: 'Выбрать дату...', + }, + ]; + + return ( +
    +
    + Сортировка +
    + +
    + {sortItems.map((item) => ( +
    + + {item.view} + +
    + ))} +
    +
    + ); +}; diff --git a/components/ui/button.tsx b/components/ui/button.tsx new file mode 100644 index 0000000..0ba4277 --- /dev/null +++ b/components/ui/button.tsx @@ -0,0 +1,56 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const buttonVariants = cva( + "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground hover:bg-primary/90", + destructive: + "bg-destructive text-destructive-foreground hover:bg-destructive/90", + outline: + "border border-input bg-background hover:bg-accent hover:text-accent-foreground", + secondary: + "bg-secondary text-secondary-foreground hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-10 px-4 py-2", + sm: "h-9 rounded-md px-3", + lg: "h-11 rounded-md px-8", + icon: "h-10 w-10", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +) + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button" + return ( + + ) + } +) +Button.displayName = "Button" + +export { Button, buttonVariants } diff --git a/components/ui/calendar.tsx b/components/ui/calendar.tsx new file mode 100644 index 0000000..d91726e --- /dev/null +++ b/components/ui/calendar.tsx @@ -0,0 +1,59 @@ +'use client'; + +import * as React from 'react'; +import { ChevronLeft, ChevronRight } from 'lucide-react'; +import { DayPicker } from 'react-day-picker'; + +import { cn } from '@/lib/utils'; +import { buttonVariants } from '@/components/ui/button'; + +export type CalendarProps = React.ComponentProps; + +function Calendar({ className, classNames, showOutsideDays = true, ...props }: CalendarProps) { + return ( + , + IconRight: ({ ...props }) => , + }} + {...props} + /> + ); +} +Calendar.displayName = 'Calendar'; + +export { Calendar }; diff --git a/components/ui/pagination.tsx b/components/ui/pagination.tsx new file mode 100644 index 0000000..0e4bbf9 --- /dev/null +++ b/components/ui/pagination.tsx @@ -0,0 +1,93 @@ +import * as React from 'react'; +import { ChevronLeft, ChevronRight, MoreHorizontal } from 'lucide-react'; + +import { cn } from '@/lib/utils'; +import { ButtonProps, buttonVariants } from '@/components/ui/button'; + +const Pagination = ({ className, ...props }: React.ComponentProps<'nav'>) => ( +