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 { IVote } from "@/models/vote.model";
import routes from "@/routes";
import { CloudFog } from "lucide-react";
import { cookies } from "next/headers";
export class Queries {
public static async getNews(
@ -259,28 +259,26 @@ export class Queries {
).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 MobileMenu from "@/components/MobileMenu";
import Nav from "@/components/Nav";
import GlobalContext from "@/context/GlobalContext";
import MainProvider from "@/providers/MainProvider";
interface IProps {
@ -13,11 +11,9 @@ const RootLayout = ({ children }: IProps) => {
return (
<div className="z-20 relative">
<MainProvider>
{/* <Buble /> */}
<div className="bg-white dark:bg-black transition-all h-full">
<h1 className="hidden">Turkmen TV</h1>
<div className="bg-white dark:bg-black transition-all min-h-screen flex flex-col">
<Nav />
<main className="min-h-[50vh]">{children}</main>
<main className="flex-1">{children}</main>
<Footer />
<MobileMenu />
</div>

View File

@ -1,11 +1,9 @@
'use client';
import LotteryAuthForm from '@/components/lottery/auth/LotteryAuthForm';
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]">
<div className="flex justify-center items-center py-[200px]">
<LotteryAuthForm />
</div>
</div>

View File

@ -1,114 +1,16 @@
"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 { authenticateLottery } from "@/api";
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 { 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>;
}
const Page = async () => {
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>
<Suspense fallback={<Loader />}>
<LotteryMain />
</Suspense>
);
};
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';
import { Queries } from '@/api/queries';
import Loader from '@/components/Loader';
import QuizQuestion from '@/components/quiz/QuizQuestion';
import QuizQuestionList from '@/components/quiz/QuizQuestionList';
import QuizSearch from '@/components/quiz/QuizSearch';
import QuizTable from '@/components/quiz/QuizTable';

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

View File

@ -1,17 +1,21 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"style": "new-york",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"config": "tailwind.config.js",
"css": "app/globals.css",
"baseColor": "slate",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"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>
<ul className="md:flex gap-5 items-center justify-start hidden">
{/* <li>
<Link
className="block text-lg text-black transition-all font-roboto font-bold dark:text-white"
href={'/news'}
style={path.includes('news') ? { color: '#FFAB48' } : {}}>
Habarlar
</Link>
</li> */}
<li>
<Link
href={'/treasury'}

View File

@ -1,9 +1,8 @@
'use client';
import { useState, useEffect } from 'react';
import ReactConfetti from 'react-confetti';
import { useWindowSize } from 'react-use';
import { useMediaQuery } from 'usehooks-ts';
"use client";
import { useEffect, useState } from "react";
import ReactConfetti from "react-confetti";
import { useWindowSize } from "react-use";
import { useMediaQuery } from "usehooks-ts";
const Confetti = ({
numberOfPieces = 200,
@ -12,27 +11,31 @@ const Confetti = ({
numberOfPieces?: number;
showConfetti: boolean;
}) => {
const [recycle, setRecycle] = useState<boolean>(true);
const { width, height } = useWindowSize();
const colors = [
'linear-gradient(45deg, #5D5D72, #8589DE)',
'linear-gradient(45deg, #E1E0FF, #575992)',
'#8589DE',
'#575992',
'#E1E0FF',
'#FF3131',
"linear-gradient(45deg, #5D5D72, #8589DE)",
"linear-gradient(45deg, #E1E0FF, #575992)",
"#8589DE",
"#575992",
"#E1E0FF",
"#FF3131",
];
const mobile = useMediaQuery('(max-width: 426px)');
const mobile = useMediaQuery("(max-width: 426px)");
useEffect(() => {
setTimeout(() => setRecycle(false), 30000);
}, []);
return (
<div className="fixed top-0 left-0 z-50">
<ReactConfetti
width={width}
height={height}
recycle={showConfetti}
numberOfPieces={mobile ? numberOfPieces / 3 : numberOfPieces}
recycle={recycle}
numberOfPieces={mobile ? 200 / 3 : 200}
tweenDuration={500}
// run={true}
colors={colors}
/>
</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 SLOT_COUNTER_DURATION = 30000;
const LotteryWinnersSection = ({
lotteryStatus,
}: {
lotteryStatus: string;
}) => {
const LotteryWinnersSection = ({ data }: { data: any }) => {
const [winners, setWinners] = useState<LotteryWinnerDataSimplified[]>([]);
const [currentNumber, setCurrentNumber] = useState<string>("00-00-00-00-00");
const [isConfettiActive, setIsConfettiActive] = useState(false);
const { lotteryData } = useLotteryAuth();
const [winnerSelectingStatus, setWinnerSelectingStatus] = useState<
"not-selected" | "is-selecting" | "selected"
>("not-selected");
@ -28,52 +22,19 @@ const LotteryWinnersSection = ({
useState<LotteryWinnerDataSimplified | null>(null);
const [topText, setTopText] = useState<string>("Bije az wagtdan başlaýar");
const [bottomText, setBottomText] = useState<string>("");
const [messageQueue, setMessageQueue] = useState<
LotteryWinnerDataSimplified[]
>([]); // Queue for incoming WebSocket messages
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(
`${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
useEffect(() => {
console.log("🎟️ Lottery Data:", lotteryData);
if (lotteryData && lotteryData.data.winners.length > 0) {
const simplifiedWinners = lotteryData.data.winners.map((winner) => ({
if (data?.data?.winners.length > 0) {
const simplifiedWinners = data.data.winners.map((winner: any) => ({
phone: winner.client,
winner_no: winner.winner_no,
ticket: winner.ticket,
@ -84,20 +45,19 @@ const LotteryWinnersSection = ({
setWinnerSelectingStatus("selected");
setTopText(`${lastWinner.winner_no}-nji ýeňiji`);
setBottomText(lastWinner.phone);
setStartNumber(lastWinner.ticket.replace(/-/g, ","));
setIsConfettiActive(true);
}
}, [lotteryData]);
}, [data]);
// Subscribe to WebSocket messages
useEffect(() => {
const unsubscribe = subscribeToMessages((event) => {
try {
const newWinner: LotteryWinnerDataSimplified = JSON.parse(event.data);
console.log("📩 WebSocket Message Received:", newWinner); // Log the parsed message
// Add new message to the queue
setMessageQueue((prevQueue) => {
console.log("📥 Adding to Queue:", newWinner);
return [...prevQueue, newWinner];
});
} catch (error) {
@ -110,8 +70,6 @@ const LotteryWinnersSection = ({
// Process queue when a new message is added
useEffect(() => {
console.log("📋 Current Message Queue:", messageQueue);
if (!isProcessing && messageQueue.length > 0) {
processQueue();
}
@ -124,10 +82,8 @@ const LotteryWinnersSection = ({
setIsProcessing(true); // Lock processing
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 {
await handleMessage(message); // Process the message
await handleMessage(message);
} catch (error) {
console.error("Error processing message:", error);
}
@ -138,7 +94,7 @@ const LotteryWinnersSection = ({
// Handle the logic for processing a single WebSocket message
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);
setTopText(`${winner.winner_no}-nji ýeňiji saýlanýar`);
setBottomText("...");
@ -152,7 +108,6 @@ const LotteryWinnersSection = ({
// Finalize winner selection
setTopText(`${winner.winner_no}-nji ýeňiji`);
setBottomText(winner.phone);
console.log("⬇️ Finalized Bottom Text:", winner.phone); // Debug Log 6: Log the final bottomText update
setWinnerSelectingStatus("selected");
setIsConfettiActive(true);
@ -172,16 +127,6 @@ const LotteryWinnersSection = ({
)}
<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="flex flex-col items-center rounded-[32px] gap-[40px]"
@ -234,7 +179,10 @@ const LotteryWinnersSection = ({
<div className="z-10">
{currentNumber && (
<LotterySlotCounter numberString={currentNumber} />
<LotterySlotCounter
numberString={currentNumber}
startNumber={startNumber}
/>
)}
</div>
<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,126 +1,101 @@
"use client";
import { Queries } from "@/api/queries";
import { useState, FormEvent } from "react";
import { useState } from "react";
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 [phone, setPhone] = useState("");
const [code, setCode] = useState("");
const form = useForm<z.infer<typeof lotteryAuthSchema>>({
resolver: zodResolver(lotteryAuthSchema),
});
const [error, setError] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(false);
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);
try {
const response = await Queries.authenticateLottery(phone, code);
const response = await authenticateLottery(phoneNumber, key);
if (response.errorMessage) {
setError(response.errorMessage);
} else {
localStorage.setItem("lotteryPhone", phone);
localStorage.setItem("lotteryCode", code);
setAuth(response, phone, code);
console.log(response);
document.cookie = `phoneNumber=${phoneNumber};path=/`;
document.cookie = `key=${key};path=/`;
router.replace("/lottery");
}
} catch (err) {
console.error("Authentication error:", err);
setError("Telefon belgisi ýa-da açar nädogry");
} finally {
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 (
<Form {...form}>
<form
onSubmit={handleSubmit}
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"
>
<FormField
control={form.control}
name="phoneNumber"
render={({ field }) => (
<FormItem className="flex flex-col gap-[8px]">
<FormLabel className="font-base-medium text-lightOnSurface">
Telefon
</label>
</FormLabel>
<FormControl>
<input
type="tel"
value={phone}
onChange={handlePhoneChange}
{...field}
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"
>
</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
</label>
</FormLabel>
<FormControl>
<input
type="text"
value={code}
onChange={handleCodeChange}
{...field}
className="px-[16px] py-[12px] bg-lightPrimaryContainer rounded-[12px] outline-none text-lightOnSurfaceVariant text-textSmall leading-textSmall"
placeholder="C5-0105639808"
required
id="code"
/>
</div>
</FormControl>
<FormMessage className="text-red-500" />
</FormItem>
)}
/>
{error && (
<p className="text-lightError text-textSmall leading-textSmall">
{error}
@ -129,12 +104,12 @@ const LotteryAuthForm = () => {
</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ş"}
</button>
</form>
</Form>
);
};

View File

@ -1,34 +1,31 @@
"use client";
import { calculateTimeLeft } from "@/lib/hooks/useCalculateTimeLeft";
import { useLotteryStatus } from "@/store/store";
import React, { useState, useEffect, Dispatch, SetStateAction } from "react";
interface LotteryCountDownProps {
startDate: string; // Event start date in "YYYY-MM-DD HH:mm:ss" format
endDate: string; // Event end date in "YYYY-MM-DD HH:mm:ss" format
lotteryStatus: string;
setLotteryStatus: Dispatch<
SetStateAction<"not-started" | "started" | "ended">
>;
lotteryStatus: "Upcoming" | "Ongoing" | "Finished";
}
const LotteryCountDown: React.FC<LotteryCountDownProps> = ({
startDate,
endDate,
lotteryStatus,
setLotteryStatus,
}) => {
const [timeLeft, setTimeLeft] = useState({
hours: 0,
minutes: 0,
seconds: 0,
});
console.log(lotteryStatus);
const { status, setStatus } = useLotteryStatus();
useEffect(() => {
setStatus(lotteryStatus);
const timer = setInterval(() => {
if (lotteryStatus === "not-started") {
if (lotteryStatus === "Upcoming") {
const timeToStart = calculateTimeLeft(startDate);
setTimeLeft(timeToStart);
@ -37,9 +34,9 @@ const LotteryCountDown: React.FC<LotteryCountDownProps> = ({
timeToStart.minutes === 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);
setTimeLeft(timeToEnd);
@ -48,32 +45,32 @@ const LotteryCountDown: React.FC<LotteryCountDownProps> = ({
timeToEnd.minutes === 0 &&
timeToEnd.seconds === 0
) {
setLotteryStatus("ended"); // Update status to "finished"
setStatus("Finished"); // Update status to "finished"
}
}
}, 1000);
return () => clearInterval(timer); // Clean up interval on component unmount
}, [startDate, endDate, lotteryStatus, setLotteryStatus]);
}, [startDate, endDate, lotteryStatus]);
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">
<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"
: lotteryStatus === "ended"
: status === "Finished"
? "Bije tamamlandy"
: "Bije"}
</h3>
{/* LotteryCountDown */}
{lotteryStatus === "not-started" && (
{status === "Upcoming" && (
<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">
<h3 className="md:text-[80px] sm:text-[56px] text-[28px] md:leading-[88px] sm:leading-[64px] leading-[36px] -tracking-[1%]">
{timeLeft.hours}
</h3>
<h4 className="font-medium md:text-[20px] sm:text-[18px] text-[14px] sm:leading-[28px] leading-[20px] -tracking-[1%] text-lightOnSurfaceVariant">
hours
sagat
</h4>
</div>
@ -88,7 +85,7 @@ const LotteryCountDown: React.FC<LotteryCountDownProps> = ({
{timeLeft.minutes}
</h3>
<h4 className="font-medium md:text-[20px] sm:text-[18px] text-[14px] sm:leading-[28px] leading-[20px] -tracking-[1%] text-lightOnSurfaceVariant">
minutes
minut
</h4>
</div>
@ -103,7 +100,7 @@ const LotteryCountDown: React.FC<LotteryCountDownProps> = ({
{timeLeft.seconds}
</h3>
<h4 className="font-medium md:text-[20px] sm:text-[18px] text-[14px] sm:leading-[28px] leading-[20px] -tracking-[1%] text-lightOnSurfaceVariant">
seconds
sekunt
</h4>
</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]">
<span>
{lotteryStatus === "not-started"
{status === "Upcoming"
? "- den başlar"
: lotteryStatus === "started"
: status === "Ongoing"
? "girmek üçin aşakda kodyňyzy giriziň"
: "netijeleri görmek üçin aşakda kodyňyzy giriziň"}
</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 {
title: string;
content: string;
show?: boolean;
data?: any;
}
const LotteryRulesSection = () => {
const { lotteryData } = useLotteryAuth();
const WEBSOCKET_URL = "wss://sms.turkmentv.gov.tm/ws/lottery?dst=";
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 (
<section>
@ -16,10 +66,10 @@ const LotteryRulesSection = () => {
Düzgünleri:
</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">
<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}>
{item.title}
</li>
@ -27,19 +77,31 @@ const LotteryRulesSection = () => {
</ul>
</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">
<h3 className="md:font-heading-5-regular sm:text-[20px] text-[18px] sm:leading-[24px] leading-[28px]">
Siziň kodlaryňyz:
Siziň bijeli sanynyz:
</h3>
<ul className="list-disc flex flex-col md:gap-4 gap-2 pl-[16px]">
{lotteryData?.user_lottery_numbers.map((item, i) => (
<li className="font-small-regular" key={i}>
<ul className="flex flex-col items-center md:gap-4 gap-2">
{data?.user_lottery_numbers.map((item: any, i: number) => (
<li
className="text-[24px] text-[#46464F] md:text-[48px] lg:text-[80px] list-none"
key={i}
>
{item}
</li>
))}
</ul>
</div>
</div>
)}
</div>
</div>
</section>

View File

@ -1,23 +1,26 @@
'use client';
import Image from 'next/image';
import React, { useEffect, useRef, useState } from 'react';
import SlotCounter, { SlotCounterRef } from 'react-slot-counter';
import { useMediaQuery } from 'usehooks-ts';
"use client";
import Image from "next/image";
import React, { useEffect, useRef, useState } from "react";
import SlotCounter, { SlotCounterRef } from "react-slot-counter";
import { useMediaQuery } from "usehooks-ts";
interface LotterySlotCounterProps {
numberString: string;
startNumber: string;
}
const LotterySlotCounter = ({ numberString }: LotterySlotCounterProps) => {
const [formattedNumber, setFormattedNumber] = useState(numberString);
const LotterySlotCounter = ({
numberString,
startNumber,
}: LotterySlotCounterProps) => {
const [formattedNumber, setFormattedNumber] = useState("00,00,00,00,00");
const slotCounterRef = useRef<SlotCounterRef>(null); // Ref for manual control
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(() => {
const formatted = numberString.replace(/-/g, ',');
const formatted = numberString.replace(/-/g, ",");
setFormattedNumber(formatted);
// 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"
style={{
background:
'linear-gradient(180deg, #454673 0%, #575992 10.5%, #575992 90%, #454673 100%)',
boxShadow: '0px 4px 4px 0px #00000040',
}}>
"linear-gradient(180deg, #454673 0%, #575992 10.5%, #575992 90%, #454673 100%)",
boxShadow: "0px 4px 4px 0px #00000040",
}}
>
{/* Highlight */}
<div
className="absolute top-[50%] -translate-y-1/2 left-0 w-full h-full"
style={{
background:
'linear-gradient(180deg, rgba(87, 89, 146, 0) 0%, #7274AB 50%, rgba(87, 89, 146, 0) 100%)',
}}></div>
"linear-gradient(180deg, rgba(87, 89, 146, 0) 0%, #7274AB 50%, rgba(87, 89, 146, 0) 100%)",
}}
></div>
<div className="z-10">
<SlotCounter
ref={slotCounterRef}
value={formattedNumber}
startValue={'00,00,00,00,00'}
startValue={startNumber}
charClassName="rolling-number"
separatorClassName="slot-seperator"
duration={3}

View File

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

View File

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

View File

@ -1,11 +1,10 @@
'use client';
import { Queries } from '@/api/queries';
"use client";
import { Queries } from "@/api/queries";
import { v4 } from 'uuid';
import { useState, useEffect, useContext, Dispatch, SetStateAction } from 'react';
import { Answer, IQuizQuestionsWinners } from '@/models/quizQuestionsWinners.model';
import QuizContext from '@/context/QuizContext';
import { IQuizQuestions } from '@/models/quizQuestions.model';
import { v4 } from "uuid";
import { useState, useEffect, useContext } from "react";
import { IQuizQuestionsWinners } from "@/models/quizQuestionsWinners.model";
import QuizContext from "@/context/QuizContext";
interface Message {
answer: string;
@ -133,11 +132,11 @@ const QuizWinnerTable = ({ quizId, quizFinished, smsNumber }: IProps) => {
// Function to handle incoming WebSocket message and update winnersData
const handleOnMessage = (message: Message) => {
if (!winnersData) {
console.error('winnersData is undefined');
console.error("winnersData is undefined");
return;
}
console.log('updating winnersData');
console.log("updating winnersData");
// Update the winnersData by matching phone with starred_src from the message
setWinnersData((prevWinnersData) => {
@ -162,7 +161,10 @@ const QuizWinnerTable = ({ quizId, quizFinished, smsNumber }: IProps) => {
// Calculate the new correct_answers_time by summing serial_number_for_correct
const updatedCorrectAnswersTime = updatedAnswers
.reduce((sum, answer) => sum + answer.serial_number_for_correct, 0)
.reduce(
(sum, answer) => sum + answer.serial_number_for_correct,
0
)
.toString();
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 ? (
@ -231,9 +233,10 @@ const QuizWinnerTable = ({ quizId, quizFinished, smsNumber }: IProps) => {
{winnersData?.data.map((winner, id) => (
<div
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">
<span>{id + 1}</span>
</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">
{questionsData
? questionsData.map((question) => {
// const matchingAnswer = winner.client.answers.find(
// (answer) => answer.question_id === question.id,
// );
const matchingAnswer =
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(
(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 (
<span
key={v4()}
className={`text-sm font-semibold leading-[125%] ${
matchingAnswer && matchingAnswer.serial_number_for_correct !== 0
? 'text-fillGreen'
matchingAnswer &&
matchingAnswer.serial_number_for_correct !== 0
? "text-fillGreen"
: matchingAnswer &&
matchingAnswer?.serial_number_for_correct === 0
? 'text-fillRed'
: 'text-textLight'
}`}>
matchingAnswer?.serial_number_for_correct ===
0
? "text-fillRed"
: "text-textLight"
}`}
>
{matchingAnswer && matchingAnswer.score !== 0
? matchingAnswer.serial_number_for_correct
: matchingAnswer && matchingAnswer?.score === 0
? 'X'
: '0'}
? "X"
: "0"}
</span>
);
})
@ -345,9 +337,10 @@ const QuizWinnerTable = ({ quizId, quizFinished, smsNumber }: IProps) => {
{winnersData?.data.map((winner, id) => (
<div
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%] ">
<span>{id + 1}</span>
</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">
{questionsData
? questionsData.map((question) => {
const matchingAnswer = winner.client.answers.find(
(answer) => answer.question_id === question.id,
const matchingAnswer =
winner.client.answers.find(
(answer) =>
answer.question_id === question.id &&
answer.score > 0
) ||
winner.client.answers.find(
(answer) => answer.question_id === question.id
);
return (
<span
key={v4()}
className={`text-sm font-semibold leading-[125%] ${
matchingAnswer && matchingAnswer.serial_number_for_correct !== 0
? 'text-fillGreen'
matchingAnswer &&
matchingAnswer.serial_number_for_correct !==
0
? "text-fillGreen"
: matchingAnswer &&
matchingAnswer.serial_number_for_correct === 0
? 'text-fillRed'
: 'text-textLight'
}`}>
{matchingAnswer && matchingAnswer.serial_number_for_correct !== 0
matchingAnswer.serial_number_for_correct ===
0
? "text-fillRed"
: "text-textLight"
}`}
>
{matchingAnswer && matchingAnswer.score !== 0
? matchingAnswer.serial_number_for_correct
: matchingAnswer &&
matchingAnswer.serial_number_for_correct === 0
? 'X'
: '0'}
matchingAnswer?.score === 0
? "X"
: "0"}
</span>
);
})
@ -436,7 +439,9 @@ const QuizWinnerTable = ({ quizId, quizFinished, smsNumber }: IProps) => {
</div>
<div className="flex gap-[10px] 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>
<span className="text-base leading-[120%] text-textLight w-full">
@ -445,7 +450,9 @@ const QuizWinnerTable = ({ quizId, quizFinished, smsNumber }: IProps) => {
</div>
<div className="flex gap-[10px] items-center">
<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>
<span className="text-base leading-[120%] text-textLight">
Dogry jogaplara näçinji bolup jogap berdi
@ -453,7 +460,9 @@ const QuizWinnerTable = ({ quizId, quizFinished, smsNumber }: IProps) => {
</div>
<div className="flex gap-[10px] items-center ">
<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>
<span className="text-base leading-[120%] text-textLight">
Soraga nädogry jogap berdi
@ -462,9 +471,13 @@ const QuizWinnerTable = ({ quizId, quizFinished, smsNumber }: IProps) => {
</div>
<div className="flex gap-[10px] items-center ">
<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>
<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>

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

307
package-lock.json generated
View File

@ -10,12 +10,14 @@
"dependencies": {
"@emotion/react": "^11.10.6",
"@emotion/styled": "^11.10.6",
"@hookform/resolvers": "^3.10.0",
"@mui/material": "^5.12.1",
"@mui/x-date-pickers": "^6.5.0",
"@radix-ui/react-alert-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-slot": "^1.1.0",
"@radix-ui/react-slot": "^1.1.2",
"@tanstack/react-query": "^4.32.0",
"@tanstack/react-query-devtools": "^4.32.0",
"@types/node": "18.15.13",
@ -24,7 +26,7 @@
"@types/uuid": "^9.0.1",
"autoprefixer": "10.4.14",
"axios": "^1.5.1",
"class-variance-authority": "^0.7.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"date-fns": "^2.30.0",
"dayjs": "^1.11.7",
@ -38,19 +40,20 @@
"react-day-picker": "^8.10.1",
"react-dom": "^18.2.0",
"react-fast-marquee": "^1.3.5",
"react-hook-form": "^7.43.9",
"react-hook-form": "^7.54.2",
"react-icons": "^4.8.0",
"react-player": "^2.12.0",
"react-slot-counter": "^3.0.4",
"react-use": "^17.5.1",
"sharp": "^0.33.5",
"swiper": "^9.2.4",
"tailwind-merge": "^2.4.0",
"tailwind-merge": "^2.6.0",
"tailwindcss": "3.3.1",
"tailwindcss-animate": "^1.0.7",
"typescript": "^5.2.2",
"usehooks-ts": "^2.9.1",
"uuid": "^9.0.0",
"zod": "^3.24.1",
"zustand": "^5.0.1"
},
"devDependencies": {
@ -375,6 +378,15 @@
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.5.tgz",
"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": {
"version": "0.11.11",
"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": {
"version": "1.1.0",
"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": {
"version": "2.6.0",
"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": {
"version": "1.1.1",
"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": {
"version": "1.2.0",
"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",
"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"
},
@ -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": {
"version": "1.1.0",
"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": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.0.tgz",
"integrity": "sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A==",
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
"integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==",
"license": "Apache-2.0",
"dependencies": {
"clsx": "2.0.0"
"clsx": "^2.1.1"
},
"funding": {
"url": "https://joebell.co.uk"
}
},
"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"
"url": "https://polar.sh/cva"
}
},
"node_modules/client-only": {
@ -2812,6 +2951,7 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
@ -4998,6 +5138,7 @@
"version": "0.408.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.408.0.tgz",
"integrity": "sha512-8kETAAeWmOvtGIr7HPHm51DXoxlfkNncQ5FZWXR+abX8saQwMYXANWIkUstaYtcKSo/imOe/q+tVFA8ANzdSVA==",
"license": "ISC",
"peerDependencies": {
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
@ -5803,18 +5944,19 @@
}
},
"node_modules/react-hook-form": {
"version": "7.45.2",
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.45.2.tgz",
"integrity": "sha512-9s45OdTaKN+4NSTbXVqeDITd/nwIg++nxJGL8+OD5uf1DxvhsXQ641kaYHk5K28cpIOTYm71O/fYk7rFaygb3A==",
"version": "7.54.2",
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.54.2.tgz",
"integrity": "sha512-eHpAUgUjWbZocoQYUHposymRb4ZP6d0uwUnooL2uOybA9/3tPUvoAKqEWK1WaSiTxxOfTpffNZP7QwlnM3/gEg==",
"license": "MIT",
"engines": {
"node": ">=12.22.0"
"node": ">=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/react-hook-form"
},
"peerDependencies": {
"react": "^16.8.0 || ^17 || ^18"
"react": "^16.8.0 || ^17 || ^18 || ^19"
}
},
"node_modules/react-icons": {
@ -6666,9 +6808,10 @@
}
},
"node_modules/tailwind-merge": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.4.0.tgz",
"integrity": "sha512-49AwoOQNKdqKPd9CViyH5wJoSKsCDjUlzL8DxuGp3P1FsGY36NJDAa18jLZcaHAUUuTj+JB8IAo8zWgBNvBF7A==",
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.0.tgz",
"integrity": "sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/dcastil"
@ -6719,6 +6862,7 @@
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz",
"integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==",
"license": "MIT",
"peerDependencies": {
"tailwindcss": ">=3.0.0 || insiders"
}
@ -7322,6 +7466,15 @@
"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": {
"version": "5.0.1",
"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",
"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": {
"version": "0.11.11",
"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",
"integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==",
"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-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": {
"version": "2.6.0",
"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-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": {
"version": "1.1.1",
"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",
"aria-hidden": "^1.1.1",
"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": {
@ -8332,8 +8535,8 @@
"integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==",
"requires": {
"@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",
@ -8341,6 +8544,24 @@
"requires": {
"@radix-ui/react-compose-refs": "1.1.0"
}
}
}
},
"@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==",
"requires": {
"@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": {
"version": "1.1.0",
@ -8938,18 +9159,11 @@
}
},
"class-variance-authority": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.0.tgz",
"integrity": "sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A==",
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
"integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==",
"requires": {
"clsx": "2.0.0"
},
"dependencies": {
"clsx": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz",
"integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q=="
}
"clsx": "^2.1.1"
}
},
"client-only": {
@ -11111,9 +11325,9 @@
"requires": {}
},
"react-hook-form": {
"version": "7.45.2",
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.45.2.tgz",
"integrity": "sha512-9s45OdTaKN+4NSTbXVqeDITd/nwIg++nxJGL8+OD5uf1DxvhsXQ641kaYHk5K28cpIOTYm71O/fYk7rFaygb3A==",
"version": "7.54.2",
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.54.2.tgz",
"integrity": "sha512-eHpAUgUjWbZocoQYUHposymRb4ZP6d0uwUnooL2uOybA9/3tPUvoAKqEWK1WaSiTxxOfTpffNZP7QwlnM3/gEg==",
"requires": {}
},
"react-icons": {
@ -11691,9 +11905,9 @@
}
},
"tailwind-merge": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.4.0.tgz",
"integrity": "sha512-49AwoOQNKdqKPd9CViyH5wJoSKsCDjUlzL8DxuGp3P1FsGY36NJDAa18jLZcaHAUUuTj+JB8IAo8zWgBNvBF7A=="
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.0.tgz",
"integrity": "sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA=="
},
"tailwindcss": {
"version": "3.3.1",
@ -12141,6 +12355,11 @@
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
"dev": true
},
"zod": {
"version": "3.24.1",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz",
"integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A=="
},
"zustand": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.1.tgz",

View File

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

View File

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

View File

@ -1,7 +1,8 @@
export default {
newsItem: (id: string) => `/posts/${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}`,
videos: (search: string) => `/materials${search}`,
video: (video_id: number) => `/material/${video_id}`,
@ -15,35 +16,35 @@ export default {
// ===================================================================
// Votes ================================================================
allVotes: '/voting/show_on_site',
allVotes: "/voting/show_on_site",
vote: (vote_id: string) => `/voting/${vote_id}`,
// ======================================================================
// Lottery ================================================================
lotteryActive: '/lottery/active',
lotteryId: (lottery_id: string) => `/lottery/${lottery_id}`,
lotteryActive: "/lottery/active",
tossId: (type: string, id: string) => `/${type}/${id}`,
// ======================================================================
addPost: '/mahabat/order',
news: '/pagination/new/posts',
lastVideos: '/materials?per_page=30',
categories: '/categories',
channels: '/channels',
banner: '/mahabatlar',
addPost: "/mahabat/order",
news: "/pagination/new/posts",
lastVideos: "/materials?per_page=30",
categories: "/categories",
channels: "/channels",
banner: "/mahabatlar",
// home: '/mahabatlar?type=home',
home: '/slider?type=big',
marquee: '/timetable',
homeSmallSlider_1: '/slider?type=small',
homeSmallSlider_2: '/slider?type=small2',
homeSmallSlider_3: '/slider?type=hazyna',
homeSmallSlider_4: '/slider?type=mahabat',
home: "/slider?type=big",
marquee: "/timetable",
homeSmallSlider_1: "/slider?type=small",
homeSmallSlider_2: "/slider?type=small2",
homeSmallSlider_3: "/slider?type=hazyna",
homeSmallSlider_4: "/slider?type=mahabat",
properties: '/mahabat/property-types',
properties: "/mahabat/property-types",
addViews: (video_id: string) => `material/${video_id}/views/increment`,
// Sms ========================================================================
myTvAdmins: '/my-tv-admins',
myTvAdmins: "/my-tv-admins",
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)' },
to: { height: '0' },
},
'dots-flash': {
from: { opacity: '0' },
to: { opacity: '1' },
@ -76,7 +75,6 @@ const config = {
animation: {
'accordion-down': 'accordion-down 0.2s ease-out',
'accordion-up': 'accordion-up 0.2s ease-out',
'dots-flash': 'dots-flash 1s infinite;',
},
},
},