authentification on lottery working

This commit is contained in:
Kakabay 2024-12-24 00:13:36 +05:00
parent 7f2e6f9850
commit 50155d1dc3
11 changed files with 506 additions and 243 deletions

View File

@ -1,39 +1,39 @@
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 { ILottery } from '@/models/lottery/lottery.model';
import { MarqueeModel } from '@/models/marquee.model';
import { NewsModel, NewsType } 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';
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 { ILotteryResponse } from "@/models/lottery/lottery.model";
import { MarqueeModel } from "@/models/marquee.model";
import { NewsModel, NewsType } 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 },
{ perPage = 8 }: { perPage?: number }
): Promise<NewsModel> {
return await fetch(
`${baseUrl.NEWS_SRC}${routes.news}?locale=tm&count=${perPage}&page=${page}`,
{
next: { revalidate: 3600 },
},
}
).then((res) => res.json().then((res) => res as NewsModel));
}
@ -50,9 +50,12 @@ export class Queries {
}
public static async getHomeBannerSingle3(id: string): Promise<NewsType> {
return await fetch(`https://turkmentv.gov.tm/v2/api/slider?type=small3/${id}`, {
next: { revalidate: 3600 },
}).then((res) => res.json().then((res) => res as NewsType));
return await fetch(
`https://turkmentv.gov.tm/v2/api/slider?type=small3/${id}`,
{
next: { revalidate: 3600 },
}
).then((res) => res.json().then((res) => res as NewsType));
}
public static async getNewsItem(id: string): Promise<NewsItemModel> {
@ -86,8 +89,8 @@ export class Queries {
}
public static async getVideo(id: number): Promise<VideoModel> {
return await fetch(`${baseUrl.MATERIALS_SRC}${routes.video(id)}`).then((res) =>
res.json().then((res) => res as VideoModel),
return await fetch(`${baseUrl.MATERIALS_SRC}${routes.video(id)}`).then(
(res) => res.json().then((res) => res as VideoModel)
);
}
@ -125,9 +128,12 @@ export class Queries {
}
public static async getMarquee(): Promise<MarqueeModel> {
return await fetch(`${baseUrl.MATERIALS_SRC}${routes.marquee}?on_morquee=1`, {
next: { revalidate: 3600 },
}).then((res) => res.json().then((res) => res as MarqueeModel));
return await fetch(
`${baseUrl.MATERIALS_SRC}${routes.marquee}?on_morquee=1`,
{
next: { revalidate: 3600 },
}
).then((res) => res.json().then((res) => res as MarqueeModel));
}
public static async getBanner(): Promise<BannerModel> {
@ -136,10 +142,15 @@ export class Queries {
}).then((res) => res.json().then((res) => res as BannerModel));
}
public static async getLiveDescription(channel: number): Promise<LiveDescriptionModel> {
return await fetch(`${baseUrl.MATERIALS_SRC}${routes.channelItem(channel)}`, {
next: { revalidate: 3600 },
}).then((res) => res.json().then((res) => res as LiveDescriptionModel));
public static async getLiveDescription(
channel: number
): Promise<LiveDescriptionModel> {
return await fetch(
`${baseUrl.MATERIALS_SRC}${routes.channelItem(channel)}`,
{
next: { revalidate: 3600 },
}
).then((res) => res.json().then((res) => res as LiveDescriptionModel));
}
public static async getProperties(): Promise<PropertiesModel> {
@ -149,17 +160,20 @@ export class Queries {
}
public static async getPlans(property_id: number): Promise<PlansModel> {
return await fetch(`${baseUrl.API_SRC}${routes.plans(String(property_id))}`, {
next: { revalidate: 3600 },
}).then((res) => res.json().then((res) => res as PlansModel));
return await fetch(
`${baseUrl.API_SRC}${routes.plans(String(property_id))}`,
{
next: { revalidate: 3600 },
}
).then((res) => res.json().then((res) => res as PlansModel));
}
public static async postAdvert(data: AddPostModel): Promise<Response> {
return await fetch(`${baseUrl.API_SRC}${routes.addPost}`, {
headers: { 'Content-Type': 'application/json' },
cache: 'no-cache',
headers: { "Content-Type": "application/json" },
cache: "no-cache",
body: JSON.stringify(data),
method: 'POST',
method: "POST",
});
}
@ -176,16 +190,26 @@ export class Queries {
}).then((res) => res.json().then((res) => res as IQuizQuestions));
}
public static async getQuizHistory(id: number): Promise<IQuizQuestionsHistory> {
return await fetch(`${baseUrl.QUIZ_SRC}${routes.getQuizQuestionHistory(id)}`, {
next: { revalidate: 3600 },
}).then((res) => res.json().then((res) => res as any));
public static async getQuizHistory(
id: number
): Promise<IQuizQuestionsHistory> {
return await fetch(
`${baseUrl.QUIZ_SRC}${routes.getQuizQuestionHistory(id)}`,
{
next: { revalidate: 3600 },
}
).then((res) => res.json().then((res) => res as any));
}
public static async getQuizWinners(id: number): Promise<IQuizQuestionsWinners> {
return await fetch(`${baseUrl.QUIZ_SRC}${routes.getQuizQuestionsWinners(id)}`, {
next: { revalidate: 3600 },
}).then((res) => res.json().then((res) => res as IQuizQuestionsWinners));
public static async getQuizWinners(
id: number
): Promise<IQuizQuestionsWinners> {
return await fetch(
`${baseUrl.QUIZ_SRC}${routes.getQuizQuestionsWinners(id)}`,
{
next: { revalidate: 3600 },
}
).then((res) => res.json().then((res) => res as IQuizQuestionsWinners));
}
// ======================================================================================
@ -207,7 +231,7 @@ export class Queries {
// Sms ========================================================================================
public static async getAdmins(): Promise<IMyTvAdmins> {
const token = localStorage.getItem('access_token');
const token = localStorage.getItem("access_token");
return await fetch(`${baseUrl.SMS_SRC}${routes.myTvAdmins}`, {
headers: { Authorization: `Bearer ${token}` },
@ -219,31 +243,43 @@ export class Queries {
current_page: number,
dateValue: string,
activeSort: string,
searchFetch: string,
searchFetch: string
): Promise<MessagesByTvAdmin> {
const token = localStorage.getItem('access_token');
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}`,
`${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));
}
// Lottery ================================================================================
public static async getLottery(): Promise<ILottery> {
return await fetch(`${baseUrl.QUIZ_SRC}${routes.lotteryActive}`, {
next: { revalidate: 3600 },
}).then((res) => res.json().then((res) => res as ILottery));
}
public static async getLotteryById(lottery_id: string): Promise<ILottery> {
return await fetch(`${baseUrl.QUIZ_SRC}${routes.lotteryId(lottery_id)}`, {
next: { revalidate: 3600 },
}).then((res) => res.json().then((res) => res as ILottery));
public static async authenticateLottery(
phone: string,
code: string
): Promise<ILotteryResponse> {
return await fetch(`${baseUrl.QUIZ_SRC}${routes.lotteryActive}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
phone: phone,
key: code,
}),
}).then((res) => {
if (!res.ok) {
throw new Error("Authentication failed");
}
return res.json();
});
}
// ============================================================================================

View File

@ -1,138 +0,0 @@
'use client';
import { Queries } from '@/api/queries';
import Loader from '@/components/Loader';
import LotteryWinnersSection from '@/components/lottery/LotteryWinnersSection';
import RollingCounter from '@/components/lottery/RollingCounter/RollingCounter';
import RollingCounterWorking from '@/components/lottery/RollingCounter/RollingCounterWorking';
import LotteryCountDown from '@/components/lottery/countDown/LotteryCountDown';
import LotteryCountDownAllert from '@/components/lottery/countDown/countDownAllert/LotteryCountDownAllert';
import LotteryForm from '@/components/lottery/form/LotteryForm';
import LotteryRulesSection from '@/components/lottery/rules/LotteryRulesSection';
import { ILottery } from '@/models/lottery/lottery.model';
import Image from 'next/image';
import { useEffect, useState } from 'react';
const page = () => {
const [isLoading, setIsLoading] = useState(true);
const [data, setData] = useState<ILottery>();
const [lotteryStatus, setLotteryStatus] = useState<'not-started' | 'started' | 'ended'>(
'not-started',
);
const [currentNumber, setCurrentNumber] = useState('22-22-22-22-22');
// useEffect(() => {
// Queries.getLottery()
// .then((res) => {
// setData(res);
// })
// .finally(() => setIsLoading(false));
// }, []);
// useEffect(() => {
// const timer = setTimeout(() => {
// setCurrentNumber('81-34-52-35-61');
// }, 10000); // 15 seconds
// return () => clearTimeout(timer); // Cleanup on unmount
// }, []);
// if (isLoading) {
// return (
// <div className="w-full h-screen flex justify-center items-center">
// <Loader />
// </div>
// );
// }
return (
<div className="flex flex-col md:gap-[128px] gap-[80px] font-roboto md:pt-[64px] sm:pt-[48px] pt-[40px] pb-[128px] text-lightOnSurface">
{/* <RollingCounter numberString={currentNumber} /> */}
<RollingCounterWorking numberString={currentNumber} />
{data && (
<section className="">
<div className="container">
<div className="flex flex-col md:gap-[32px] gap-[24px]">
<div className="flex flex-col gap-[24px] items-center ">
{data.data.title && (
<h1 className="sm:font-display-1-regular text-[32px] leading-[40px] text-center">
{data.data.title}
</h1>
)}
{data.data.description && (
<p className="max-w-[600px] w-full font-base-regular text-center">
{data.data.description}
</p>
)}
{data.data.sms_code && (
<div className="px-4 py-3 font-base-medium flex items-center gap-2 bg-lightInfoAllertContainer text-lightOnInfoAllertContainer">
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M4.92893 4.92893C6.8043 3.05357 9.34784 2 12 2C14.6522 2 17.1957 3.05357 19.0711 4.92893C20.9464 6.8043 22 9.34784 22 12C22 13.3132 21.7413 14.6136 21.2388 15.8268C20.7362 17.0401 19.9997 18.1425 19.0711 19.0711C18.1425 19.9997 17.0401 20.7362 15.8268 21.2388C14.6136 21.7413 13.3132 22 12 22C10.6868 22 9.38642 21.7413 8.17317 21.2388C6.95991 20.7362 5.85752 19.9997 4.92893 19.0711C4.00035 18.1425 3.26375 17.0401 2.7612 15.8268C2.25866 14.6136 2 13.3132 2 12C2 9.34784 3.05357 6.8043 4.92893 4.92893ZM12 4C9.87827 4 7.84344 4.84285 6.34315 6.34315C4.84285 7.84344 4 9.87827 4 12C4 13.0506 4.20693 14.0909 4.60896 15.0615C5.011 16.0321 5.60028 16.914 6.34315 17.6569C7.08601 18.3997 7.96793 18.989 8.93853 19.391C9.90914 19.7931 10.9494 20 12 20C13.0506 20 14.0909 19.7931 15.0615 19.391C16.0321 18.989 16.914 18.3997 17.6569 17.6569C18.3997 16.914 18.989 16.0321 19.391 15.0615C19.7931 14.0909 20 13.0506 20 12C20 9.87827 19.1571 7.84344 17.6569 6.34315C16.1566 4.84285 14.1217 4 12 4ZM11 9C11 8.44772 11.4477 8 12 8H12.01C12.5623 8 13.01 8.44772 13.01 9C13.01 9.55228 12.5623 10 12.01 10H12C11.4477 10 11 9.55228 11 9ZM10 12C10 11.4477 10.4477 11 11 11H12C12.5523 11 13 11.4477 13 12V15C13.5523 15 14 15.4477 14 16C14 16.5523 13.5523 17 13 17H12C11.4477 17 11 16.5523 11 16V13C10.4477 13 10 12.5523 10 12Z"
fill="#1E3A5F"
/>
</svg>
<span>SMS-kod: {data.data.sms_code}</span>
</div>
)}
</div>
{data.data.image && (
<div className="md:mb-8 sm:mb-[40px] mb-[16px]">
<Image
src={data.data.image}
width={1416}
height={177}
alt="banner"
className="rounded-[12px] object-cover h-[177px]"
/>
</div>
)}
<div className="bg-lightSurfaceContainer flex flex-col rounded-[12px]">
<LotteryCountDown
startDate={'2024-12-16 18:09:00'}
endDate={'2024-12-25 16:00:00'}
lotteryStatus={lotteryStatus}
setLotteryStatus={setLotteryStatus}
/>
<LotteryForm />
</div>
</div>
</div>
</section>
)}
<section className="flex flex-col gap-[40px]">
{lotteryStatus === 'not-started' && (
<div className="container">
<LotteryCountDown
startDate={'2024-12-16 18:09:00'}
endDate={'2024-12-25 16:00:00'}
lotteryStatus={lotteryStatus}
setLotteryStatus={setLotteryStatus}
/>
</div>
)}
<LotteryCountDownAllert
startDate={'2024-12-16 18:09:00'}
endDate={'2024-12-25 16:00:00'}
lotteryStatus={lotteryStatus}
setLotteryStatus={setLotteryStatus}
/>
<LotteryWinnersSection />
</section>
<LotteryRulesSection />
</div>
);
};
export default page;

View File

@ -0,0 +1,31 @@
"use client";
import LotteryAuthForm from "@/components/lottery/auth/LotteryAuthForm";
import { useEffect } from "react";
import { useRouter } from "next/navigation";
import { useLotteryAuth } from "@/store/useLotteryAuth";
const LotteryAuthPage = () => {
const router = useRouter();
const { isAuthenticated, logout } = useLotteryAuth();
useEffect(() => {
console.log("Auth page - Authentication state:", isAuthenticated);
if (isAuthenticated) {
console.log("Auth page - Redirecting to lottery...");
router.push("/lottery");
}
}, [isAuthenticated, router]);
useEffect(() => {
logout();
}, [logout]);
return (
<div className="flex justify-center items-center min-h-[50vh] py-[200px]">
<LotteryAuthForm />
</div>
);
};
export default LotteryAuthPage;

103
app/(main)/lottery/page.tsx Normal file
View File

@ -0,0 +1,103 @@
"use client";
import { useState } from "react";
import { useLotteryAuth } from "@/store/useLotteryAuth";
import ProtectedRoute from "@/components/lottery/auth/ProtectedRoute";
import { Queries } from "@/api/queries";
import Loader from "@/components/Loader";
import LotteryWinnersSection from "@/components/lottery/LotteryWinnersSection";
import RollingCounter from "@/components/lottery/RollingCounter/RollingCounter";
import RollingCounterWorking from "@/components/lottery/RollingCounter/RollingCounterWorking";
import LotteryCountDown from "@/components/lottery/countDown/LotteryCountDown";
import LotteryCountDownAllert from "@/components/lottery/countDown/countDownAllert/LotteryCountDownAllert";
import LotteryForm from "@/components/lottery/form/LotteryForm";
import LotteryRulesSection from "@/components/lottery/rules/LotteryRulesSection";
import Image from "next/image";
import { useRouter } from "next/navigation";
import { useEffect } from "react";
const Page = () => {
const { lotteryData } = useLotteryAuth();
const [lotteryStatus, setLotteryStatus] = useState<
"not-started" | "started" | "ended"
>("not-started");
const [currentNumber, setCurrentNumber] = useState("22-22-22-22-22");
return (
<ProtectedRoute>
<div className="flex flex-col md:gap-[128px] gap-[80px] font-roboto md:pt-[64px] sm:pt-[48px] pt-[40px] pb-[128px] text-lightOnSurface">
<RollingCounterWorking numberString={currentNumber} />
{lotteryData && (
<section className="">
<div className="container">
<div className="flex flex-col md:gap-[32px] gap-[24px]">
<div className="flex flex-col gap-[24px] items-center ">
{lotteryData.data.title && (
<h1 className="sm:font-display-1-regular text-[32px] leading-[40px] text-center">
{lotteryData.data.title}
</h1>
)}
{lotteryData.data.description && (
<p className="max-w-[600px] w-full font-base-regular text-center">
{lotteryData.data.description}
</p>
)}
{lotteryData.data.sms_code && (
<div className="px-4 py-3 font-base-medium flex items-center gap-2 bg-lightInfoAllertContainer text-lightOnInfoAllertContainer">
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M4.92893 4.92893C6.8043 3.05357 9.34784 2 12 2C14.6522 2 17.1957 3.05357 19.0711 4.92893C20.9464 6.8043 22 9.34784 22 12C22 13.3132 21.7413 14.6136 21.2388 15.8268C20.7362 17.0401 19.9997 18.1425 19.0711 19.0711C18.1425 19.9997 17.0401 20.7362 15.8268 21.2388C14.6136 21.7413 13.3132 22 12 22C10.6868 22 9.38642 21.7413 8.17317 21.2388C6.95991 20.7362 5.85752 19.9997 4.92893 19.0711C4.00035 18.1425 3.26375 17.0401 2.7612 15.8268C2.25866 14.6136 2 13.3132 2 12C2 9.34784 3.05357 6.8043 4.92893 4.92893ZM12 4C9.87827 4 7.84344 4.84285 6.34315 6.34315C4.84285 7.84344 4 9.87827 4 12C4 13.0506 4.20693 14.0909 4.60896 15.0615C5.011 16.0321 5.60028 16.914 6.34315 17.6569C7.08601 18.3997 7.96793 18.989 8.93853 19.391C9.90914 19.7931 10.9494 20 12 20C13.0506 20 14.0909 19.7931 15.0615 19.391C16.0321 18.989 16.914 18.3997 17.6569 17.6569C18.3997 16.914 18.989 16.0321 19.391 15.0615C19.7931 14.0909 20 13.0506 20 12C20 9.87827 19.1571 7.84344 17.6569 6.34315C16.1566 4.84285 14.1217 4 12 4ZM11 9C11 8.44772 11.4477 8 12 8H12.01C12.5623 8 13.01 8.44772 13.01 9C13.01 9.55228 12.5623 10 12.01 10H12C11.4477 10 11 9.55228 11 9ZM10 12C10 11.4477 10.4477 11 11 11H12C12.5523 11 13 11.4477 13 12V15C13.5523 15 14 15.4477 14 16C14 16.5523 13.5523 17 13 17H12C11.4477 17 11 16.5523 11 16V13C10.4477 13 10 12.5523 10 12Z"
fill="#1E3A5F"
/>
</svg>
<span>SMS-kod: {lotteryData.data.sms_code}</span>
</div>
)}
</div>
{lotteryData.data.image && (
<div className="md:mb-8 sm:mb-[40px] mb-[16px]">
<Image
src={lotteryData.data.image}
width={1416}
height={177}
alt="banner"
className="rounded-[12px] object-cover h-[177px]"
/>
</div>
)}
{lotteryStatus === "not-started" && (
<LotteryCountDown
startDate={"2024-12-16 18:09:00"}
endDate={"2024-12-25 16:00:00"}
lotteryStatus={lotteryStatus}
setLotteryStatus={setLotteryStatus}
/>
)}
</div>
</div>
</section>
)}
<LotteryRulesSection />
<section className="flex flex-col gap-[40px]">
{lotteryStatus === "ended" || lotteryStatus === "started" ? (
<LotteryWinnersSection />
) : null}
</section>
</div>
</ProtectedRoute>
);
};
export default Page;

View File

@ -1,10 +1,10 @@
import SmsForm from '@/components/prizes/SmsForm';
import React from 'react';
import SmsForm from "@/components/prizes/SmsForm";
import React from "react";
const page = () => {
return (
<div className="">
<div className="flex justify-center items-center min-h-[50vh]">
<div className="flex justify-center items-center min-h-[50vh] py-[200px]">
<SmsForm />
</div>
</div>

View File

@ -0,0 +1,132 @@
"use client";
import { Queries } from "@/api/queries";
import { useState, FormEvent, useEffect } from "react";
import { useRouter } from "next/navigation";
import { useLotteryAuth } from "@/store/useLotteryAuth";
const LotteryAuthForm = () => {
const [phone, setPhone] = useState("");
const [code, setCode] = useState("");
const [error, setError] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [shouldRedirect, setShouldRedirect] = useState(false);
const router = useRouter();
const setAuth = useLotteryAuth((state) => state.setAuth);
// Phone validation function
const validatePhone = (value: string) => {
const phoneRegex = /^99363\d{6}$/;
return phoneRegex.test(value);
};
// Code validation function
const validateCode = (value: string) => {
const codeRegex = /^\d-\d{10}$/;
return codeRegex.test(value);
};
const handleSubmit = async (e: FormEvent) => {
e.preventDefault();
setError(null);
console.log("Form submitted");
if (!validatePhone(phone)) {
setError("Telefon belgisi nädogry formatda");
return;
}
if (!validateCode(code)) {
setError("Kod nädogry formatda");
return;
}
setIsLoading(true);
try {
console.log("Making API request...");
const response = await Queries.authenticateLottery(phone, code);
console.log("API Response:", response);
setAuth(response);
console.log("Auth state set");
router.replace("/lottery");
} catch (err) {
console.error("Authentication error:", err);
setError("Telefon belgisi ýa-da kod nädogry");
} finally {
setIsLoading(false);
}
};
useEffect(() => {
if (shouldRedirect) {
console.log("Redirect effect triggered");
router.push("/lottery");
}
}, [shouldRedirect, router]);
const handlePhoneChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value.replace(/\D/g, ""); // Remove non-digits
if (value.length <= 11) {
// Limit to 11 digits (99363 + 6 digits)
setPhone(value);
}
};
const handleCodeChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
if (value.length <= 12) {
// Limit to 12 characters (X-XXXXXXXXXX)
setCode(value);
}
};
return (
<form
onSubmit={handleSubmit}
className="bg-lightSurfaceContainer rounded-[24px] p-[40px] w-[530px] flex flex-col gap-[24px]"
>
<h1 className="text-display3 font-[500] leading-display3">
Lotereýa giriş
</h1>
<div className="flex flex-col gap-[16px]">
<div className="flex flex-col gap-[8px]">
<input
type="tel"
value={phone}
onChange={handlePhoneChange}
className="px-[16px] py-[12px] bg-lightPrimaryContainer rounded-[12px] outline-none text-lightOnSurfaceVariant text-textSmall leading-textSmall"
placeholder="99363XXXXXX"
required
/>
</div>
<div className="flex flex-col gap-[8px]">
<input
type="text"
value={code}
onChange={handleCodeChange}
className="px-[16px] py-[12px] bg-lightPrimaryContainer rounded-[12px] outline-none text-lightOnSurfaceVariant text-textSmall leading-textSmall"
placeholder="5-0102030408"
required
/>
</div>
{error && (
<p className="text-lightError text-textSmall leading-textSmall">
{error}
</p>
)}
</div>
<button
type="submit"
disabled={isLoading || !phone || !code}
className="text-textLarge leading-textLarge py-[12px] w-full flex justify-center items-center rounded-[12px] bg-lightPrimary font-medium text-lightOnPrimary disabled:opacity-50"
>
{isLoading ? "Ýüklenilýär..." : "Giriş"}
</button>
</form>
);
};
export default LotteryAuthForm;

View File

@ -0,0 +1,46 @@
"use client";
import { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import { useLotteryAuth } from "@/store/useLotteryAuth";
import Loader from "@/components/Loader";
interface ProtectedRouteProps {
children: React.ReactNode;
}
const ProtectedRoute = ({ children }: ProtectedRouteProps) => {
const router = useRouter();
const { isAuthenticated } = useLotteryAuth();
const [isHydrated, setIsHydrated] = useState(false);
useEffect(() => {
setIsHydrated(true);
}, []);
useEffect(() => {
if (isHydrated && !isAuthenticated) {
router.replace("/lottery/auth");
}
}, [isHydrated, isAuthenticated, router]);
if (!isHydrated) {
return (
<div className="w-full h-screen flex justify-center items-center">
<Loader />
</div>
);
}
if (!isAuthenticated) {
return (
<div className="w-full h-screen flex justify-center items-center">
<Loader />
</div>
);
}
return <>{children}</>;
};
export default ProtectedRoute;

View File

@ -1,13 +1,15 @@
'use client';
"use client";
import { calculateTimeLeft } from '@/lib/hooks/useCalculateTimeLeft';
import React, { useState, useEffect, Dispatch, SetStateAction } from 'react';
import { calculateTimeLeft } from "@/lib/hooks/useCalculateTimeLeft";
import React, { useState, useEffect, Dispatch, SetStateAction } from "react";
interface LotteryCountDownProps {
startDate: string; // Event start date in "YYYY-MM-DD HH:mm:ss" format
endDate: string; // Event end date in "YYYY-MM-DD HH:mm:ss" format
lotteryStatus: string;
setLotteryStatus: Dispatch<SetStateAction<'not-started' | 'started' | 'ended'>>;
setLotteryStatus: Dispatch<
SetStateAction<"not-started" | "started" | "ended">
>;
}
const LotteryCountDown: React.FC<LotteryCountDownProps> = ({
@ -24,19 +26,27 @@ const LotteryCountDown: React.FC<LotteryCountDownProps> = ({
useEffect(() => {
const timer = setInterval(() => {
if (lotteryStatus === 'not-started') {
if (lotteryStatus === "not-started") {
const timeToStart = calculateTimeLeft(startDate);
setTimeLeft(timeToStart);
if (timeToStart.hours === 0 && timeToStart.minutes === 0 && timeToStart.seconds === 0) {
setLotteryStatus('started'); // Update status to "started"
if (
timeToStart.hours === 0 &&
timeToStart.minutes === 0 &&
timeToStart.seconds === 0
) {
setLotteryStatus("started"); // Update status to "started"
}
} else if (lotteryStatus === 'started') {
} else if (lotteryStatus === "started") {
const timeToEnd = calculateTimeLeft(endDate);
setTimeLeft(timeToEnd);
if (timeToEnd.hours === 0 && timeToEnd.minutes === 0 && timeToEnd.seconds === 0) {
setLotteryStatus('ended'); // Update status to "finished"
if (
timeToEnd.hours === 0 &&
timeToEnd.minutes === 0 &&
timeToEnd.seconds === 0
) {
setLotteryStatus("ended"); // Update status to "finished"
}
}
}, 1000);
@ -49,14 +59,14 @@ const LotteryCountDown: React.FC<LotteryCountDownProps> = ({
return (
<div className="bg-lightPrimaryContainer sm:p-6 p-2 flex flex-col w-full md:gap-2 rounded-[12px] text-lightOnPrimaryContainer">
<h3 className="text-center md:font-heading-1-regular text-[32px] leading-[40px] text-lightOnSurface">
{lotteryStatus === 'started'
? 'Bije dowam edýär'
: lotteryStatus === 'ended'
? 'Bije tamamlandy'
: 'Bije'}
{lotteryStatus === "started"
? "Bije dowam edýär"
: lotteryStatus === "ended"
? "Bije tamamlandy"
: "Bije"}
</h3>
{/* LotteryCountDown */}
{lotteryStatus === 'not-started' && (
{lotteryStatus === "not-started" && (
<div className="flex items-center sm:gap-6 gap-2 justify-between">
<div className="flex flex-col items-center justify-center flex-1 p-6">
<h3 className="md:text-[80px] sm:text-[56px] text-[28px] md:leading-[88px] sm:leading-[64px] leading-[36px] -tracking-[1%]">
@ -99,11 +109,11 @@ const LotteryCountDown: React.FC<LotteryCountDownProps> = ({
<div className="flex items-center justify-center text-lightOnSurfaceVariant md:font-heading-1-regular md:text-[20px] sm:text-[18px] sm:leading-[28px] text-[14px] leading-[20px]">
<span>
{lotteryStatus === 'not-started'
? '- den başlar'
: lotteryStatus === 'started'
? 'girmek üçin aşakda kodyňyzy giriziň'
: 'netijeleri görmek üçin aşakda kodyňyzy giriziň'}
{lotteryStatus === "not-started"
? "- den başlar"
: lotteryStatus === "started"
? "girmek üçin aşakda kodyňyzy giriziň"
: "netijeleri görmek üçin aşakda kodyňyzy giriziň"}
</span>
</div>
</div>

View File

@ -1,4 +1,8 @@
import { useLotteryAuth } from "@/store/useLotteryAuth";
const LotteryRulesSection = () => {
const { lotteryData } = useLotteryAuth();
return (
<section>
<div className="container">
@ -13,7 +17,7 @@ const LotteryRulesSection = () => {
</h3>
<ul className="list-disc flex flex-col md:gap-4 gap-2 pl-[16px]">
{Array(5)
.fill(' ')
.fill(" ")
.map((item, i) => (
<li className="font-small-regular" key={i}>
Ilkinji we dogry jogap beren sanawda ilkinji ýeri eýelýär
@ -23,10 +27,12 @@ const LotteryRulesSection = () => {
</div>
<div className="flex flex-col md:gap-4 gap-2 bg-lightSurfaceContainer py-4 md:px-8 px-6 rounded-[12px] w-full">
<h3 className="md:font-heading-5-regular text-[20px] leading-[24px]">Üns beriň:</h3>
<h3 className="md:font-heading-5-regular text-[20px] leading-[24px]">
Üns beriň:
</h3>
<ul className="list-disc flex flex-col md:gap-4 gap-2 pl-[16px]">
{Array(1)
.fill(' ')
.fill(" ")
.map((item, i) => (
<li className="font-small-regular" key={i}>
SMS = 1 manat

View File

@ -1,8 +1,12 @@
export interface ILottery {
data: Data;
export interface ILotteryWinner {
no: number;
client: string;
dt: string;
winner_no: number;
ticket: string;
}
export interface Data {
export interface ILotteryData {
id: number;
title: string;
description: string;
@ -10,12 +14,10 @@ export interface Data {
start_time: string;
end_time: string;
sms_code: string;
winners: Winner[];
winners: ILotteryWinner[];
}
export interface Winner {
no: number;
client: string;
dt: string;
winner_no: number;
export interface ILotteryResponse {
data: ILotteryData;
user_lottery_numbers: string[];
}

35
store/useLotteryAuth.ts Normal file
View File

@ -0,0 +1,35 @@
import { create } from "zustand";
import { persist, createJSONStorage } from "zustand/middleware";
import { ILotteryResponse } from "@/models/lottery/lottery.model";
interface LotteryAuthState {
isAuthenticated: boolean;
lotteryData: ILotteryResponse | null;
setAuth: (data: ILotteryResponse) => void;
clearAuth: () => void;
logout: () => void;
}
export const useLotteryAuth = create<LotteryAuthState>()(
persist(
(set) => ({
isAuthenticated: false,
lotteryData: null,
setAuth: (data) => set({ isAuthenticated: true, lotteryData: data }),
clearAuth: () => set({ isAuthenticated: false, lotteryData: null }),
logout: () => {
console.log("Logging out from lottery...");
set({ isAuthenticated: false, lotteryData: null });
// Could add additional cleanup here if needed
},
}),
{
name: "lottery-auth-storage",
storage: createJSONStorage(() => localStorage),
partialize: (state) => ({
isAuthenticated: state.isAuthenticated,
lotteryData: state.lotteryData,
}),
}
)
);