fix quiz/active mobile version table & new logic in lottery route

This commit is contained in:
Ilgeldi 2025-02-08 14:02:21 +05:00
parent aba13f527f
commit fd6a0daf8e
36 changed files with 1264 additions and 618 deletions

34
api/index.ts Normal file
View File

@ -0,0 +1,34 @@
"use server";
import baseUrl from "@/baseUrl";
import routes from "@/routes";
import { cookies } from "next/headers";
export async function authenticateLottery(
phone: string,
code: string,
) {
try {
const res = await fetch(`${baseUrl.QUIZ_SRC}${routes.lotteryActive}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
phone: phone,
key: code,
}),
});
if (!res.ok) {
console.log("Authentication failed");
return undefined;
}
const result = await res.json();
return result;
} catch (err) {
console.log(err);
return undefined;
}
}

View File

@ -22,7 +22,7 @@ import { VideoModel } from "@/models/video.model";
import { VideosModel } from "@/models/videos.model"; import { VideosModel } from "@/models/videos.model";
import { IVote } from "@/models/vote.model"; import { IVote } from "@/models/vote.model";
import routes from "@/routes"; import routes from "@/routes";
import { CloudFog } from "lucide-react"; import { cookies } from "next/headers";
export class Queries { export class Queries {
public static async getNews( public static async getNews(
@ -259,28 +259,26 @@ export class Queries {
).then((res) => res.json().then((res) => res as MessagesByTvAdmin)); ).then((res) => res.json().then((res) => res as MessagesByTvAdmin));
} }
// Lottery ================================================================================
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();
});
}
// ============================================================================================ // ============================================================================================
} }
export const getTossData = async ({
type,
id,
}: {
type: string;
id: string;
}) => {
try {
const res = await fetch(`${baseUrl.QUIZ_SRC}${routes.tossId(type, id)}`);
if (!res.ok) {
return undefined;
}
const result = await res.json();
return result;
} catch (err) {
console.log(err);
}
};

View File

@ -1,8 +1,6 @@
import Buble from "@/components/Buble";
import Footer from "@/components/Footer"; import Footer from "@/components/Footer";
import MobileMenu from "@/components/MobileMenu"; import MobileMenu from "@/components/MobileMenu";
import Nav from "@/components/Nav"; import Nav from "@/components/Nav";
import GlobalContext from "@/context/GlobalContext";
import MainProvider from "@/providers/MainProvider"; import MainProvider from "@/providers/MainProvider";
interface IProps { interface IProps {
@ -13,11 +11,9 @@ const RootLayout = ({ children }: IProps) => {
return ( return (
<div className="z-20 relative"> <div className="z-20 relative">
<MainProvider> <MainProvider>
{/* <Buble /> */} <div className="bg-white dark:bg-black transition-all min-h-screen flex flex-col">
<div className="bg-white dark:bg-black transition-all h-full">
<h1 className="hidden">Turkmen TV</h1>
<Nav /> <Nav />
<main className="min-h-[50vh]">{children}</main> <main className="flex-1">{children}</main>
<Footer /> <Footer />
<MobileMenu /> <MobileMenu />
</div> </div>

View File

@ -1,15 +1,13 @@
'use client'; import LotteryAuthForm from "@/components/lottery/auth/LotteryAuthForm";
import LotteryAuthForm from '@/components/lottery/auth/LotteryAuthForm';
const LotteryAuthPage = () => { const LotteryAuthPage = () => {
return ( return (
<div className="container"> <div className="container">
<div className="flex justify-center items-center min-h-[50vh] py-[200px]"> <div className="flex justify-center items-center py-[200px]">
<LotteryAuthForm /> <LotteryAuthForm />
</div> </div>
</div> </div>
); );
}; };
export default LotteryAuthPage; export default LotteryAuthPage;

View File

@ -1,114 +1,16 @@
"use client"; import { authenticateLottery } from "@/api";
import { useEffect, useState } from "react";
import { useLotteryAuth } from "@/store/useLotteryAuth";
import ProtectedRoute from "@/components/lottery/auth/ProtectedRoute";
import LotteryHeader from "@/components/lottery/LotteryHeader";
import LotteryWinnersSection from "@/components/lottery/LotteryWinnersSection";
import LotteryRulesSection from "@/components/lottery/rules/LotteryRulesSection";
import LotteryCountDown from "@/components/lottery/countDown/LotteryCountDown";
import { Queries } from "@/api/queries";
import Link from "next/link";
import { useRouter } from "next/navigation";
import Loader from "@/components/Loader"; import Loader from "@/components/Loader";
import LotteryMain from "@/components/lottery/LotteryMain";
import { cookies } from "next/headers";
import { redirect } from "next/navigation";
import React, { Suspense } from "react";
const LotteryPage = () => { const Page = async () => {
const { lotteryData, setAuth } = useLotteryAuth();
const [status, setStatus] = useState<"not-started" | "started" | "ended">(
"not-started"
);
const [isLoading, setIsLoading] = useState(true);
const router = useRouter();
useEffect(() => {
const checkAuth = async () => {
// ✅ Check credentials from localStorage
const phone = localStorage.getItem("lotteryPhone");
const code = localStorage.getItem("lotteryCode");
if (phone && code) {
try {
// ✅ Authenticate using stored credentials
const response = await Queries.authenticateLottery(phone, code);
if (response.errorMessage) {
// If authentication fails, redirect to the auth page
console.log("redirecting form lottery/");
router.replace("/lottery/auth");
} else {
// ✅ Set the authenticated state
setAuth(response, phone, code);
setIsLoading(false);
}
} catch (err) {
console.error("Authentication failed:", err);
router.replace("/lottery/auth");
}
} else {
// Redirect to the auth page if no credentials are found
router.replace("/lottery/auth");
}
};
checkAuth();
}, [router, setAuth]);
if (isLoading && !lotteryData?.data) {
<div className="flex w-full h-[90vh] justify-center items-center">
<Loader />
</div>;
}
return ( return (
<ProtectedRoute> <Suspense fallback={<Loader />}>
{lotteryData?.data && ( <LotteryMain />
<div className="flex flex-col md:gap-[128px] gap-[80px] font-roboto md:pt-[64px] sm:pt-[48px] pt-[40px] ms:pb-[128px] pb-[80px] text-lightOnSurface"> </Suspense>
{lotteryData && (
<div className="flex flex-col sm:gap-[64px] gap-[40px]">
<LotteryHeader
title={lotteryData.data.title}
description={lotteryData.data.description}
image={lotteryData.data.image}
smsCode={lotteryData.data.sms_code}
startDate={lotteryData.data.start_time}
/>
{status === "not-started" ? (
<div className="container">
<LotteryCountDown
lotteryStatus={status}
setLotteryStatus={setStatus}
endDate={lotteryData.data.end_time}
startDate={lotteryData.data.start_time}
/>
</div>
) : null}
</div>
)}
<LotteryRulesSection />
<div className="flex flex-col gap-10">
{lotteryData && (status === "ended" || status === "started") && (
<LotteryWinnersSection lotteryStatus={status} />
)}
<div className="w-full">
<div className="container">
<Link
href="/lottery/auth"
className="sm:text-textLarge sm:leading-textLarge text-[16px] rounded-full leading-[24px] sm:py-[12px] py-[8px] w-full flex justify-center items-center border-2 border-lightPrimary hover:bg-lightPrimary font-medium text-lightPrimary hover:text-lightOnPrimary disabled:opacity-50 transition-all duration-300"
>
Çykmak
</Link>
</div>
</div>
</div>
</div>
)}
</ProtectedRoute>
); );
}; };
export default LotteryPage; export default Page;

View File

@ -0,0 +1,13 @@
import LotteryAuthForm from "@/components/lottery/auth/LotteryAuthForm";
const LotteryAuthPage = () => {
return (
<div className="container">
<div className="flex justify-center items-center min-h-[50vh] py-[200px]">
{/* <LotteryAuthForm /> */}
</div>
</div>
);
};
export default LotteryAuthPage;

View File

@ -0,0 +1,91 @@
"use client";
import { useEffect, useState } from "react";
import { useLotteryAuth } from "@/store/useLotteryAuth";
import ProtectedRoute from "@/components/lottery/auth/ProtectedRoute";
import LotteryHeader from "@/components/lottery/LotteryHeader";
import LotteryWinnersSection from "@/components/lottery/LotteryWinnersSection";
import LotteryRulesSection from "@/components/lottery/rules/LotteryRulesSection";
import LotteryCountDown from "@/components/lottery/countDown/LotteryCountDown";
import { Queries } from "@/api/queries";
import Link from "next/link";
import { useRouter } from "next/navigation";
import Loader from "@/components/Loader";
import { authenticateLottery } from "@/api";
import { ILotteryResponse } from "@/models/lottery/lottery.model";
const LotteryPage = () => {
const [lotteryData, setData] = useState<ILotteryResponse>();
const [status, setStatus] = useState<"not-started" | "started" | "ended">(
"not-started"
);
const [isLoading, setIsLoading] = useState(true);
const router = useRouter();
useEffect(() => {
const checkAuth = async () => {
const res = await authenticateLottery();
setData(res);
};
checkAuth();
}, []);
if (isLoading && !lotteryData?.data) {
<div className="flex w-full h-[90vh] justify-center items-center">
<Loader />
</div>;
}
return (
<ProtectedRoute>
{lotteryData?.data && (
<div className="flex flex-col md:gap-[128px] gap-[80px] font-roboto md:pt-[64px] sm:pt-[48px] pt-[40px] ms:pb-[128px] pb-[80px] text-lightOnSurface">
{lotteryData && (
<div className="flex flex-col sm:gap-[64px] gap-[40px]">
<LotteryHeader
title={lotteryData.data.title}
description={lotteryData.data.description}
image={lotteryData.data.image}
smsCode={lotteryData.data.sms_code}
startDate={lotteryData.data.start_time}
/>
{status === "not-started" ? (
<div className="container">
<LotteryCountDown
lotteryStatus={status}
setLotteryStatus={setStatus}
endDate={lotteryData.data.end_time}
startDate={lotteryData.data.start_time}
/>
</div>
) : null}
</div>
)}
<LotteryRulesSection />
<div className="flex flex-col gap-10">
{lotteryData && (status === "ended" || status === "started") && (
<LotteryWinnersSection lotteryStatus={status} />
)}
<div className="w-full">
<div className="container">
<Link
href="/lottery/auth"
className="sm:text-textLarge sm:leading-textLarge text-[16px] rounded-full leading-[24px] sm:py-[12px] py-[8px] w-full flex justify-center items-center border-2 border-lightPrimary hover:bg-lightPrimary font-medium text-lightPrimary hover:text-lightOnPrimary disabled:opacity-50 transition-all duration-300"
>
Çykmak
</Link>
</div>
</div>
</div>
</div>
)}
</ProtectedRoute>
);
};
export default LotteryPage;

View File

@ -1,7 +1,5 @@
'use client'; 'use client';
import { Queries } from '@/api/queries'; import { Queries } from '@/api/queries';
import Loader from '@/components/Loader';
import QuizQuestion from '@/components/quiz/QuizQuestion';
import QuizQuestionList from '@/components/quiz/QuizQuestionList'; import QuizQuestionList from '@/components/quiz/QuizQuestionList';
import QuizSearch from '@/components/quiz/QuizSearch'; import QuizSearch from '@/components/quiz/QuizSearch';
import QuizTable from '@/components/quiz/QuizTable'; import QuizTable from '@/components/quiz/QuizTable';

30
app/(main)/toss/page.tsx Normal file
View File

@ -0,0 +1,30 @@
import Loader from "@/components/Loader";
import TossPage from "@/components/toss";
import { notFound } from "next/navigation";
import React, { Suspense } from "react";
type SearchParams = Promise<{ [key: string]: string }>;
const Page = async (props: { searchParams: SearchParams }) => {
const searchParams = await props.searchParams;
let type: "bije" | "cekilis";
if (searchParams?.cekilis) {
type = "cekilis";
} else if (searchParams?.bije) {
type = "bije";
} else {
notFound();
}
return (
<Suspense fallback={<Loader />}>
<TossPage
type={type}
id={searchParams?.cekilis ? searchParams?.cekilis : searchParams?.bije}
/>
</Suspense>
);
};
export default Page;

View File

@ -5,24 +5,24 @@
@layer base { @layer base {
:root { :root {
--background: 0 0% 100%; --background: 0 0% 100%;
--foreground: 222.2 84% 4.9%; --foreground: 0 0% 3.9%;
--card: 0 0% 100%; --card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%; --card-foreground: 0 0% 3.9%;
--popover: 0 0% 100%; --popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%; --popover-foreground: 0 0% 3.9%;
--primary: 222.2 47.4% 11.2%; --primary: 0 0% 9%;
--primary-foreground: 210 40% 98%; --primary-foreground: 0 0% 98%;
--secondary: 210 40% 96.1%; --secondary: 0 0% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%; --secondary-foreground: 0 0% 9%;
--muted: 210 40% 96.1%; --muted: 0 0% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%; --muted-foreground: 0 0% 45.1%;
--accent: 210 40% 96.1%; --accent: 0 0% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%; --accent-foreground: 0 0% 9%;
--destructive: 0 84.2% 60.2%; --destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%; --destructive-foreground: 0 0% 98%;
--border: 214.3 31.8% 91.4%; --border: 0 0% 89.8%;
--input: 214.3 31.8% 91.4%; --input: 0 0% 89.8%;
--ring: 222.2 84% 4.9%; --ring: 0 0% 3.9%;
--radius: 0.5rem; --radius: 0.5rem;
--chart-1: 12 76% 61%; --chart-1: 12 76% 61%;
--chart-2: 173 58% 39%; --chart-2: 173 58% 39%;
@ -32,25 +32,25 @@
} }
.dark { .dark {
--background: 222.2 84% 4.9%; --background: 0 0% 3.9%;
--foreground: 210 40% 98%; --foreground: 0 0% 98%;
--card: 222.2 84% 4.9%; --card: 0 0% 3.9%;
--card-foreground: 210 40% 98%; --card-foreground: 0 0% 98%;
--popover: 222.2 84% 4.9%; --popover: 0 0% 3.9%;
--popover-foreground: 210 40% 98%; --popover-foreground: 0 0% 98%;
--primary: 210 40% 98%; --primary: 0 0% 98%;
--primary-foreground: 222.2 47.4% 11.2%; --primary-foreground: 0 0% 9%;
--secondary: 217.2 32.6% 17.5%; --secondary: 0 0% 14.9%;
--secondary-foreground: 210 40% 98%; --secondary-foreground: 0 0% 98%;
--muted: 217.2 32.6% 17.5%; --muted: 0 0% 14.9%;
--muted-foreground: 215 20.2% 65.1%; --muted-foreground: 0 0% 63.9%;
--accent: 217.2 32.6% 17.5%; --accent: 0 0% 14.9%;
--accent-foreground: 210 40% 98%; --accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%; --destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%; --destructive-foreground: 0 0% 98%;
--border: 217.2 32.6% 17.5%; --border: 0 0% 14.9%;
--input: 217.2 32.6% 17.5%; --input: 0 0% 14.9%;
--ring: 212.7 26.8% 83.9%; --ring: 0 0% 83.1%;
--chart-1: 220 70% 50%; --chart-1: 220 70% 50%;
--chart-2: 160 60% 45%; --chart-2: 160 60% 45%;
--chart-3: 30 80% 55%; --chart-3: 30 80% 55%;
@ -59,21 +59,6 @@
} }
} }
/* @layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
} */
/* <<<<<<< HEAD * {
scroll-behavior: smooth;
}
=======>>>>>>>2c1eddb85d62e4709470605eb8df7458c2d6c37e body, */
* { * {
scroll-behavior: smooth; scroll-behavior: smooth;
} }
@ -357,10 +342,6 @@ big {
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.2); /* Add depth */ box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.2); /* Add depth */
} }
.span.flash {
animation: flash 1s infinite;
}
@keyframes dots-flash { @keyframes dots-flash {
0%, 0%,
100% { 100% {

View File

@ -1,17 +1,21 @@
{ {
"$schema": "https://ui.shadcn.com/schema.json", "$schema": "https://ui.shadcn.com/schema.json",
"style": "default", "style": "new-york",
"rsc": true, "rsc": true,
"tsx": true, "tsx": true,
"tailwind": { "tailwind": {
"config": "tailwind.config.ts", "config": "tailwind.config.js",
"css": "app/globals.css", "css": "app/globals.css",
"baseColor": "slate", "baseColor": "neutral",
"cssVariables": true, "cssVariables": true,
"prefix": "" "prefix": ""
}, },
"aliases": { "aliases": {
"components": "@/components", "components": "@/components",
"utils": "@/lib/utils" "utils": "@/lib/utils",
} "ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"iconLibrary": "lucide"
} }

View File

@ -49,14 +49,6 @@ const Nav = () => {
/> />
</Link> </Link>
<ul className="md:flex gap-5 items-center justify-start hidden"> <ul className="md:flex gap-5 items-center justify-start hidden">
{/* <li>
<Link
className="block text-lg text-black transition-all font-roboto font-bold dark:text-white"
href={'/news'}
style={path.includes('news') ? { color: '#FFAB48' } : {}}>
Habarlar
</Link>
</li> */}
<li> <li>
<Link <Link
href={'/treasury'} href={'/treasury'}

View File

@ -1,9 +1,8 @@
'use client'; "use client";
import { useEffect, useState } from "react";
import { useState, useEffect } from 'react'; import ReactConfetti from "react-confetti";
import ReactConfetti from 'react-confetti'; import { useWindowSize } from "react-use";
import { useWindowSize } from 'react-use'; import { useMediaQuery } from "usehooks-ts";
import { useMediaQuery } from 'usehooks-ts';
const Confetti = ({ const Confetti = ({
numberOfPieces = 200, numberOfPieces = 200,
@ -12,27 +11,31 @@ const Confetti = ({
numberOfPieces?: number; numberOfPieces?: number;
showConfetti: boolean; showConfetti: boolean;
}) => { }) => {
const [recycle, setRecycle] = useState<boolean>(true);
const { width, height } = useWindowSize(); const { width, height } = useWindowSize();
const colors = [ const colors = [
'linear-gradient(45deg, #5D5D72, #8589DE)', "linear-gradient(45deg, #5D5D72, #8589DE)",
'linear-gradient(45deg, #E1E0FF, #575992)', "linear-gradient(45deg, #E1E0FF, #575992)",
'#8589DE', "#8589DE",
'#575992', "#575992",
'#E1E0FF', "#E1E0FF",
'#FF3131', "#FF3131",
]; ];
const mobile = useMediaQuery('(max-width: 426px)'); const mobile = useMediaQuery("(max-width: 426px)");
useEffect(() => {
setTimeout(() => setRecycle(false), 30000);
}, []);
return ( return (
<div className="fixed top-0 left-0 z-50"> <div className="fixed top-0 left-0 z-50">
<ReactConfetti <ReactConfetti
width={width} width={width}
height={height} height={height}
recycle={showConfetti} recycle={recycle}
numberOfPieces={mobile ? numberOfPieces / 3 : numberOfPieces} numberOfPieces={mobile ? 200 / 3 : 200}
tweenDuration={500} tweenDuration={500}
// run={true}
colors={colors} colors={colors}
/> />
</div> </div>

View File

@ -0,0 +1,74 @@
import LotteryHeader from "@/components/lottery/LotteryHeader";
import LotteryRulesSection from "@/components/lottery/rules/LotteryRulesSection";
import LotteryCountDown from "@/components/lottery/countDown/LotteryCountDown";
import Link from "next/link";
import { authenticateLottery } from "@/api";
import { redirect } from "next/navigation";
import { cookies } from "next/headers";
import { getLotteryStatus } from "@/lib/actions";
import LotteryWinners from "./LotteryWinners";
async function getData() {
const cookieStore = await cookies();
const phone = cookieStore.get("phoneNumber");
const key = cookieStore.get("key");
if (phone?.value && key?.value) {
const res = await authenticateLottery(phone.value, key.value);
return res;
} else {
redirect("/lottery/auth");
}
}
const LotteryMain = async () => {
const lotteryData = await getData();
const status = await getLotteryStatus(
lotteryData?.data.start_time,
lotteryData?.data.end_time
);
return lotteryData?.errorMessage ? (
<div className="flex flex-1 w-full justify-center items-center">
<h1 className="text-[50px]">{lotteryData.errorMessage}</h1>
</div>
) : (
<div className="flex flex-col md:gap-[128px] gap-[80px] font-roboto md:pt-[64px] sm:pt-[48px] pt-[40px] ms:pb-[128px] pb-[80px] text-lightOnSurface">
<div className="flex flex-col sm:gap-[64px] gap-[40px]">
<LotteryHeader
title={lotteryData.data.title}
description={lotteryData.data.description}
image={lotteryData.data.image}
smsCode={lotteryData.data.sms_code}
startDate={lotteryData.data.start_time}
/>
{status === "Upcoming" && (
<div className="container">
<LotteryCountDown
lotteryStatus={status}
endDate={lotteryData.data.end_time}
startDate={lotteryData.data.start_time}
/>
</div>
)}
</div>
<LotteryRulesSection data={lotteryData} />
<div className="flex flex-col gap-10">
<LotteryWinners data={lotteryData} lotteryStatus={status} />
<div className="w-full">
<div className="container">
<Link
href="/lottery/auth"
className="sm:text-textLarge sm:leading-textLarge text-[16px] rounded-full leading-[24px] sm:py-[12px] py-[8px] w-full flex justify-center items-center border-2 border-lightPrimary hover:bg-lightPrimary font-medium text-lightPrimary hover:text-lightOnPrimary disabled:opacity-50 transition-all duration-300"
>
Çykmak
</Link>
</div>
</div>
</div>
</div>
);
};
export default LotteryMain;

View File

@ -0,0 +1,27 @@
"use client";
import { useLotteryStatus } from "@/store/store";
import React, { useEffect } from "react";
import LotteryWinnersSection from "./LotteryWinnersSection";
import { ILotteryResponse } from "@/models/lottery/lottery.model";
const LotteryWinners = ({
data,
lotteryStatus,
}: {
data: ILotteryResponse;
lotteryStatus: "Upcoming" | "Ongoing" | "Finished";
}) => {
const { status, setStatus } = useLotteryStatus();
useEffect(() => {
setStatus(lotteryStatus);
}, []);
return (
(status === "Finished" || status === "Ongoing") && (
<LotteryWinnersSection data={data} />
)
);
};
export default LotteryWinners;

View File

@ -11,16 +11,10 @@ import { AnimatePresence, motion } from "framer-motion";
const WEBSOCKET_URL = "wss://sms.turkmentv.gov.tm/ws/lottery?dst="; const WEBSOCKET_URL = "wss://sms.turkmentv.gov.tm/ws/lottery?dst=";
const SLOT_COUNTER_DURATION = 30000; const SLOT_COUNTER_DURATION = 30000;
const LotteryWinnersSection = ({ const LotteryWinnersSection = ({ data }: { data: any }) => {
lotteryStatus,
}: {
lotteryStatus: string;
}) => {
const [winners, setWinners] = useState<LotteryWinnerDataSimplified[]>([]); const [winners, setWinners] = useState<LotteryWinnerDataSimplified[]>([]);
const [currentNumber, setCurrentNumber] = useState<string>("00-00-00-00-00"); const [currentNumber, setCurrentNumber] = useState<string>("00-00-00-00-00");
const [isConfettiActive, setIsConfettiActive] = useState(false); const [isConfettiActive, setIsConfettiActive] = useState(false);
const { lotteryData } = useLotteryAuth();
const [winnerSelectingStatus, setWinnerSelectingStatus] = useState< const [winnerSelectingStatus, setWinnerSelectingStatus] = useState<
"not-selected" | "is-selecting" | "selected" "not-selected" | "is-selecting" | "selected"
>("not-selected"); >("not-selected");
@ -28,52 +22,19 @@ const LotteryWinnersSection = ({
useState<LotteryWinnerDataSimplified | null>(null); useState<LotteryWinnerDataSimplified | null>(null);
const [topText, setTopText] = useState<string>("Bije az wagtdan başlaýar"); const [topText, setTopText] = useState<string>("Bije az wagtdan başlaýar");
const [bottomText, setBottomText] = useState<string>(""); const [bottomText, setBottomText] = useState<string>("");
const [messageQueue, setMessageQueue] = useState< const [messageQueue, setMessageQueue] = useState<
LotteryWinnerDataSimplified[] LotteryWinnerDataSimplified[]
>([]); // Queue for incoming WebSocket messages >([]); // Queue for incoming WebSocket messages
const [isProcessing, setIsProcessing] = useState<boolean>(false); // Track if a message is being processed const [isProcessing, setIsProcessing] = useState<boolean>(false); // Track if a message is being processed
const [startNumber, setStartNumber] = useState("00,00,00,00,00");
const { wsStatus, subscribeToMessages } = useWebsocketLottery( const { wsStatus, subscribeToMessages } = useWebsocketLottery(
`${WEBSOCKET_URL}${lotteryData?.data.sms_number}` `${WEBSOCKET_URL}${data?.data.sms_number}`
); );
// Simulate WebSocket message for testing
const simulateMessage = () => {
const dummyWinner: LotteryWinnerDataSimplified = {
phone: `9936${Math.floor(10000000 + Math.random() * 90000000)}`, // Generate random client number
winner_no: winners.length + 1, // Increment winner number
ticket: `${Math.floor(Math.random() * 99)
.toString()
.padStart(2, "0")}-${Math.floor(Math.random() * 99)
.toString()
.padStart(2, "0")}-${Math.floor(Math.random() * 99)
.toString()
.padStart(2, "0")}-${Math.floor(Math.random() * 99)
.toString()
.padStart(2, "0")}-${Math.floor(Math.random() * 99)
.toString()
.padStart(2, "0")}`, // Generate random ticket
};
console.log("📩 Simulated Message:", dummyWinner); // Log the simulated message
setMessageQueue((prevQueue) => [...prevQueue, dummyWinner]);
};
// useEffect(() => {
// const interval = setInterval(() => {
// simulateMessage();
// }, 20000); // Trigger every 10 seconds
// return () => clearInterval(interval); // Clean up interval on unmount
// }, []);
// Initialize winners from lottery data // Initialize winners from lottery data
useEffect(() => { useEffect(() => {
console.log("🎟️ Lottery Data:", lotteryData); if (data?.data?.winners.length > 0) {
const simplifiedWinners = data.data.winners.map((winner: any) => ({
if (lotteryData && lotteryData.data.winners.length > 0) {
const simplifiedWinners = lotteryData.data.winners.map((winner) => ({
phone: winner.client, phone: winner.client,
winner_no: winner.winner_no, winner_no: winner.winner_no,
ticket: winner.ticket, ticket: winner.ticket,
@ -84,20 +45,19 @@ const LotteryWinnersSection = ({
setWinnerSelectingStatus("selected"); setWinnerSelectingStatus("selected");
setTopText(`${lastWinner.winner_no}-nji ýeňiji`); setTopText(`${lastWinner.winner_no}-nji ýeňiji`);
setBottomText(lastWinner.phone); setBottomText(lastWinner.phone);
setStartNumber(lastWinner.ticket.replace(/-/g, ","));
setIsConfettiActive(true); setIsConfettiActive(true);
} }
}, [lotteryData]); }, [data]);
// Subscribe to WebSocket messages // Subscribe to WebSocket messages
useEffect(() => { useEffect(() => {
const unsubscribe = subscribeToMessages((event) => { const unsubscribe = subscribeToMessages((event) => {
try { try {
const newWinner: LotteryWinnerDataSimplified = JSON.parse(event.data); const newWinner: LotteryWinnerDataSimplified = JSON.parse(event.data);
console.log("📩 WebSocket Message Received:", newWinner); // Log the parsed message
// Add new message to the queue // Add new message to the queue
setMessageQueue((prevQueue) => { setMessageQueue((prevQueue) => {
console.log("📥 Adding to Queue:", newWinner);
return [...prevQueue, newWinner]; return [...prevQueue, newWinner];
}); });
} catch (error) { } catch (error) {
@ -110,8 +70,6 @@ const LotteryWinnersSection = ({
// Process queue when a new message is added // Process queue when a new message is added
useEffect(() => { useEffect(() => {
console.log("📋 Current Message Queue:", messageQueue);
if (!isProcessing && messageQueue.length > 0) { if (!isProcessing && messageQueue.length > 0) {
processQueue(); processQueue();
} }
@ -124,10 +82,8 @@ const LotteryWinnersSection = ({
setIsProcessing(true); // Lock processing setIsProcessing(true); // Lock processing
const message = messageQueue[0]; // Get the first message in the queue const message = messageQueue[0]; // Get the first message in the queue
console.log("⚙️ Processing Message:", message); // Debug Log 4: Log the message being processed
try { try {
await handleMessage(message); // Process the message await handleMessage(message);
} catch (error) { } catch (error) {
console.error("Error processing message:", error); console.error("Error processing message:", error);
} }
@ -138,7 +94,7 @@ const LotteryWinnersSection = ({
// Handle the logic for processing a single WebSocket message // Handle the logic for processing a single WebSocket message
const handleMessage = async (winner: LotteryWinnerDataSimplified) => { const handleMessage = async (winner: LotteryWinnerDataSimplified) => {
console.log("⬇️ Updating Top and Bottom Text:", winner); // Debug Log 5: Log winner data before setting states setStartNumber("00,00,00,00,00");
setIsConfettiActive(false); setIsConfettiActive(false);
setTopText(`${winner.winner_no}-nji ýeňiji saýlanýar`); setTopText(`${winner.winner_no}-nji ýeňiji saýlanýar`);
setBottomText("..."); setBottomText("...");
@ -152,7 +108,6 @@ const LotteryWinnersSection = ({
// Finalize winner selection // Finalize winner selection
setTopText(`${winner.winner_no}-nji ýeňiji`); setTopText(`${winner.winner_no}-nji ýeňiji`);
setBottomText(winner.phone); setBottomText(winner.phone);
console.log("⬇️ Finalized Bottom Text:", winner.phone); // Debug Log 6: Log the final bottomText update
setWinnerSelectingStatus("selected"); setWinnerSelectingStatus("selected");
setIsConfettiActive(true); setIsConfettiActive(true);
@ -172,16 +127,6 @@ const LotteryWinnersSection = ({
)} )}
<Confetti showConfetti={isConfettiActive} numberOfPieces={300} /> <Confetti showConfetti={isConfettiActive} numberOfPieces={300} />
{/* Simmulation Button */}
{/* <div className="w-full flex justify-center py-4">
<button
onClick={simulateMessage}
className="px-4 py-2 bg-blue-500 text-white rounded"
>
Simulate Message
</button>
</div> */}
<div className="container"> <div className="container">
<div <div
className="flex flex-col items-center rounded-[32px] gap-[40px]" className="flex flex-col items-center rounded-[32px] gap-[40px]"
@ -234,7 +179,10 @@ const LotteryWinnersSection = ({
<div className="z-10"> <div className="z-10">
{currentNumber && ( {currentNumber && (
<LotterySlotCounter numberString={currentNumber} /> <LotterySlotCounter
numberString={currentNumber}
startNumber={startNumber}
/>
)} )}
</div> </div>
<div className="flex gap-6 rounded-[12px] flex-1 w-full items-center justify-center sm:pb-[62px] pb-[32px] px-4"> <div className="flex gap-6 rounded-[12px] flex-1 w-full items-center justify-center sm:pb-[62px] pb-[32px] px-4">

View File

@ -1,140 +1,115 @@
"use client"; "use client";
import { Queries } from "@/api/queries"; import { useState } from "react";
import { useState, FormEvent } from "react";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { useLotteryAuth } from "@/store/useLotteryAuth"; import { useForm } from "react-hook-form";
import z from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { Form, FormField, FormItem, FormMessage } from "@/components/ui/form";
import { FormControl, FormLabel } from "@mui/material";
import { authenticateLottery } from "@/api";
const lotteryAuthSchema = z.object({
phoneNumber: z.string().regex(/^993\d{8}$/, {
message: "Dogry telefon belgisini girizin",
}),
key: z.string().regex(/^.+-\d{10}$/, {
message: "Dogry acar sozuni girizin",
}),
});
const LotteryAuthForm = () => { const LotteryAuthForm = () => {
const [phone, setPhone] = useState(""); const form = useForm<z.infer<typeof lotteryAuthSchema>>({
const [code, setCode] = useState(""); resolver: zodResolver(lotteryAuthSchema),
});
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const router = useRouter(); const router = useRouter();
const setAuth = useLotteryAuth((state) => state.setAuth);
const validatePhone = (value: string) => {
const phoneRegex = /^993\d{8}$/;
const isValid = phoneRegex.test(value);
return isValid;
};
const validateCode = (value: string) => {
const codeRegex = /^.+-\d{10}$/; // Any characters before "-", exactly 10 digits after
const isValid = codeRegex.test(value);
return isValid;
};
const handleSubmit = async (e: FormEvent) => {
e.preventDefault();
setError(null);
if (!validatePhone(phone)) {
setError("Telefon belgisi nädogry");
return;
}
if (!validateCode(code)) {
setError("Açar nädogry");
return;
}
async function onSubmit(values: z.infer<typeof lotteryAuthSchema>) {
const { phoneNumber, key } = values;
setIsLoading(true); setIsLoading(true);
try { try {
const response = await Queries.authenticateLottery(phone, code); const response = await authenticateLottery(phoneNumber, key);
if (response.errorMessage) { if (response.errorMessage) {
setError(response.errorMessage); setError(response.errorMessage);
} else { } else {
localStorage.setItem("lotteryPhone", phone); console.log(response);
localStorage.setItem("lotteryCode", code); document.cookie = `phoneNumber=${phoneNumber};path=/`;
document.cookie = `key=${key};path=/`;
setAuth(response, phone, code);
router.replace("/lottery"); router.replace("/lottery");
} }
} catch (err) { } catch (err) {
console.error("Authentication error:", err);
setError("Telefon belgisi ýa-da açar nädogry"); setError("Telefon belgisi ýa-da açar nädogry");
} finally { } finally {
setIsLoading(false); setIsLoading(false);
} }
}; }
const handlePhoneChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value.replace(/\\D/g, "");
if (value.length <= 11) {
setPhone(value);
}
};
const handleCodeChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setCode(value);
// if (value.length <= 12) {
// }
};
return ( return (
<form <Form {...form}>
onSubmit={handleSubmit} <form
className="bg-lightSurfaceContainer rounded-[24px] md:p-[40px] sm:p-[24px] p-[16px] w-[530px] flex flex-col gap-[24px]" onSubmit={form.handleSubmit(onSubmit)}
> className="bg-lightSurfaceContainer rounded-[24px] md:p-[40px] sm:p-[24px] p-[16px] w-[530px] flex flex-col gap-[24px]"
<h1 className="md:text-display3 sm:text-[32px] text-[24px] font-[500] md:leading-display3">
Giriş
</h1>
<div className="flex flex-col gap-[16px]">
<div className="flex flex-col gap-[8px]">
<label
htmlFor="phone"
className="font-base-medium text-lightOnSurface cursor-pointer"
>
Telefon
</label>
<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
id="phone"
/>
</div>
<div className="flex flex-col gap-[8px]">
<label
htmlFor="code"
className="font-base-medium text-lightOnSurface cursor-pointer"
>
Açar
</label>
<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="C5-0105639808"
required
id="code"
/>
</div>
{error && (
<p className="text-lightError text-textSmall leading-textSmall">
{error}
</p>
)}
</div>
<button
type="submit"
disabled={isLoading || !phone || !code}
className="sm:text-textLarge sm:leading-textLarge text-[16px] leading-[24px] sm:py-[12px] py-[8px] w-full flex justify-center items-center rounded-[12px] bg-lightPrimary font-medium text-lightOnPrimary disabled:opacity-50"
> >
{isLoading ? "Ýüklenilýär..." : "Giriş"} <h1 className="md:text-display3 sm:text-[32px] text-[24px] font-[500] md:leading-display3">
</button> Giriş
</form> </h1>
<div className="flex flex-col gap-[16px]">
<FormField
control={form.control}
name="phoneNumber"
render={({ field }) => (
<FormItem className="flex flex-col gap-[8px]">
<FormLabel className="font-base-medium text-lightOnSurface">
Telefon
</FormLabel>
<FormControl>
<input
{...field}
className="px-[16px] py-[12px] bg-lightPrimaryContainer rounded-[12px] outline-none text-lightOnSurfaceVariant text-textSmall leading-textSmall"
placeholder="99363XXXXXX"
/>
</FormControl>
<FormMessage className="text-red-500" />
</FormItem>
)}
/>
<FormField
control={form.control}
name="key"
render={({ field }) => (
<FormItem className="flex flex-col gap-[8px]">
<FormLabel className="font-base-medium text-lightOnSurface">
Açar
</FormLabel>
<FormControl>
<input
{...field}
className="px-[16px] py-[12px] bg-lightPrimaryContainer rounded-[12px] outline-none text-lightOnSurfaceVariant text-textSmall leading-textSmall"
placeholder="C5-0105639808"
/>
</FormControl>
<FormMessage className="text-red-500" />
</FormItem>
)}
/>
{error && (
<p className="text-lightError text-textSmall leading-textSmall">
{error}
</p>
)}
</div>
<button
type="submit"
className="sm:text-textLarge sm:leading-textLarge text-[16px] leading-[24px] sm:py-[12px] py-[8px] w-full flex justify-center items-center rounded-[12px] bg-lightPrimary font-medium text-lightOnPrimary disabled:opacity-50"
>
{isLoading ? "Ýüklenilýär..." : "Giriş"}
</button>
</form>
</Form>
); );
}; };

View File

@ -1,34 +1,31 @@
"use client"; "use client";
import { calculateTimeLeft } from "@/lib/hooks/useCalculateTimeLeft"; import { calculateTimeLeft } from "@/lib/hooks/useCalculateTimeLeft";
import { useLotteryStatus } from "@/store/store";
import React, { useState, useEffect, Dispatch, SetStateAction } from "react"; import React, { useState, useEffect, Dispatch, SetStateAction } from "react";
interface LotteryCountDownProps { interface LotteryCountDownProps {
startDate: string; // Event start date in "YYYY-MM-DD HH:mm:ss" format 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 endDate: string; // Event end date in "YYYY-MM-DD HH:mm:ss" format
lotteryStatus: string; lotteryStatus: "Upcoming" | "Ongoing" | "Finished";
setLotteryStatus: Dispatch<
SetStateAction<"not-started" | "started" | "ended">
>;
} }
const LotteryCountDown: React.FC<LotteryCountDownProps> = ({ const LotteryCountDown: React.FC<LotteryCountDownProps> = ({
startDate, startDate,
endDate, endDate,
lotteryStatus, lotteryStatus,
setLotteryStatus,
}) => { }) => {
const [timeLeft, setTimeLeft] = useState({ const [timeLeft, setTimeLeft] = useState({
hours: 0, hours: 0,
minutes: 0, minutes: 0,
seconds: 0, seconds: 0,
}); });
const { status, setStatus } = useLotteryStatus();
console.log(lotteryStatus);
useEffect(() => { useEffect(() => {
setStatus(lotteryStatus);
const timer = setInterval(() => { const timer = setInterval(() => {
if (lotteryStatus === "not-started") { if (lotteryStatus === "Upcoming") {
const timeToStart = calculateTimeLeft(startDate); const timeToStart = calculateTimeLeft(startDate);
setTimeLeft(timeToStart); setTimeLeft(timeToStart);
@ -37,9 +34,9 @@ const LotteryCountDown: React.FC<LotteryCountDownProps> = ({
timeToStart.minutes === 0 && timeToStart.minutes === 0 &&
timeToStart.seconds === 0 timeToStart.seconds === 0
) { ) {
setLotteryStatus("started"); // Update status to "started" setStatus("Ongoing"); // Update status to "started"
} }
} else if (lotteryStatus === "started") { } else if (lotteryStatus === "Ongoing") {
const timeToEnd = calculateTimeLeft(endDate); const timeToEnd = calculateTimeLeft(endDate);
setTimeLeft(timeToEnd); setTimeLeft(timeToEnd);
@ -48,32 +45,32 @@ const LotteryCountDown: React.FC<LotteryCountDownProps> = ({
timeToEnd.minutes === 0 && timeToEnd.minutes === 0 &&
timeToEnd.seconds === 0 timeToEnd.seconds === 0
) { ) {
setLotteryStatus("ended"); // Update status to "finished" setStatus("Finished"); // Update status to "finished"
} }
} }
}, 1000); }, 1000);
return () => clearInterval(timer); // Clean up interval on component unmount return () => clearInterval(timer); // Clean up interval on component unmount
}, [startDate, endDate, lotteryStatus, setLotteryStatus]); }, [startDate, endDate, lotteryStatus]);
return ( return (
<div className="bg-lightPrimaryContainer sm:p-6 py-3 flex flex-col w-full md:gap-2 rounded-[12px] sm:gap-3 gap-0 text-lightOnPrimaryContainer"> <div className="bg-lightPrimaryContainer sm:p-6 py-3 flex flex-col w-full md:gap-2 rounded-[12px] sm:gap-3 gap-0 text-lightOnPrimaryContainer">
<h3 className="text-center md:font-heading-1-regular sm:text-[32px] sm:leading-[40px] text-[20px] leading-[28px] text-lightOnSurface"> <h3 className="text-center md:font-heading-1-regular sm:text-[32px] sm:leading-[40px] text-[20px] leading-[28px] text-lightOnSurface">
{lotteryStatus === "started" {status === "Ongoing"
? "Bije dowam edýär" ? "Bije dowam edýär"
: lotteryStatus === "ended" : status === "Finished"
? "Bije tamamlandy" ? "Bije tamamlandy"
: "Bije"} : "Bije"}
</h3> </h3>
{/* LotteryCountDown */} {/* LotteryCountDown */}
{lotteryStatus === "not-started" && ( {status === "Upcoming" && (
<div className="flex items-center sm:gap-6 gap-2 justify-between"> <div className="flex items-center sm:gap-6 gap-2 justify-between">
<div className="flex flex-col items-center justify-center flex-1 sm:p-6 p-4 sm:pb-3"> <div className="flex flex-col items-center justify-center flex-1 sm:p-6 p-4 sm:pb-3">
<h3 className="md:text-[80px] sm:text-[56px] text-[28px] md:leading-[88px] sm:leading-[64px] leading-[36px] -tracking-[1%]"> <h3 className="md:text-[80px] sm:text-[56px] text-[28px] md:leading-[88px] sm:leading-[64px] leading-[36px] -tracking-[1%]">
{timeLeft.hours} {timeLeft.hours}
</h3> </h3>
<h4 className="font-medium md:text-[20px] sm:text-[18px] text-[14px] sm:leading-[28px] leading-[20px] -tracking-[1%] text-lightOnSurfaceVariant"> <h4 className="font-medium md:text-[20px] sm:text-[18px] text-[14px] sm:leading-[28px] leading-[20px] -tracking-[1%] text-lightOnSurfaceVariant">
hours sagat
</h4> </h4>
</div> </div>
@ -88,7 +85,7 @@ const LotteryCountDown: React.FC<LotteryCountDownProps> = ({
{timeLeft.minutes} {timeLeft.minutes}
</h3> </h3>
<h4 className="font-medium md:text-[20px] sm:text-[18px] text-[14px] sm:leading-[28px] leading-[20px] -tracking-[1%] text-lightOnSurfaceVariant"> <h4 className="font-medium md:text-[20px] sm:text-[18px] text-[14px] sm:leading-[28px] leading-[20px] -tracking-[1%] text-lightOnSurfaceVariant">
minutes minut
</h4> </h4>
</div> </div>
@ -103,7 +100,7 @@ const LotteryCountDown: React.FC<LotteryCountDownProps> = ({
{timeLeft.seconds} {timeLeft.seconds}
</h3> </h3>
<h4 className="font-medium md:text-[20px] sm:text-[18px] text-[14px] sm:leading-[28px] leading-[20px] -tracking-[1%] text-lightOnSurfaceVariant"> <h4 className="font-medium md:text-[20px] sm:text-[18px] text-[14px] sm:leading-[28px] leading-[20px] -tracking-[1%] text-lightOnSurfaceVariant">
seconds sekunt
</h4> </h4>
</div> </div>
</div> </div>
@ -111,9 +108,9 @@ 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]"> <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> <span>
{lotteryStatus === "not-started" {status === "Upcoming"
? "- den başlar" ? "- den başlar"
: lotteryStatus === "started" : status === "Ongoing"
? "girmek üçin aşakda kodyňyzy giriziň" ? "girmek üçin aşakda kodyňyzy giriziň"
: "netijeleri görmek üçin aşakda kodyňyzy giriziň"} : "netijeleri görmek üçin aşakda kodyňyzy giriziň"}
</span> </span>

View File

@ -1,12 +1,62 @@
import { useLotteryAuth } from "@/store/useLotteryAuth"; "use client";
import { useWebsocketLottery } from "@/hooks/useWebSocketLottery";
import { useEffect, useState } from "react";
interface IProps { interface IProps {
title: string; show?: boolean;
content: string; data?: any;
} }
const LotteryRulesSection = () => { const WEBSOCKET_URL = "wss://sms.turkmentv.gov.tm/ws/lottery?dst=";
const { lotteryData } = useLotteryAuth();
const LotteryRulesSection = ({ show = true, data }: IProps) => {
const [messageQueue, setMessageQueue] = useState<any[]>([]); // Queue for incoming WebSocket messages
const [isProcessing, setIsProcessing] = useState<boolean>(false); // Track if a message is being processed
const { subscribeToMessages } = useWebsocketLottery(
`${WEBSOCKET_URL}${data?.data.sms_number}`
);
const [totalParticipants, setTotalParticipants] = useState<number>(
data ? data?.data?.bije_count : 0
);
if (!data.errorMessage) {
document.cookie = "phoneNumber=;path=/";
document.cookie = "key=;path=/";
}
// Subscribe to WebSocket messages
useEffect(() => {
const unsubscribe = subscribeToMessages((event) => {
// Add new message to the queue
setMessageQueue((prevQueue) => {
return [...prevQueue, JSON.parse(event.data)];
});
});
return () => unsubscribe();
}, [subscribeToMessages]);
// Process queue when a new message is added
useEffect(() => {
if (!isProcessing && messageQueue.length > 0) {
processQueue();
}
}, [messageQueue, isProcessing]);
// Process a single message from the queue
const processQueue = async () => {
if (isProcessing || messageQueue.length === 0) return;
setIsProcessing(true); // Lock processing
const message = messageQueue[0]; // Get the first message in the queue
if (!message?.winner_no) {
setTotalParticipants(totalParticipants + 1);
}
setMessageQueue((prevQueue) => prevQueue.slice(1)); // Remove the processed message from the queue
setIsProcessing(false); // Unlock processing
};
return ( return (
<section> <section>
@ -16,10 +66,10 @@ const LotteryRulesSection = () => {
Düzgünleri: Düzgünleri:
</h2> </h2>
<div className="flex sm:flex-row flex-col gap-6"> <div className="grid md:grid-cols-2 gap-6">
<div className="flex flex-col bg-lightSurfaceContainer sm:py-4 md:px-8 sm:px-6 py-3 px-4 rounded-[12px] w-full"> <div className="flex flex-col bg-lightSurfaceContainer sm:py-4 md:px-8 sm:px-6 py-3 px-4 rounded-[12px] w-full">
<ul className="list-disc flex flex-col md:gap-4 gap-2 pl-[16px]"> <ul className="list-disc flex flex-col md:gap-4 gap-2 pl-[16px]">
{lotteryData?.data.rules?.map((item, i) => ( {data?.data.rules?.map((item: any, i: number) => (
<li className="font-small-regular" key={i}> <li className="font-small-regular" key={i}>
{item.title} {item.title}
</li> </li>
@ -27,19 +77,31 @@ const LotteryRulesSection = () => {
</ul> </ul>
</div> </div>
<div className="bg-lightSurfaceContainer flex flex-col gap-4 px-4 py-[12px] rounded-[12px]">
<h1 className="md:font-heading-5-regular sm:text-[20px] text-[18px] sm:leading-[24px] leading-[28px]">
Umumy gatnaşyjylaryň sany:
</h1>
<p className="text-[24px]">{totalParticipants}</p>
</div>
</div>
{show && (
<div className="flex flex-col md:gap-4 sm:gap-2 gap-4 bg-lightSurfaceContainer sm:py-4 md:px-8 sm:px-6 py-3 px-4 rounded-[12px] w-full"> <div className="flex flex-col md:gap-4 sm:gap-2 gap-4 bg-lightSurfaceContainer sm:py-4 md:px-8 sm:px-6 py-3 px-4 rounded-[12px] w-full">
<h3 className="md:font-heading-5-regular sm:text-[20px] text-[18px] sm:leading-[24px] leading-[28px]"> <h3 className="md:font-heading-5-regular sm:text-[20px] text-[18px] sm:leading-[24px] leading-[28px]">
Siziň kodlaryňyz: Siziň bijeli sanynyz:
</h3> </h3>
<ul className="list-disc flex flex-col md:gap-4 gap-2 pl-[16px]"> <ul className="flex flex-col items-center md:gap-4 gap-2">
{lotteryData?.user_lottery_numbers.map((item, i) => ( {data?.user_lottery_numbers.map((item: any, i: number) => (
<li className="font-small-regular" key={i}> <li
className="text-[24px] text-[#46464F] md:text-[48px] lg:text-[80px] list-none"
key={i}
>
{item} {item}
</li> </li>
))} ))}
</ul> </ul>
</div> </div>
</div> )}
</div> </div>
</div> </div>
</section> </section>

View File

@ -1,23 +1,26 @@
'use client'; "use client";
import Image from 'next/image'; import Image from "next/image";
import React, { useEffect, useRef, useState } from 'react'; import React, { useEffect, useRef, useState } from "react";
import SlotCounter, { SlotCounterRef } from 'react-slot-counter'; import SlotCounter, { SlotCounterRef } from "react-slot-counter";
import { useMediaQuery } from 'usehooks-ts'; import { useMediaQuery } from "usehooks-ts";
interface LotterySlotCounterProps { interface LotterySlotCounterProps {
numberString: string; numberString: string;
startNumber: string;
} }
const LotterySlotCounter = ({ numberString }: LotterySlotCounterProps) => { const LotterySlotCounter = ({
const [formattedNumber, setFormattedNumber] = useState(numberString); numberString,
startNumber,
}: LotterySlotCounterProps) => {
const [formattedNumber, setFormattedNumber] = useState("00,00,00,00,00");
const slotCounterRef = useRef<SlotCounterRef>(null); // Ref for manual control const slotCounterRef = useRef<SlotCounterRef>(null); // Ref for manual control
const isFirstRender = useRef(true); // Ref to track the initial render const isFirstRender = useRef(true); // Ref to track the initial render
const tablet = useMediaQuery('(max-width: 769px)'); const mobile = useMediaQuery("(max-width: 426px)");
const mobile = useMediaQuery('(max-width: 426px)');
useEffect(() => { useEffect(() => {
const formatted = numberString.replace(/-/g, ','); const formatted = numberString.replace(/-/g, ",");
setFormattedNumber(formatted); setFormattedNumber(formatted);
// Skip animation on the first render // Skip animation on the first render
@ -74,22 +77,24 @@ const LotterySlotCounter = ({ numberString }: LotterySlotCounterProps) => {
className="flex items-center h-fit md:max-w-[1132px] sm:max-w-[640px] max-w-[400px] w-full justify-center text-white min-[1025px]:py-4 md:px-6 rounded-full overflow-y-hidden overflow-x-visible relative border-4 border-lightPrimary" className="flex items-center h-fit md:max-w-[1132px] sm:max-w-[640px] max-w-[400px] w-full justify-center text-white min-[1025px]:py-4 md:px-6 rounded-full overflow-y-hidden overflow-x-visible relative border-4 border-lightPrimary"
style={{ style={{
background: background:
'linear-gradient(180deg, #454673 0%, #575992 10.5%, #575992 90%, #454673 100%)', "linear-gradient(180deg, #454673 0%, #575992 10.5%, #575992 90%, #454673 100%)",
boxShadow: '0px 4px 4px 0px #00000040', boxShadow: "0px 4px 4px 0px #00000040",
}}> }}
>
{/* Highlight */} {/* Highlight */}
<div <div
className="absolute top-[50%] -translate-y-1/2 left-0 w-full h-full" className="absolute top-[50%] -translate-y-1/2 left-0 w-full h-full"
style={{ style={{
background: background:
'linear-gradient(180deg, rgba(87, 89, 146, 0) 0%, #7274AB 50%, rgba(87, 89, 146, 0) 100%)', "linear-gradient(180deg, rgba(87, 89, 146, 0) 0%, #7274AB 50%, rgba(87, 89, 146, 0) 100%)",
}}></div> }}
></div>
<div className="z-10"> <div className="z-10">
<SlotCounter <SlotCounter
ref={slotCounterRef} ref={slotCounterRef}
value={formattedNumber} value={formattedNumber}
startValue={'00,00,00,00,00'} startValue={startNumber}
charClassName="rolling-number" charClassName="rolling-number"
separatorClassName="slot-seperator" separatorClassName="slot-seperator"
duration={3} duration={3}

View File

@ -1,9 +1,9 @@
'use client'; "use client";
import Image from 'next/image'; import Image from "next/image";
import { Dispatch, SetStateAction, useState } from 'react'; import { Dispatch, SetStateAction, useState } from "react";
import Confetti from 'react-confetti'; import Confetti from "react-confetti";
import { useWindowSize } from 'react-use'; import { useWindowSize } from "react-use";
interface IProps { interface IProps {
setWinners: Dispatch<SetStateAction<number[]>>; setWinners: Dispatch<SetStateAction<number[]>>;
@ -72,12 +72,12 @@ const SpinWheel = ({ setWinners }: IProps) => {
tweenDuration={10000} tweenDuration={10000}
run={true} run={true}
colors={[ colors={[
'linear-gradient(45deg, #5D5D72, #8589DE)', "linear-gradient(45deg, #5D5D72, #8589DE)",
'linear-gradient(45deg, #E1E0FF, #575992)', "linear-gradient(45deg, #E1E0FF, #575992)",
'#8589DE', "#8589DE",
'#575992', "#575992",
'#E1E0FF', "#E1E0FF",
'#BA1A1A', "#BA1A1A",
]} ]}
/> />
</div> </div>
@ -85,7 +85,7 @@ const SpinWheel = ({ setWinners }: IProps) => {
<div className="relative rounded-full md:max-w-[554px] md:max-h-[554px] w-[276px] h-[276px] md:w-full md:h-full"> <div className="relative rounded-full md:max-w-[554px] md:max-h-[554px] w-[276px] h-[276px] md:w-full md:h-full">
{/* Wheel triangle */} {/* Wheel triangle */}
<Image <Image
src={'/wheel-triangle.svg'} src={"/wheel-triangle.svg"}
alt="wheel" alt="wheel"
height={34} height={34}
width={35} width={35}
@ -96,12 +96,13 @@ const SpinWheel = ({ setWinners }: IProps) => {
<div <div
style={{ style={{
transform: `rotate(${rotation}deg)`, transform: `rotate(${rotation}deg)`,
transition: isSpinning ? 'transform 5s ease-out' : '', transition: isSpinning ? "transform 5s ease-out" : "",
}} }}
className="lg:p-3 p-2 bg-[#5D5D72] rounded-full overflow-hidden"> className="lg:p-3 p-2 bg-[#5D5D72] rounded-full overflow-hidden"
>
<div className="lg:p-[15px] p-[10px] bg-[#8589DE] rounded-full "> <div className="lg:p-[15px] p-[10px] bg-[#8589DE] rounded-full ">
<Image <Image
src={'/wheel-circle-inner.png'} src={"/wheel-circle-inner.png"}
alt="wheel" alt="wheel"
height={530} height={530}
width={530} width={530}
@ -124,14 +125,15 @@ const SpinWheel = ({ setWinners }: IProps) => {
disabled={isSpinning || isCountingDown} disabled={isSpinning || isCountingDown}
className={`mt-6 px-6 py-3 rounded-full text-white font-bold ${ className={`mt-6 px-6 py-3 rounded-full text-white font-bold ${
isSpinning || isCountingDown isSpinning || isCountingDown
? 'bg-gray-400 cursor-not-allowed' ? "bg-gray-400 cursor-not-allowed"
: 'bg-blue-500 hover:bg-blue-700' : "bg-blue-500 hover:bg-blue-700"
}`}> }`}
>
{isCountingDown {isCountingDown
? `Starting in ${countdown}...` ? `Starting in ${countdown}...`
: isSpinning : isSpinning
? 'Spinning...' ? "Spinning..."
: 'Spin the Wheel'} : "Spin the Wheel"}
</button> </button>
</div> </div>
); );

View File

@ -1,9 +1,8 @@
import { import {
LotteryWinnerData,
LotteryWinnerDataSimplified, LotteryWinnerDataSimplified,
} from "@/typings/lottery/lottery.types"; } from "@/typings/lottery/lottery.types";
import LotteryWinner from "./LotteryWinner"; import LotteryWinner from "./LotteryWinner";
import { motion, AnimatePresence } from "framer-motion"; import { motion } from "framer-motion";
import { v4 } from "uuid"; import { v4 } from "uuid";
const LotteryWinnersList = ({ const LotteryWinnersList = ({

View File

@ -1,11 +1,10 @@
'use client'; "use client";
import { Queries } from '@/api/queries'; import { Queries } from "@/api/queries";
import { v4 } from 'uuid'; import { v4 } from "uuid";
import { useState, useEffect, useContext, Dispatch, SetStateAction } from 'react'; import { useState, useEffect, useContext } from "react";
import { Answer, IQuizQuestionsWinners } from '@/models/quizQuestionsWinners.model'; import { IQuizQuestionsWinners } from "@/models/quizQuestionsWinners.model";
import QuizContext from '@/context/QuizContext'; import QuizContext from "@/context/QuizContext";
import { IQuizQuestions } from '@/models/quizQuestions.model';
interface Message { interface Message {
answer: string; answer: string;
@ -133,11 +132,11 @@ const QuizWinnerTable = ({ quizId, quizFinished, smsNumber }: IProps) => {
// Function to handle incoming WebSocket message and update winnersData // Function to handle incoming WebSocket message and update winnersData
const handleOnMessage = (message: Message) => { const handleOnMessage = (message: Message) => {
if (!winnersData) { if (!winnersData) {
console.error('winnersData is undefined'); console.error("winnersData is undefined");
return; return;
} }
console.log('updating winnersData'); console.log("updating winnersData");
// Update the winnersData by matching phone with starred_src from the message // Update the winnersData by matching phone with starred_src from the message
setWinnersData((prevWinnersData) => { setWinnersData((prevWinnersData) => {
@ -162,7 +161,10 @@ const QuizWinnerTable = ({ quizId, quizFinished, smsNumber }: IProps) => {
// Calculate the new correct_answers_time by summing serial_number_for_correct // Calculate the new correct_answers_time by summing serial_number_for_correct
const updatedCorrectAnswersTime = updatedAnswers const updatedCorrectAnswersTime = updatedAnswers
.reduce((sum, answer) => sum + answer.serial_number_for_correct, 0) .reduce(
(sum, answer) => sum + answer.serial_number_for_correct,
0
)
.toString(); .toString();
return { return {
@ -182,7 +184,7 @@ const QuizWinnerTable = ({ quizId, quizFinished, smsNumber }: IProps) => {
}; };
}); });
console.log('winnersData is updated'); console.log("winnersData is updated");
}; };
return winnersData?.data.length !== 0 ? ( return winnersData?.data.length !== 0 ? (
@ -231,9 +233,10 @@ const QuizWinnerTable = ({ quizId, quizFinished, smsNumber }: IProps) => {
{winnersData?.data.map((winner, id) => ( {winnersData?.data.map((winner, id) => (
<div <div
className={`flex border-b border-fillTableStrokeTableRow ${ className={`flex border-b border-fillTableStrokeTableRow ${
id % 2 === 0 ? 'bg-fillTableRow' : 'bg-fillTableRow2' id % 2 === 0 ? "bg-fillTableRow" : "bg-fillTableRow2"
}`} }`}
key={v4()}> key={v4()}
>
<div className="flex justify-center items-center text-base text-textBlack leading-[125%] max-w-[54px] w-[100%] pl-6 pr-3 py-5"> <div className="flex justify-center items-center text-base text-textBlack leading-[125%] max-w-[54px] w-[100%] pl-6 pr-3 py-5">
<span>{id + 1}</span> <span>{id + 1}</span>
</div> </div>
@ -246,46 +249,35 @@ const QuizWinnerTable = ({ quizId, quizFinished, smsNumber }: IProps) => {
<div className="flex justify-center items-center gap-6 text-base text-textGray leading-[125%] w-[100%] px-3 py-5"> <div className="flex justify-center items-center gap-6 text-base text-textGray leading-[125%] w-[100%] px-3 py-5">
{questionsData {questionsData
? questionsData.map((question) => { ? questionsData.map((question) => {
// const matchingAnswer = winner.client.answers.find(
// (answer) => answer.question_id === question.id,
// );
const matchingAnswer = const matchingAnswer =
winner.client.answers.find( winner.client.answers.find(
(answer) => answer.question_id === question.id && answer.score > 0, (answer) =>
answer.question_id === question.id &&
answer.score > 0
) || ) ||
winner.client.answers.find( winner.client.answers.find(
(answer) => answer.question_id === question.id, (answer) => answer.question_id === question.id
); );
// const matchingAnswer = () => {
// const duplicateAnswers: Answer[] = [];
// winner.client.answers.map((answer) =>
// answer.question_id === question.id
// ? duplicateAnswers.push(answer)
// : null,
// );
// console.log(duplicateAnswers);
// };
return ( return (
<span <span
key={v4()} key={v4()}
className={`text-sm font-semibold leading-[125%] ${ className={`text-sm font-semibold leading-[125%] ${
matchingAnswer && matchingAnswer.serial_number_for_correct !== 0 matchingAnswer &&
? 'text-fillGreen' matchingAnswer.serial_number_for_correct !== 0
? "text-fillGreen"
: matchingAnswer && : matchingAnswer &&
matchingAnswer?.serial_number_for_correct === 0 matchingAnswer?.serial_number_for_correct ===
? 'text-fillRed' 0
: 'text-textLight' ? "text-fillRed"
}`}> : "text-textLight"
}`}
>
{matchingAnswer && matchingAnswer.score !== 0 {matchingAnswer && matchingAnswer.score !== 0
? matchingAnswer.serial_number_for_correct ? matchingAnswer.serial_number_for_correct
: matchingAnswer && matchingAnswer?.score === 0 : matchingAnswer && matchingAnswer?.score === 0
? 'X' ? "X"
: '0'} : "0"}
</span> </span>
); );
}) })
@ -345,9 +337,10 @@ const QuizWinnerTable = ({ quizId, quizFinished, smsNumber }: IProps) => {
{winnersData?.data.map((winner, id) => ( {winnersData?.data.map((winner, id) => (
<div <div
className={`flex border-b border-fillTableStrokeTableRow items-center p-[8px] gap-[8px] ${ className={`flex border-b border-fillTableStrokeTableRow items-center p-[8px] gap-[8px] ${
id % 2 === 0 ? 'bg-fillTableRow' : 'bg-fillTableRow2' id % 2 === 0 ? "bg-fillTableRow" : "bg-fillTableRow2"
}`} }`}
key={v4()}> key={v4()}
>
<div className="flex items-center text-base text-textBlack leading-[125%] max-w-[14px] w-[100%] "> <div className="flex items-center text-base text-textBlack leading-[125%] max-w-[14px] w-[100%] ">
<span>{id + 1}</span> <span>{id + 1}</span>
</div> </div>
@ -385,26 +378,36 @@ const QuizWinnerTable = ({ quizId, quizFinished, smsNumber }: IProps) => {
<div className="flex justify-center items-center gap-[4px] text-xs text-textGray leading-[125%] w-fit"> <div className="flex justify-center items-center gap-[4px] text-xs text-textGray leading-[125%] w-fit">
{questionsData {questionsData
? questionsData.map((question) => { ? questionsData.map((question) => {
const matchingAnswer = winner.client.answers.find( const matchingAnswer =
(answer) => answer.question_id === question.id, winner.client.answers.find(
); (answer) =>
answer.question_id === question.id &&
answer.score > 0
) ||
winner.client.answers.find(
(answer) => answer.question_id === question.id
);
return ( return (
<span <span
key={v4()} key={v4()}
className={`text-sm font-semibold leading-[125%] ${ className={`text-sm font-semibold leading-[125%] ${
matchingAnswer && matchingAnswer.serial_number_for_correct !== 0 matchingAnswer &&
? 'text-fillGreen' matchingAnswer.serial_number_for_correct !==
0
? "text-fillGreen"
: matchingAnswer && : matchingAnswer &&
matchingAnswer.serial_number_for_correct === 0 matchingAnswer.serial_number_for_correct ===
? 'text-fillRed' 0
: 'text-textLight' ? "text-fillRed"
}`}> : "text-textLight"
{matchingAnswer && matchingAnswer.serial_number_for_correct !== 0 }`}
>
{matchingAnswer && matchingAnswer.score !== 0
? matchingAnswer.serial_number_for_correct ? matchingAnswer.serial_number_for_correct
: matchingAnswer && : matchingAnswer &&
matchingAnswer.serial_number_for_correct === 0 matchingAnswer?.score === 0
? 'X' ? "X"
: '0'} : "0"}
</span> </span>
); );
}) })
@ -436,7 +439,9 @@ const QuizWinnerTable = ({ quizId, quizFinished, smsNumber }: IProps) => {
</div> </div>
<div className="flex gap-[10px] items-center "> <div className="flex gap-[10px] items-center ">
<div className="border border-fillBlue rounded-full min-w-[32px] h-[32px] flex justify-center items-center"> <div className="border border-fillBlue rounded-full min-w-[32px] h-[32px] flex justify-center items-center">
<span className="text-fillBlue text-sm leading-[125%] ">100</span> <span className="text-fillBlue text-sm leading-[125%] ">
100
</span>
</div> </div>
<span className="text-base leading-[120%] text-textLight w-full"> <span className="text-base leading-[120%] text-textLight w-full">
@ -445,7 +450,9 @@ const QuizWinnerTable = ({ quizId, quizFinished, smsNumber }: IProps) => {
</div> </div>
<div className="flex gap-[10px] items-center"> <div className="flex gap-[10px] items-center">
<div className="flex justify-center items-center min-w-[32px]"> <div className="flex justify-center items-center min-w-[32px]">
<span className="text-xl font-semibold leading-[120%] text-fillGreen ">1</span> <span className="text-xl font-semibold leading-[120%] text-fillGreen ">
1
</span>
</div> </div>
<span className="text-base leading-[120%] text-textLight"> <span className="text-base leading-[120%] text-textLight">
Dogry jogaplara näçinji bolup jogap berdi Dogry jogaplara näçinji bolup jogap berdi
@ -453,7 +460,9 @@ const QuizWinnerTable = ({ quizId, quizFinished, smsNumber }: IProps) => {
</div> </div>
<div className="flex gap-[10px] items-center "> <div className="flex gap-[10px] items-center ">
<div className="flex justify-center items-center min-w-[32px]"> <div className="flex justify-center items-center min-w-[32px]">
<span className="text-xl font-semibold leading-[120%] text-fillRed">X</span> <span className="text-xl font-semibold leading-[120%] text-fillRed">
X
</span>
</div> </div>
<span className="text-base leading-[120%] text-textLight"> <span className="text-base leading-[120%] text-textLight">
Soraga nädogry jogap berdi Soraga nädogry jogap berdi
@ -462,9 +471,13 @@ const QuizWinnerTable = ({ quizId, quizFinished, smsNumber }: IProps) => {
</div> </div>
<div className="flex gap-[10px] items-center "> <div className="flex gap-[10px] items-center ">
<div className="flex justify-center items-center min-w-[32px]"> <div className="flex justify-center items-center min-w-[32px]">
<span className="text-xl font-semibold leading-[120%] text-textLight">0</span> <span className="text-xl font-semibold leading-[120%] text-textLight">
0
</span>
</div> </div>
<span className="text-base leading-[120%] text-textLight">Soraga jogap ugratmady</span> <span className="text-base leading-[120%] text-textLight">
Soraga jogap ugratmady
</span>
</div> </div>
</div> </div>
</div> </div>

63
components/toss/index.tsx Normal file
View File

@ -0,0 +1,63 @@
import LotteryHeader from "@/components/lottery/LotteryHeader";
import LotteryRulesSection from "@/components/lottery/rules/LotteryRulesSection";
import LotteryCountDown from "@/components/lottery/countDown/LotteryCountDown";
import { getTossData } from "@/api/queries";
import { getLotteryStatus } from "@/lib/actions";
import LotteryWinners from "../lottery/LotteryWinners";
const TossPage = async ({
type,
id,
}: {
type: "bije" | "cekilis";
id: string;
}) => {
const tossData = await getTossData({ type, id });
const status = await getLotteryStatus(
tossData?.data?.start_time,
tossData?.data?.end_time
);
return (
<>
{tossData?.data ? (
<div className="flex flex-col md:gap-[128px] gap-[80px] font-roboto md:pt-[64px] sm:pt-[48px] pt-[40px] ms:pb-[128px] pb-[80px] text-lightOnSurface">
{tossData && (
<div className="flex flex-col sm:gap-[64px] gap-[40px]">
<LotteryHeader
title={tossData.data.title}
description={tossData.data.description}
image={tossData.data.image}
smsCode={tossData.data.sms_code}
startDate={tossData.data.start_time}
/>
{status === "Upcoming" && (
<div className="container">
<LotteryCountDown
lotteryStatus={status}
endDate={tossData.data.end_time}
startDate={tossData.data.start_time}
/>
</div>
)}
</div>
)}
<LotteryRulesSection show={false} data={tossData} />
<div className="flex flex-col gap-10">
<LotteryWinners data={tossData} lotteryStatus={status} />
</div>
</div>
) : (
<div className="flex flex-col items-center md:gap-[128px] gap-[80px] font-roboto md:pt-[64px] sm:pt-[48px] pt-[40px] ms:pb-[128px] pb-[80px] text-lightOnSurface">
<h1 className="text-[22px]">{tossData?.errorMessage}</h1>
</div>
)}
</>
);
};
export default TossPage;

178
components/ui/form.tsx Normal file
View File

@ -0,0 +1,178 @@
"use client"
import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"
import { Slot } from "@radix-ui/react-slot"
import {
Controller,
ControllerProps,
FieldPath,
FieldValues,
FormProvider,
useFormContext,
} from "react-hook-form"
import { cn } from "@/lib/utils"
import { Label } from "@/components/ui/label"
const Form = FormProvider
type FormFieldContextValue<
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
> = {
name: TName
}
const FormFieldContext = React.createContext<FormFieldContextValue>(
{} as FormFieldContextValue
)
const FormField = <
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
>({
...props
}: ControllerProps<TFieldValues, TName>) => {
return (
<FormFieldContext.Provider value={{ name: props.name }}>
<Controller {...props} />
</FormFieldContext.Provider>
)
}
const useFormField = () => {
const fieldContext = React.useContext(FormFieldContext)
const itemContext = React.useContext(FormItemContext)
const { getFieldState, formState } = useFormContext()
const fieldState = getFieldState(fieldContext.name, formState)
if (!fieldContext) {
throw new Error("useFormField should be used within <FormField>")
}
const { id } = itemContext
return {
id,
name: fieldContext.name,
formItemId: `${id}-form-item`,
formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`,
...fieldState,
}
}
type FormItemContextValue = {
id: string
}
const FormItemContext = React.createContext<FormItemContextValue>(
{} as FormItemContextValue
)
const FormItem = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
const id = React.useId()
return (
<FormItemContext.Provider value={{ id }}>
<div ref={ref} className={cn("space-y-2", className)} {...props} />
</FormItemContext.Provider>
)
})
FormItem.displayName = "FormItem"
const FormLabel = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
>(({ className, ...props }, ref) => {
const { error, formItemId } = useFormField()
return (
<Label
ref={ref}
className={cn(error && "text-destructive", className)}
htmlFor={formItemId}
{...props}
/>
)
})
FormLabel.displayName = "FormLabel"
const FormControl = React.forwardRef<
React.ElementRef<typeof Slot>,
React.ComponentPropsWithoutRef<typeof Slot>
>(({ ...props }, ref) => {
const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
return (
<Slot
ref={ref}
id={formItemId}
aria-describedby={
!error
? `${formDescriptionId}`
: `${formDescriptionId} ${formMessageId}`
}
aria-invalid={!!error}
{...props}
/>
)
})
FormControl.displayName = "FormControl"
const FormDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => {
const { formDescriptionId } = useFormField()
return (
<p
ref={ref}
id={formDescriptionId}
className={cn("text-[0.8rem] text-muted-foreground", className)}
{...props}
/>
)
})
FormDescription.displayName = "FormDescription"
const FormMessage = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, children, ...props }, ref) => {
const { error, formMessageId } = useFormField()
const body = error ? String(error?.message) : children
if (!body) {
return null
}
return (
<p
ref={ref}
id={formMessageId}
className={cn("text-[0.8rem] font-medium text-destructive", className)}
{...props}
>
{body}
</p>
)
})
FormMessage.displayName = "FormMessage"
export {
useFormField,
Form,
FormItem,
FormLabel,
FormControl,
FormDescription,
FormMessage,
FormField,
}

26
components/ui/label.tsx Normal file
View File

@ -0,0 +1,26 @@
"use client"
import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const labelVariants = cva(
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
)
const Label = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
VariantProps<typeof labelVariants>
>(({ className, ...props }, ref) => (
<LabelPrimitive.Root
ref={ref}
className={cn(labelVariants(), className)}
{...props}
/>
))
Label.displayName = LabelPrimitive.Root.displayName
export { Label }

View File

@ -14,15 +14,12 @@ export const useWebsocketLottery = (url: string) => {
const setupWebSocket = () => { const setupWebSocket = () => {
if (!isMounted) return; if (!isMounted) return;
console.log("🔄 [WebSocket] Connecting...");
const socket = new WebSocket(url); const socket = new WebSocket(url);
wsRef.current = socket; wsRef.current = socket;
socket.onopen = () => { socket.onopen = () => {
if (!isMounted) return; if (!isMounted) return;
console.log("✅ [WebSocket] Connected");
console.log("🔗 [WebSocket URL]:", url);
setWsStatus("connected"); setWsStatus("connected");
if (reconnectTimeoutRef.current) if (reconnectTimeoutRef.current)
clearTimeout(reconnectTimeoutRef.current); clearTimeout(reconnectTimeoutRef.current);
@ -31,21 +28,18 @@ export const useWebsocketLottery = (url: string) => {
socket.onmessage = (event) => { socket.onmessage = (event) => {
if (!isMounted) return; if (!isMounted) return;
console.log("📩 [WebSocket] Message received:", event.data);
messageListeners.current.forEach((listener) => listener(event)); messageListeners.current.forEach((listener) => listener(event));
}; };
socket.onerror = () => { socket.onerror = () => {
if (!isMounted) return; if (!isMounted) return;
console.error("❌ [WebSocket] Error occurred");
setWsStatus("error"); setWsStatus("error");
}; };
socket.onclose = () => { socket.onclose = () => {
if (!isMounted) return; if (!isMounted) return;
console.log("❌ [WebSocket] Closed");
setWsStatus("closed"); setWsStatus("closed");
reconnectWebSocket(); reconnectWebSocket();
}; };
@ -54,7 +48,6 @@ export const useWebsocketLottery = (url: string) => {
const reconnectWebSocket = () => { const reconnectWebSocket = () => {
if (!isMounted) return; if (!isMounted) return;
console.log("🔄 [WebSocket] Reconnecting in 5 seconds...");
reconnectTimeoutRef.current = setTimeout(() => { reconnectTimeoutRef.current = setTimeout(() => {
setupWebSocket(); setupWebSocket();
}, 5000); }, 5000);
@ -63,7 +56,6 @@ export const useWebsocketLottery = (url: string) => {
setupWebSocket(); setupWebSocket();
return () => { return () => {
console.log("🔌 [WebSocket] Cleaning up...");
isMounted = false; isMounted = false;
if (wsRef.current) { if (wsRef.current) {
@ -79,17 +71,14 @@ export const useWebsocketLottery = (url: string) => {
const sendPing = useCallback(() => { const sendPing = useCallback(() => {
if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) { if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) {
console.log("📤 [WebSocket] Sending ping");
wsRef.current.send(JSON.stringify({ type: "ping" })); wsRef.current.send(JSON.stringify({ type: "ping" }));
} }
}, []); }, []);
const subscribeToMessages = useCallback( const subscribeToMessages = useCallback(
(listener: (event: MessageEvent) => void) => { (listener: (event: MessageEvent) => void) => {
console.log("👂 [WebSocket] Subscribing to messages");
messageListeners.current.push(listener); messageListeners.current.push(listener);
return () => { return () => {
console.log("❌ [WebSocket] Unsubscribing from messages");
messageListeners.current = messageListeners.current.filter( messageListeners.current = messageListeners.current.filter(
(l) => l !== listener (l) => l !== listener
); );

15
lib/actions.ts Normal file
View File

@ -0,0 +1,15 @@
"use server";
export async function getLotteryStatus(startTime: string, endTime: string) {
const now = new Date();
const start = new Date(startTime);
const end = new Date(endTime);
if (now < start) {
return "Upcoming";
} else if (now >= start && now <= end) {
return "Ongoing";
} else {
return "Finished";
}
}

View File

@ -1,6 +1,6 @@
import { type ClassValue, clsx } from "clsx" import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge" import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) { export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs)) return twMerge(clsx(inputs));
} }

View File

@ -20,6 +20,7 @@ export interface ILotteryData {
sms_number: string; sms_number: string;
winners: ILotteryWinner[]; winners: ILotteryWinner[];
rules: ILotteryRule[] | null; rules: ILotteryRule[] | null;
bije_count: number;
} }
export interface ILotteryResponse { export interface ILotteryResponse {

313
package-lock.json generated
View File

@ -10,12 +10,14 @@
"dependencies": { "dependencies": {
"@emotion/react": "^11.10.6", "@emotion/react": "^11.10.6",
"@emotion/styled": "^11.10.6", "@emotion/styled": "^11.10.6",
"@hookform/resolvers": "^3.10.0",
"@mui/material": "^5.12.1", "@mui/material": "^5.12.1",
"@mui/x-date-pickers": "^6.5.0", "@mui/x-date-pickers": "^6.5.0",
"@radix-ui/react-alert-dialog": "^1.1.2", "@radix-ui/react-alert-dialog": "^1.1.2",
"@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-label": "^2.1.2",
"@radix-ui/react-popover": "^1.1.1", "@radix-ui/react-popover": "^1.1.1",
"@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-slot": "^1.1.2",
"@tanstack/react-query": "^4.32.0", "@tanstack/react-query": "^4.32.0",
"@tanstack/react-query-devtools": "^4.32.0", "@tanstack/react-query-devtools": "^4.32.0",
"@types/node": "18.15.13", "@types/node": "18.15.13",
@ -24,7 +26,7 @@
"@types/uuid": "^9.0.1", "@types/uuid": "^9.0.1",
"autoprefixer": "10.4.14", "autoprefixer": "10.4.14",
"axios": "^1.5.1", "axios": "^1.5.1",
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"date-fns": "^2.30.0", "date-fns": "^2.30.0",
"dayjs": "^1.11.7", "dayjs": "^1.11.7",
@ -38,19 +40,20 @@
"react-day-picker": "^8.10.1", "react-day-picker": "^8.10.1",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-fast-marquee": "^1.3.5", "react-fast-marquee": "^1.3.5",
"react-hook-form": "^7.43.9", "react-hook-form": "^7.54.2",
"react-icons": "^4.8.0", "react-icons": "^4.8.0",
"react-player": "^2.12.0", "react-player": "^2.12.0",
"react-slot-counter": "^3.0.4", "react-slot-counter": "^3.0.4",
"react-use": "^17.5.1", "react-use": "^17.5.1",
"sharp": "^0.33.5", "sharp": "^0.33.5",
"swiper": "^9.2.4", "swiper": "^9.2.4",
"tailwind-merge": "^2.4.0", "tailwind-merge": "^2.6.0",
"tailwindcss": "3.3.1", "tailwindcss": "3.3.1",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"typescript": "^5.2.2", "typescript": "^5.2.2",
"usehooks-ts": "^2.9.1", "usehooks-ts": "^2.9.1",
"uuid": "^9.0.0", "uuid": "^9.0.0",
"zod": "^3.24.1",
"zustand": "^5.0.1" "zustand": "^5.0.1"
}, },
"devDependencies": { "devDependencies": {
@ -375,6 +378,15 @@
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.5.tgz", "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.5.tgz",
"integrity": "sha512-sTcG+QZ6fdEUObICavU+aB3Mp8HY4n14wYHdxK4fXjPmv3PXZZeY5RaguJmGyeH/CJQhX3fqKUtS4qc1LoHwhQ==" "integrity": "sha512-sTcG+QZ6fdEUObICavU+aB3Mp8HY4n14wYHdxK4fXjPmv3PXZZeY5RaguJmGyeH/CJQhX3fqKUtS4qc1LoHwhQ=="
}, },
"node_modules/@hookform/resolvers": {
"version": "3.10.0",
"resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.10.0.tgz",
"integrity": "sha512-79Dv+3mDF7i+2ajj7SkypSKHhl1cbln1OGavqrsF7p6mbUv11xpqpacPsGDCTRvCSjEEIez2ef1NveSVL3b0Ag==",
"license": "MIT",
"peerDependencies": {
"react-hook-form": "^7.0.0"
}
},
"node_modules/@humanwhocodes/config-array": { "node_modules/@humanwhocodes/config-array": {
"version": "0.11.11", "version": "0.11.11",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz",
@ -1447,6 +1459,24 @@
} }
} }
}, },
"node_modules/@radix-ui/react-alert-dialog/node_modules/@radix-ui/react-slot": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz",
"integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-arrow": { "node_modules/@radix-ui/react-arrow": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.0.tgz",
@ -1632,6 +1662,24 @@
} }
} }
}, },
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz",
"integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-dialog/node_modules/react-remove-scroll": { "node_modules/@radix-ui/react-dialog/node_modules/react-remove-scroll": {
"version": "2.6.0", "version": "2.6.0",
"resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.0.tgz", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.0.tgz",
@ -1737,6 +1785,52 @@
} }
} }
}, },
"node_modules/@radix-ui/react-label": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.2.tgz",
"integrity": "sha512-zo1uGMTaNlHehDyFQcDZXRJhUPDuukcnHz0/jnrup0JA6qL+AFpAnty+7VKa9esuU5xTblAZzTGYJKSKaBxBhw==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-primitive": "2.0.2"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-label/node_modules/@radix-ui/react-primitive": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.2.tgz",
"integrity": "sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-slot": "1.1.2"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-popover": { "node_modules/@radix-ui/react-popover": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.1.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.1.tgz",
@ -1773,6 +1867,24 @@
} }
} }
}, },
"node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-slot": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz",
"integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-popper": { "node_modules/@radix-ui/react-popper": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.0.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.0.tgz",
@ -1872,10 +1984,11 @@
} }
} }
}, },
"node_modules/@radix-ui/react-slot": { "node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz",
"integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==", "integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==",
"license": "MIT",
"dependencies": { "dependencies": {
"@radix-ui/react-compose-refs": "1.1.0" "@radix-ui/react-compose-refs": "1.1.0"
}, },
@ -1889,6 +2002,39 @@
} }
} }
}, },
"node_modules/@radix-ui/react-slot": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz",
"integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-slot/node_modules/@radix-ui/react-compose-refs": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz",
"integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-use-callback-ref": { "node_modules/@radix-ui/react-use-callback-ref": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz",
@ -2785,22 +2931,15 @@
} }
}, },
"node_modules/class-variance-authority": { "node_modules/class-variance-authority": {
"version": "0.7.0", "version": "0.7.1",
"resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.0.tgz", "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
"integrity": "sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A==", "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==",
"license": "Apache-2.0",
"dependencies": { "dependencies": {
"clsx": "2.0.0" "clsx": "^2.1.1"
}, },
"funding": { "funding": {
"url": "https://joebell.co.uk" "url": "https://polar.sh/cva"
}
},
"node_modules/class-variance-authority/node_modules/clsx": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz",
"integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==",
"engines": {
"node": ">=6"
} }
}, },
"node_modules/client-only": { "node_modules/client-only": {
@ -2812,6 +2951,7 @@
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
"license": "MIT",
"engines": { "engines": {
"node": ">=6" "node": ">=6"
} }
@ -4998,6 +5138,7 @@
"version": "0.408.0", "version": "0.408.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.408.0.tgz", "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.408.0.tgz",
"integrity": "sha512-8kETAAeWmOvtGIr7HPHm51DXoxlfkNncQ5FZWXR+abX8saQwMYXANWIkUstaYtcKSo/imOe/q+tVFA8ANzdSVA==", "integrity": "sha512-8kETAAeWmOvtGIr7HPHm51DXoxlfkNncQ5FZWXR+abX8saQwMYXANWIkUstaYtcKSo/imOe/q+tVFA8ANzdSVA==",
"license": "ISC",
"peerDependencies": { "peerDependencies": {
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
} }
@ -5803,18 +5944,19 @@
} }
}, },
"node_modules/react-hook-form": { "node_modules/react-hook-form": {
"version": "7.45.2", "version": "7.54.2",
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.45.2.tgz", "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.54.2.tgz",
"integrity": "sha512-9s45OdTaKN+4NSTbXVqeDITd/nwIg++nxJGL8+OD5uf1DxvhsXQ641kaYHk5K28cpIOTYm71O/fYk7rFaygb3A==", "integrity": "sha512-eHpAUgUjWbZocoQYUHposymRb4ZP6d0uwUnooL2uOybA9/3tPUvoAKqEWK1WaSiTxxOfTpffNZP7QwlnM3/gEg==",
"license": "MIT",
"engines": { "engines": {
"node": ">=12.22.0" "node": ">=18.0.0"
}, },
"funding": { "funding": {
"type": "opencollective", "type": "opencollective",
"url": "https://opencollective.com/react-hook-form" "url": "https://opencollective.com/react-hook-form"
}, },
"peerDependencies": { "peerDependencies": {
"react": "^16.8.0 || ^17 || ^18" "react": "^16.8.0 || ^17 || ^18 || ^19"
} }
}, },
"node_modules/react-icons": { "node_modules/react-icons": {
@ -6666,9 +6808,10 @@
} }
}, },
"node_modules/tailwind-merge": { "node_modules/tailwind-merge": {
"version": "2.4.0", "version": "2.6.0",
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.4.0.tgz", "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.0.tgz",
"integrity": "sha512-49AwoOQNKdqKPd9CViyH5wJoSKsCDjUlzL8DxuGp3P1FsGY36NJDAa18jLZcaHAUUuTj+JB8IAo8zWgBNvBF7A==", "integrity": "sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==",
"license": "MIT",
"funding": { "funding": {
"type": "github", "type": "github",
"url": "https://github.com/sponsors/dcastil" "url": "https://github.com/sponsors/dcastil"
@ -6719,6 +6862,7 @@
"version": "1.0.7", "version": "1.0.7",
"resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz", "resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz",
"integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==", "integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==",
"license": "MIT",
"peerDependencies": { "peerDependencies": {
"tailwindcss": ">=3.0.0 || insiders" "tailwindcss": ">=3.0.0 || insiders"
} }
@ -7322,6 +7466,15 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/zod": {
"version": "3.24.1",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz",
"integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
},
"node_modules/zustand": { "node_modules/zustand": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.1.tgz", "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.1.tgz",
@ -7604,6 +7757,12 @@
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.5.tgz", "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.5.tgz",
"integrity": "sha512-sTcG+QZ6fdEUObICavU+aB3Mp8HY4n14wYHdxK4fXjPmv3PXZZeY5RaguJmGyeH/CJQhX3fqKUtS4qc1LoHwhQ==" "integrity": "sha512-sTcG+QZ6fdEUObICavU+aB3Mp8HY4n14wYHdxK4fXjPmv3PXZZeY5RaguJmGyeH/CJQhX3fqKUtS4qc1LoHwhQ=="
}, },
"@hookform/resolvers": {
"version": "3.10.0",
"resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.10.0.tgz",
"integrity": "sha512-79Dv+3mDF7i+2ajj7SkypSKHhl1cbln1OGavqrsF7p6mbUv11xpqpacPsGDCTRvCSjEEIez2ef1NveSVL3b0Ag==",
"requires": {}
},
"@humanwhocodes/config-array": { "@humanwhocodes/config-array": {
"version": "0.11.11", "version": "0.11.11",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz",
@ -8133,6 +8292,14 @@
"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz",
"integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==", "integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==",
"requires": {} "requires": {}
},
"@radix-ui/react-slot": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz",
"integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==",
"requires": {
"@radix-ui/react-compose-refs": "1.1.0"
}
} }
} }
}, },
@ -8219,6 +8386,14 @@
"@radix-ui/react-use-layout-effect": "1.1.0" "@radix-ui/react-use-layout-effect": "1.1.0"
} }
}, },
"@radix-ui/react-slot": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz",
"integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==",
"requires": {
"@radix-ui/react-compose-refs": "1.1.0"
}
},
"react-remove-scroll": { "react-remove-scroll": {
"version": "2.6.0", "version": "2.6.0",
"resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.0.tgz", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.0.tgz",
@ -8269,6 +8444,24 @@
"@radix-ui/react-use-layout-effect": "1.1.0" "@radix-ui/react-use-layout-effect": "1.1.0"
} }
}, },
"@radix-ui/react-label": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.2.tgz",
"integrity": "sha512-zo1uGMTaNlHehDyFQcDZXRJhUPDuukcnHz0/jnrup0JA6qL+AFpAnty+7VKa9esuU5xTblAZzTGYJKSKaBxBhw==",
"requires": {
"@radix-ui/react-primitive": "2.0.2"
},
"dependencies": {
"@radix-ui/react-primitive": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.2.tgz",
"integrity": "sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==",
"requires": {
"@radix-ui/react-slot": "1.1.2"
}
}
}
},
"@radix-ui/react-popover": { "@radix-ui/react-popover": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.1.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.1.tgz",
@ -8289,6 +8482,16 @@
"@radix-ui/react-use-controllable-state": "1.1.0", "@radix-ui/react-use-controllable-state": "1.1.0",
"aria-hidden": "^1.1.1", "aria-hidden": "^1.1.1",
"react-remove-scroll": "2.5.7" "react-remove-scroll": "2.5.7"
},
"dependencies": {
"@radix-ui/react-slot": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz",
"integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==",
"requires": {
"@radix-ui/react-compose-refs": "1.1.0"
}
}
} }
}, },
"@radix-ui/react-popper": { "@radix-ui/react-popper": {
@ -8332,14 +8535,32 @@
"integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==",
"requires": { "requires": {
"@radix-ui/react-slot": "1.1.0" "@radix-ui/react-slot": "1.1.0"
},
"dependencies": {
"@radix-ui/react-slot": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz",
"integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==",
"requires": {
"@radix-ui/react-compose-refs": "1.1.0"
}
}
} }
}, },
"@radix-ui/react-slot": { "@radix-ui/react-slot": {
"version": "1.1.0", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz",
"integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==", "integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==",
"requires": { "requires": {
"@radix-ui/react-compose-refs": "1.1.0" "@radix-ui/react-compose-refs": "1.1.1"
},
"dependencies": {
"@radix-ui/react-compose-refs": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz",
"integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==",
"requires": {}
}
} }
}, },
"@radix-ui/react-use-callback-ref": { "@radix-ui/react-use-callback-ref": {
@ -8938,18 +9159,11 @@
} }
}, },
"class-variance-authority": { "class-variance-authority": {
"version": "0.7.0", "version": "0.7.1",
"resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.0.tgz", "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
"integrity": "sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A==", "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==",
"requires": { "requires": {
"clsx": "2.0.0" "clsx": "^2.1.1"
},
"dependencies": {
"clsx": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz",
"integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q=="
}
} }
}, },
"client-only": { "client-only": {
@ -11111,9 +11325,9 @@
"requires": {} "requires": {}
}, },
"react-hook-form": { "react-hook-form": {
"version": "7.45.2", "version": "7.54.2",
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.45.2.tgz", "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.54.2.tgz",
"integrity": "sha512-9s45OdTaKN+4NSTbXVqeDITd/nwIg++nxJGL8+OD5uf1DxvhsXQ641kaYHk5K28cpIOTYm71O/fYk7rFaygb3A==", "integrity": "sha512-eHpAUgUjWbZocoQYUHposymRb4ZP6d0uwUnooL2uOybA9/3tPUvoAKqEWK1WaSiTxxOfTpffNZP7QwlnM3/gEg==",
"requires": {} "requires": {}
}, },
"react-icons": { "react-icons": {
@ -11691,9 +11905,9 @@
} }
}, },
"tailwind-merge": { "tailwind-merge": {
"version": "2.4.0", "version": "2.6.0",
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.4.0.tgz", "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.0.tgz",
"integrity": "sha512-49AwoOQNKdqKPd9CViyH5wJoSKsCDjUlzL8DxuGp3P1FsGY36NJDAa18jLZcaHAUUuTj+JB8IAo8zWgBNvBF7A==" "integrity": "sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA=="
}, },
"tailwindcss": { "tailwindcss": {
"version": "3.3.1", "version": "3.3.1",
@ -12141,6 +12355,11 @@
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
"dev": true "dev": true
}, },
"zod": {
"version": "3.24.1",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz",
"integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A=="
},
"zustand": { "zustand": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.1.tgz", "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.1.tgz",

View File

@ -11,12 +11,14 @@
"dependencies": { "dependencies": {
"@emotion/react": "^11.10.6", "@emotion/react": "^11.10.6",
"@emotion/styled": "^11.10.6", "@emotion/styled": "^11.10.6",
"@hookform/resolvers": "^3.10.0",
"@mui/material": "^5.12.1", "@mui/material": "^5.12.1",
"@mui/x-date-pickers": "^6.5.0", "@mui/x-date-pickers": "^6.5.0",
"@radix-ui/react-alert-dialog": "^1.1.2", "@radix-ui/react-alert-dialog": "^1.1.2",
"@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-label": "^2.1.2",
"@radix-ui/react-popover": "^1.1.1", "@radix-ui/react-popover": "^1.1.1",
"@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-slot": "^1.1.2",
"@tanstack/react-query": "^4.32.0", "@tanstack/react-query": "^4.32.0",
"@tanstack/react-query-devtools": "^4.32.0", "@tanstack/react-query-devtools": "^4.32.0",
"@types/node": "18.15.13", "@types/node": "18.15.13",
@ -25,7 +27,7 @@
"@types/uuid": "^9.0.1", "@types/uuid": "^9.0.1",
"autoprefixer": "10.4.14", "autoprefixer": "10.4.14",
"axios": "^1.5.1", "axios": "^1.5.1",
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"date-fns": "^2.30.0", "date-fns": "^2.30.0",
"dayjs": "^1.11.7", "dayjs": "^1.11.7",
@ -39,19 +41,20 @@
"react-day-picker": "^8.10.1", "react-day-picker": "^8.10.1",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-fast-marquee": "^1.3.5", "react-fast-marquee": "^1.3.5",
"react-hook-form": "^7.43.9", "react-hook-form": "^7.54.2",
"react-icons": "^4.8.0", "react-icons": "^4.8.0",
"react-player": "^2.12.0", "react-player": "^2.12.0",
"react-slot-counter": "^3.0.4", "react-slot-counter": "^3.0.4",
"react-use": "^17.5.1", "react-use": "^17.5.1",
"sharp": "^0.33.5", "sharp": "^0.33.5",
"swiper": "^9.2.4", "swiper": "^9.2.4",
"tailwind-merge": "^2.4.0", "tailwind-merge": "^2.6.0",
"tailwindcss": "3.3.1", "tailwindcss": "3.3.1",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"typescript": "^5.2.2", "typescript": "^5.2.2",
"usehooks-ts": "^2.9.1", "usehooks-ts": "^2.9.1",
"uuid": "^9.0.0", "uuid": "^9.0.0",
"zod": "^3.24.1",
"zustand": "^5.0.1" "zustand": "^5.0.1"
}, },
"devDependencies": { "devDependencies": {

View File

@ -7,7 +7,6 @@ import { useState, useMemo, useEffect, ReactNode } from 'react';
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
import { IBurger } from '@/typings/burger.type'; import { IBurger } from '@/typings/burger.type';
import { IAdvertisment, IAdvertismentContext } from '@/typings/advertisment.type';
interface IProps { interface IProps {
children: ReactNode; children: ReactNode;

View File

@ -1,7 +1,8 @@
export default { export default {
newsItem: (id: string) => `/posts/${id}`, newsItem: (id: string) => `/posts/${id}`,
pageItem: (id: string) => `/pages/${id}`, pageItem: (id: string) => `/pages/${id}`,
channelItem: (channel: number) => `/timetable?on_channel=1&channel=${channel}`, channelItem: (channel: number) =>
`/timetable?on_channel=1&channel=${channel}`,
smallSwiper: (type: string) => `/slider?type=${type}`, smallSwiper: (type: string) => `/slider?type=${type}`,
videos: (search: string) => `/materials${search}`, videos: (search: string) => `/materials${search}`,
video: (video_id: number) => `/material/${video_id}`, video: (video_id: number) => `/material/${video_id}`,
@ -15,35 +16,35 @@ export default {
// =================================================================== // ===================================================================
// Votes ================================================================ // Votes ================================================================
allVotes: '/voting/show_on_site', allVotes: "/voting/show_on_site",
vote: (vote_id: string) => `/voting/${vote_id}`, vote: (vote_id: string) => `/voting/${vote_id}`,
// ====================================================================== // ======================================================================
// Lottery ================================================================ // Lottery ================================================================
lotteryActive: '/lottery/active', lotteryActive: "/lottery/active",
lotteryId: (lottery_id: string) => `/lottery/${lottery_id}`, tossId: (type: string, id: string) => `/${type}/${id}`,
// ====================================================================== // ======================================================================
addPost: '/mahabat/order', addPost: "/mahabat/order",
news: '/pagination/new/posts', news: "/pagination/new/posts",
lastVideos: '/materials?per_page=30', lastVideos: "/materials?per_page=30",
categories: '/categories', categories: "/categories",
channels: '/channels', channels: "/channels",
banner: '/mahabatlar', banner: "/mahabatlar",
// home: '/mahabatlar?type=home', // home: '/mahabatlar?type=home',
home: '/slider?type=big', home: "/slider?type=big",
marquee: '/timetable', marquee: "/timetable",
homeSmallSlider_1: '/slider?type=small', homeSmallSlider_1: "/slider?type=small",
homeSmallSlider_2: '/slider?type=small2', homeSmallSlider_2: "/slider?type=small2",
homeSmallSlider_3: '/slider?type=hazyna', homeSmallSlider_3: "/slider?type=hazyna",
homeSmallSlider_4: '/slider?type=mahabat', homeSmallSlider_4: "/slider?type=mahabat",
properties: '/mahabat/property-types', properties: "/mahabat/property-types",
addViews: (video_id: string) => `material/${video_id}/views/increment`, addViews: (video_id: string) => `material/${video_id}/views/increment`,
// Sms ======================================================================== // Sms ========================================================================
myTvAdmins: '/my-tv-admins', myTvAdmins: "/my-tv-admins",
messagesByTvAdmin: (id: number) => `/messages-by-tv-admin/${id}`, messagesByTvAdmin: (id: number) => `/messages-by-tv-admin/${id}`,
}; };

12
store/store.ts Normal file
View File

@ -0,0 +1,12 @@
import { create } from "zustand";
interface ILotteryStatus {
status: "Upcoming" | "Finished" | "Ongoing";
setStatus: (value: "Upcoming" | "Finished" | "Ongoing") => void;
}
export const useLotteryStatus = create<ILotteryStatus>((set) => ({
status: "Upcoming",
setStatus: (value: "Upcoming" | "Finished" | "Ongoing") =>
set({ status: value }),
}));

View File

@ -67,7 +67,6 @@ const config = {
from: { height: 'var(--radix-accordion-content-height)' }, from: { height: 'var(--radix-accordion-content-height)' },
to: { height: '0' }, to: { height: '0' },
}, },
'dots-flash': { 'dots-flash': {
from: { opacity: '0' }, from: { opacity: '0' },
to: { opacity: '1' }, to: { opacity: '1' },
@ -76,7 +75,6 @@ const config = {
animation: { animation: {
'accordion-down': 'accordion-down 0.2s ease-out', 'accordion-down': 'accordion-down 0.2s ease-out',
'accordion-up': 'accordion-up 0.2s ease-out', 'accordion-up': 'accordion-up 0.2s ease-out',
'dots-flash': 'dots-flash 1s infinite;',
}, },
}, },
}, },