diff --git a/app/(main)/lottery/auth/page.tsx b/app/(main)/lottery/auth/page.tsx index b3d2cd1..54cd361 100644 --- a/app/(main)/lottery/auth/page.tsx +++ b/app/(main)/lottery/auth/page.tsx @@ -1,24 +1,8 @@ -"use client"; +'use client'; -import LotteryAuthForm from "@/components/lottery/auth/LotteryAuthForm"; -import { useEffect } from "react"; -import { useRouter } from "next/navigation"; -import { useLotteryAuth } from "@/store/useLotteryAuth"; +import LotteryAuthForm from '@/components/lottery/auth/LotteryAuthForm'; const LotteryAuthPage = () => { - const router = useRouter(); - const { isAuthenticated, logout } = useLotteryAuth(); - - useEffect(() => { - if (isAuthenticated) { - router.push("/lottery"); - } - }, [isAuthenticated, router]); - - useEffect(() => { - logout(); - }, [logout]); - return (
diff --git a/app/(main)/lottery/page.tsx b/app/(main)/lottery/page.tsx index 5a3d6a3..d211694 100644 --- a/app/(main)/lottery/page.tsx +++ b/app/(main)/lottery/page.tsx @@ -9,6 +9,7 @@ import LotteryWinnersSection from '@/components/lottery/LotteryWinnersSection'; import LotteryRulesSection from '@/components/lottery/rules/LotteryRulesSection'; import LotteryCountDown from '@/components/lottery/countDown/LotteryCountDown'; import LotteryCountDownAllert from '@/components/lottery/countDown/countDownAllert/LotteryCountDownAllert'; +import { LotteryWinnerDataSimplified } from '@/typings/lottery/lottery.types'; const LotteryPage = () => { const { lotteryData } = useLotteryAuth(); @@ -42,15 +43,7 @@ const LotteryPage = () => { {lotteryData && (status === 'ended' || status === 'started') && ( -
- - -
+ )}
diff --git a/components/common/AnimatedText.tsx b/components/common/AnimatedText.tsx new file mode 100644 index 0000000..3967d4a --- /dev/null +++ b/components/common/AnimatedText.tsx @@ -0,0 +1,49 @@ +import { motion } from 'framer-motion'; + +interface AnimatedTextProps { + text: string; + className?: string; + wordClassName?: string; + initialY?: number; + duration?: number; + wordDelay?: number; +} + +const AnimatedText = ({ + text, + className = '', + wordClassName = '', + initialY = -100, + duration = 0.5, + wordDelay = 0.2, +}: AnimatedTextProps) => { + const words = text.split(' '); + + return ( +
+ + {words.map((word, i) => ( + + {word} + + ))} + +
+ ); +}; + +export default AnimatedText; diff --git a/components/lottery/LotteryHeader.tsx b/components/lottery/LotteryHeader.tsx index 5b1807d..5dc9eac 100644 --- a/components/lottery/LotteryHeader.tsx +++ b/components/lottery/LotteryHeader.tsx @@ -16,25 +16,6 @@ const LotteryHeader = ({ title, description, image, smsCode }: LotteryHeaderProp {title}

{description}

-
- - - - - - SMS-kod: {smsCode} - -
{image && (
diff --git a/components/lottery/LotteryWinnersSection.tsx b/components/lottery/LotteryWinnersSection.tsx index 3fdfd67..17d6e35 100644 --- a/components/lottery/LotteryWinnersSection.tsx +++ b/components/lottery/LotteryWinnersSection.tsx @@ -1,42 +1,40 @@ -"use client"; +'use client'; -import { useState, useEffect, useRef } from "react"; -import { useLotteryAuth } from "@/store/useLotteryAuth"; -import { LotteryWinnerDataSimplified } from "@/typings/lottery/lottery.types"; -import LotteryWinnersList from "./winners/LotteryWinnersList"; -import LotterySlotCounter from "./slotCounter/LotterySlotCounter"; -import ReactConfetti from "react-confetti"; -import { useWindowSize } from "react-use"; -import LotteryCountDownAllert from "./countDown/countDownAllert/LotteryCountDownAllert"; +import { useState, useEffect, useRef } from 'react'; +import { useLotteryAuth } from '@/store/useLotteryAuth'; +import { LotteryWinnerDataSimplified } from '@/typings/lottery/lottery.types'; +import LotteryWinnersList from './winners/LotteryWinnersList'; +import LotterySlotCounter from './slotCounter/LotterySlotCounter'; +import ReactConfetti from 'react-confetti'; +import { useWindowSize } from 'react-use'; +import LotteryCountDownAllert from './countDown/countDownAllert/LotteryCountDownAllert'; +import { motion } from 'framer-motion'; +import AnimatedText from '@/components/common/AnimatedText'; -const WEBSOCKET_URL = "wss://sms.turkmentv.gov.tm/ws/lottery?dst=0506"; +const WEBSOCKET_URL = 'wss://sms.turkmentv.gov.tm/ws/lottery?dst=0506'; const PING_INTERVAL = 25000; const SLOT_COUNTER_DURATION = 20000; -const LotteryWinnersSection = ({ - lotteryStatus, -}: { - lotteryStatus: string; -}) => { +const LotteryWinnersSection = ({ lotteryStatus }: { lotteryStatus: string }) => { // UI States const [winners, setWinners] = useState([]); - const [currentNumber, setCurrentNumber] = useState("00-00-00-00-00"); + const [currentNumber, setCurrentNumber] = useState('00-00-00-00-00'); const [isConfettiActive, setIsConfettiActive] = useState(false); - const [wsStatus, setWsStatus] = useState< - "connecting" | "connected" | "error" - >("connecting"); + const [wsStatus, setWsStatus] = useState<'connecting' | 'connected' | 'error'>('connecting'); const { width, height } = useWindowSize(); const { lotteryData } = useLotteryAuth(); const [isSlotCounterAnimating, setIsSlotCounterAnimating] = useState(false); - const [pendingWinner, setPendingWinner] = - useState(null); + const [pendingWinner, setPendingWinner] = useState(null); // Refs const wsRef = useRef(null); const pingIntervalRef = useRef(); const mountedRef = useRef(false); + // Add new state for display text + const [displayText, setDisplayText] = useState('...'); + // Initialize winners from lottery data useEffect(() => { if (lotteryData?.data.winners) { @@ -46,9 +44,7 @@ const LotteryWinnersSection = ({ ticket: winner.ticket, })); setWinners(simplifiedWinners); - setCurrentNumber( - lotteryData.data.winners.at(-1)?.ticket || "00-00-00-00-00" - ); + setCurrentNumber(lotteryData.data.winners.at(-1)?.ticket || '00-00-00-00-00'); } }, [lotteryData]); @@ -61,21 +57,21 @@ const LotteryWinnersSection = ({ const socket = new WebSocket(WEBSOCKET_URL); wsRef.current = socket; - socket.addEventListener("open", () => { + socket.addEventListener('open', () => { if (!mountedRef.current) return; - console.log("WebSocket Connected"); - setWsStatus("connected"); + console.log('WebSocket Connected'); + setWsStatus('connected'); pingIntervalRef.current = setInterval(() => { if (socket.readyState === WebSocket.OPEN) { - socket.send(JSON.stringify({ type: "ping" })); + socket.send(JSON.stringify({ type: 'ping' })); } }, PING_INTERVAL); }); - socket.addEventListener("message", async (event) => { + socket.addEventListener('message', async (event) => { if (!mountedRef.current) return; - console.log("Message received:", event.data); + console.log('Message received:', event.data); try { const newWinner = JSON.parse(event.data); @@ -85,67 +81,58 @@ const LotteryWinnersSection = ({ ticket: newWinner.ticket, }; + // Set initial animation text + setDisplayText(`${winnerData.winner_no}-nji ýeňiji saýlanýar`); + // Start the sequence setIsSlotCounterAnimating(true); setPendingWinner(winnerData); setCurrentNumber(winnerData.ticket); // Wait for slot counter animation - await new Promise((resolve) => - setTimeout(resolve, SLOT_COUNTER_DURATION) - ); + await new Promise((resolve) => setTimeout(resolve, SLOT_COUNTER_DURATION)); + + // Update text to show winner's phone + setDisplayText(winnerData.client); setIsConfettiActive(true); setWinners((prev) => [...prev, winnerData]); - // Hide confetti after 5 seconds - // setTimeout(() => { - // if (mountedRef.current) { - // setIsConfettiActive(false); - // setIsSlotCounterAnimating(false); - // setPendingWinner(null); - // } - // }, 5000); - - // Show confetti and add winner simultaneously - if (mountedRef.current) { - setIsConfettiActive(true); - setWinners((prev) => [...prev, winnerData]); - - // Hide confetti after 5 seconds - setTimeout(() => { - if (mountedRef.current) { - setIsConfettiActive(false); - setIsSlotCounterAnimating(false); - setPendingWinner(null); - } - }, 5000); - } + // Reset everything after 5 seconds + setTimeout(() => { + if (mountedRef.current) { + setIsConfettiActive(false); + setIsSlotCounterAnimating(false); + setPendingWinner(null); + setDisplayText('...'); // Reset text + } + }, 5000); } catch (error) { - console.error("Error processing message:", error); + console.error('Error processing message:', error); setIsSlotCounterAnimating(false); setPendingWinner(null); + setDisplayText('...'); // Reset text on error } }); - socket.addEventListener("error", (error) => { + socket.addEventListener('error', (error) => { if (!mountedRef.current) return; - console.error("WebSocket Error:", error); - setWsStatus("error"); + console.error('WebSocket Error:', error); + setWsStatus('error'); }); - socket.addEventListener("close", () => { + socket.addEventListener('close', () => { if (!mountedRef.current) return; - console.log("WebSocket Closed"); - setWsStatus("error"); + console.log('WebSocket Closed'); + setWsStatus('error'); if (pingIntervalRef.current) { clearInterval(pingIntervalRef.current); } }); } catch (error) { - console.error("Error creating WebSocket:", error); - setWsStatus("error"); + console.error('Error creating WebSocket:', error); + setWsStatus('error'); } }; @@ -165,7 +152,7 @@ const LotteryWinnersSection = ({ return (
- {wsStatus === "error" && ( + {wsStatus === 'error' && (
Connection error. Please refresh the page.
@@ -181,26 +168,29 @@ const LotteryWinnersSection = ({ 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', ]} />
)}
-
+
+
- +
-
+
diff --git a/components/lottery/auth/ProtectedRoute.tsx b/components/lottery/auth/ProtectedRoute.tsx index 889f8b8..76cdb05 100644 --- a/components/lottery/auth/ProtectedRoute.tsx +++ b/components/lottery/auth/ProtectedRoute.tsx @@ -1,24 +1,41 @@ 'use client'; -import { useEffect } from 'react'; +import { useEffect, useState } from 'react'; import { useRouter } from 'next/navigation'; import { useLotteryAuth } from '@/store/useLotteryAuth'; +import { Queries } from '@/api/queries'; -interface ProtectedRouteProps { - children: React.ReactNode; -} - -const ProtectedRoute = ({ children }: ProtectedRouteProps) => { +const ProtectedRoute = ({ children }: { children: React.ReactNode }) => { const router = useRouter(); - const { isAuthenticated } = useLotteryAuth(); + const { isAuthenticated, phone, code, setAuth } = useLotteryAuth(); + const [isLoading, setIsLoading] = useState(true); useEffect(() => { - if (!isAuthenticated) { - router.replace('/lottery/auth'); - } - }, [isAuthenticated, router]); + const checkAuth = async () => { + // First, check if we have credentials in localStorage + if (phone && code) { + try { + // Try to authenticate with stored credentials + const response = await Queries.authenticateLottery(phone, code); + setAuth(response, phone, code); + setIsLoading(false); + return; // Exit early if authentication successful + } catch (err) { + console.error('Authentication failed:', err); + // Only redirect if API request fails + router.replace('/lottery/auth'); + } + } else { + // Only redirect if no credentials found + router.replace('/lottery/auth'); + } + }; - if (!isAuthenticated) { + checkAuth(); + }, []); + + // Show nothing while checking auth + if (isLoading) { return null; } diff --git a/components/lottery/slotCounter/LotterySlotCounter.tsx b/components/lottery/slotCounter/LotterySlotCounter.tsx index 5f34d1c..e598790 100644 --- a/components/lottery/slotCounter/LotterySlotCounter.tsx +++ b/components/lottery/slotCounter/LotterySlotCounter.tsx @@ -1,27 +1,24 @@ -"use client"; -import Image from "next/image"; -import React, { useEffect, useState } from "react"; -import SlotCounter from "react-slot-counter"; -import { useMediaQuery } from "usehooks-ts"; +'use client'; +import Image from 'next/image'; +import React, { useEffect, useState } from 'react'; +import SlotCounter from 'react-slot-counter'; +import { useMediaQuery } from 'usehooks-ts'; interface LotterySlotCounterProps { numberString: string; isAnimating: boolean; } -const LotterySlotCounter = ({ - numberString, - isAnimating, -}: LotterySlotCounterProps) => { +const LotterySlotCounter = ({ numberString, isAnimating }: LotterySlotCounterProps) => { const [formattedNumber, setFormattedNumber] = useState(numberString); useEffect(() => { - const formatted = numberString.replace(/-/g, ","); + const formatted = numberString.replace(/-/g, ','); setFormattedNumber(formatted); }, [numberString]); - const tablet = useMediaQuery("(max-width: 769px)"); - const mobile = useMediaQuery("(max-width: 426px)"); + const tablet = useMediaQuery('(max-width: 769px)'); + const mobile = useMediaQuery('(max-width: 426px)'); return (
@@ -65,31 +62,29 @@ const LotterySlotCounter = ({ className="flex items-center h-fit md:max-w-[1132px] sm:max-w-[640px] max-w-[324px] w-full justify-center text-white md: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 */}
+ 'linear-gradient(180deg, rgba(87, 89, 146, 0) 0%, #7274AB 50%, rgba(87, 89, 146, 0) 100%)', + }}>
diff --git a/components/lottery/winners/LotteryWinnersList.tsx b/components/lottery/winners/LotteryWinnersList.tsx index 879fdfa..1f57c43 100644 --- a/components/lottery/winners/LotteryWinnersList.tsx +++ b/components/lottery/winners/LotteryWinnersList.tsx @@ -1,16 +1,9 @@ -import { - LotteryWinnerData, - LotteryWinnerDataSimplified, -} from "@/typings/lottery/lottery.types"; -import LotteryWinner from "./LotteryWinner"; -import { motion, AnimatePresence } from "framer-motion"; -import { v4 } from "uuid"; +import { LotteryWinnerData, LotteryWinnerDataSimplified } from '@/typings/lottery/lottery.types'; +import LotteryWinner from './LotteryWinner'; +import { motion, AnimatePresence } from 'framer-motion'; +import { v4 } from 'uuid'; -const LotteryWinnersList = ({ - winners, -}: { - winners: LotteryWinnerDataSimplified[]; -}) => { +const LotteryWinnersList = ({ winners }: { winners: LotteryWinnerDataSimplified[] }) => { return (
@@ -21,9 +14,8 @@ const LotteryWinnersList = ({
- + className="grid md:grid-cols-3 sm:grid-cols-2 grid-cols-1 gap-x-2 gap-y-4 w-full "> + {winners.map((item, index) => ( void; } -export const useLotteryAuth = create((set) => ({ - isAuthenticated: false, - lotteryData: null, - phone: null, - code: null, - setAuth: (data, phone, code) => set({ - isAuthenticated: true, - lotteryData: data, - phone, - code - }), - logout: () => set({ - isAuthenticated: false, - lotteryData: null, - phone: null, - code: null - }), -})); +export const useLotteryAuth = create()( + persist( + (set) => ({ + isAuthenticated: false, + lotteryData: null, + phone: null, + code: null, + setAuth: (data, phone, code) => + set({ + isAuthenticated: true, + lotteryData: data, + phone, + code, + }), + logout: () => + set({ + isAuthenticated: false, + lotteryData: null, + phone: null, + code: null, + }), + }), + { + name: 'lottery-auth-storage', + }, + ), +);