From fb6ad9761f279d9488ff7c6602831431b7ca5ed3 Mon Sep 17 00:00:00 2001 From: Kakabay <2kakabayashyrberdyew@gmail.com> Date: Sun, 5 Jan 2025 23:32:06 +0500 Subject: [PATCH] websocket hook created --- components/lottery/LotteryWinnersSection.tsx | 240 ++++++++---------- .../slotCounter/LotterySlotCounter.tsx | 35 +-- hooks/useWebSocketLottery.ts | 45 ++++ 3 files changed, 167 insertions(+), 153 deletions(-) create mode 100644 hooks/useWebSocketLottery.ts diff --git a/components/lottery/LotteryWinnersSection.tsx b/components/lottery/LotteryWinnersSection.tsx index f35aadc..54d76f3 100644 --- a/components/lottery/LotteryWinnersSection.tsx +++ b/components/lottery/LotteryWinnersSection.tsx @@ -1,46 +1,41 @@ -'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 AnimatedText from "@/components/common/AnimatedText"; +import { useWebsocketLottery } from "@/hooks/useWebSocketLottery"; -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 { AnimatePresence, motion } from 'framer-motion'; -import AnimatedText from '@/components/common/AnimatedText'; - -const WEBSOCKET_URL = 'wss://sms.turkmentv.gov.tm/ws/lottery?dst=0506'; -const PING_INTERVAL = 25000; +const WEBSOCKET_URL = "wss://sms.turkmentv.gov.tm/ws/lottery?dst=0506"; const SLOT_COUNTER_DURATION = 20000; const RECONNECT_INTERVAL = 5000; -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(); const [isConfettiActive, setIsConfettiActive] = useState(false); - const [wsStatus, setWsStatus] = useState<'connecting' | 'connected' | 'error'>('connecting'); const { width, height } = useWindowSize(); const { lotteryData } = useLotteryAuth(); - const [isSlotCounterAnimating, setIsSlotCounterAnimating] = useState(false); const [winnerSelectingStatus, setWinnerSelectingStatus] = useState< - 'not-selected' | 'is-selecting' | 'selected' - >('not-selected'); - const [pendingWinner, setPendingWinner] = useState(null); - // Add new state for display text - const [displayText, setDisplayText] = useState('...'); - const [winnerText, setWinnerText] = useState(''); + "not-selected" | "is-selecting" | "selected" + >("not-selected"); + const [pendingWinner, setPendingWinner] = + useState(null); + const [displayText, setDisplayText] = useState("..."); + const [winnerText, setWinnerText] = useState(""); + // WebSocket Hook + const { wsStatus } = useWebsocketLottery(WEBSOCKET_URL); const wsRef = useRef(null); - const pingIntervalRef = useRef(null); - const reconnectTimeoutRef = useRef(null); - const mountedRef = useRef(false); - let reconnectAttempts: number; - // Initialize winners from lottery data useEffect(() => { if (lotteryData?.data.winners) { const simplifiedWinners = lotteryData.data.winners.map((winner) => ({ @@ -49,94 +44,31 @@ const LotteryWinnersSection = ({ lotteryStatus }: { lotteryStatus: string }) => 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]); useEffect(() => { - mountedRef.current = true; + const handleWebSocketMessage = async (event: MessageEvent) => { + if (!event?.data) return; - const setupWebSocket = () => { - if (wsRef.current) return; + console.log("πŸ“© Message received:", event.data); - try { - const socket = new WebSocket(WEBSOCKET_URL); - wsRef.current = socket; - - socket.addEventListener('open', () => { - console.log('βœ… WebSocket Connected'); - setWsStatus('connected'); - reconnectAttempts = 0; // Reset reconnect attempts - }); - - socket.addEventListener('message', handleWebSocketMessage); - - socket.addEventListener('error', (error) => { - console.error('❌ WebSocket Error:', error); - setWsStatus('error'); - }); - - socket.addEventListener('close', (event) => { - console.log('❌ WebSocket Closed:', event); - console.log(`Code: ${event.code}, Reason: ${event.reason}`); - - setWsStatus('error'); - - // Only reconnect if closure is abnormal - if (event.code !== 1000) { - reconnectWebSocket(); - } - }); - } catch (error) { - console.error('❌ Error creating WebSocket:', error); - setWsStatus('error'); - reconnectWebSocket(); - } - }; - - const reconnectWebSocket = () => { - if (!mountedRef.current) return; - - const delay = Math.min(1000 * 2 ** reconnectAttempts, 30000); // Exponential backoff - reconnectAttempts += 1; - - console.log(`Reconnecting in ${delay / 1000} seconds...`); - - reconnectTimeoutRef.current = setTimeout(() => { - setupWebSocket(); - }, delay); - }; - - setupWebSocket(); - - return () => { - mountedRef.current = false; - - if (pingIntervalRef.current) { - clearInterval(pingIntervalRef.current); - } - - if (reconnectTimeoutRef.current) { - clearTimeout(reconnectTimeoutRef.current); - } - - if (wsRef.current) { - wsRef.current.close(); - wsRef.current = null; - } - }; - }, []); - - // WebSocket Message Handler - const handleWebSocketMessage = async (event: MessageEvent) => { - if (!mountedRef.current) return; - - try { const newWinner = JSON.parse(event.data); - if (!newWinner || !newWinner.phone || !newWinner.winner_no || !newWinner.ticket) { - throw new Error('Invalid data format received'); + if ( + !newWinner || + !newWinner.phone || + !newWinner.winner_no || + !newWinner.ticket + ) { + console.error("❌ Invalid data format received"); + return; } + console.log("πŸŽ‰ New winner selected:", newWinner); + const winnerData = { client: newWinner.phone, winner_no: newWinner.winner_no, @@ -144,39 +76,72 @@ const LotteryWinnersSection = ({ lotteryStatus }: { lotteryStatus: string }) => }; setDisplayText(`${winnerData.winner_no}-nji Γ½eňiji saΓ½lanΓ½ar`); - setWinnerSelectingStatus('is-selecting'); + setWinnerSelectingStatus("is-selecting"); setPendingWinner(winnerData); setCurrentNumber(winnerData.ticket); - await new Promise((resolve) => setTimeout(resolve, SLOT_COUNTER_DURATION)); + await new Promise((resolve) => + setTimeout(resolve, SLOT_COUNTER_DURATION) + ); - setDisplayText('The winner is'); + setDisplayText("The winner is"); setWinnerText(winnerData.client); - setWinnerSelectingStatus('selected'); + setWinnerSelectingStatus("selected"); setIsConfettiActive(true); setWinners((prev) => [...prev, winnerData]); setTimeout(() => { - if (mountedRef.current) { - setIsConfettiActive(false); - setWinnerSelectingStatus('not-selected'); - setPendingWinner(null); - setDisplayText('...'); - setWinnerText(''); - } + setIsConfettiActive(false); + setWinnerSelectingStatus("not-selected"); + setPendingWinner(null); + setDisplayText("..."); + setWinnerText(""); }, 10000); - } catch (error) { - console.error('Error processing message:', error); - setDisplayText('Error processing winner'); + }; + + if (wsStatus === "connected" && !wsRef.current) { + console.log("βœ… WebSocket connected"); + wsRef.current = new WebSocket(WEBSOCKET_URL); + wsRef.current.addEventListener("message", handleWebSocketMessage); + + wsRef.current.addEventListener("error", (error) => { + console.error("❌ WebSocket error:", error); + }); + + wsRef.current.addEventListener("close", (event) => { + console.log("❌ WebSocket closed:", event); + }); + + // Set up ping to keep connection alive + const pingInterval = setInterval(() => { + if (wsRef.current?.readyState === WebSocket.OPEN) { + console.log("πŸ“€ Sending ping"); + wsRef.current.send(JSON.stringify({ type: "ping" })); + } + }, RECONNECT_INTERVAL); + + // Cleanup on unmount + return () => { + console.log("πŸ”Œ Cleaning up WebSocket connection"); + if (wsRef.current) { + wsRef.current.close(); + wsRef.current = null; + } + clearInterval(pingInterval); + }; + } else if (wsStatus === "connecting") { + console.log("πŸ”„ WebSocket connecting..."); + } else if (wsStatus === "error") { + console.log("❗ WebSocket error detected"); } - }; + }, [wsStatus]); return (
- {wsStatus === 'error' && ( + {wsStatus === "error" && (
- Connection error. Please refresh the page. + Websocket connection error.
)} {isConfettiActive && ( @@ -188,25 +153,19 @@ const LotteryWinnersSection = ({ lotteryStatus }: { lotteryStatus: string }) => numberOfPieces={1000} tweenDuration={10000} run={true} - colors={[ - 'linear-gradient(45deg, #5D5D72, #8589DE)', - 'linear-gradient(45deg, #E1E0FF, #575992)', - '#8589DE', - '#575992', - '#E1E0FF', - '#BA1A1A', - ]} /> )} -
+ style={{ + background: "linear-gradient(180deg, #F0ECF4 0%, #E1E0FF 43.5%)", + }} + >
- {winnerSelectingStatus === 'not-selected' || - winnerSelectingStatus === 'is-selecting' ? ( + {winnerSelectingStatus === "not-selected" || + winnerSelectingStatus === "is-selecting" ? ( )}
- + {currentNumber && ( + + )}
diff --git a/components/lottery/slotCounter/LotterySlotCounter.tsx b/components/lottery/slotCounter/LotterySlotCounter.tsx index 3c49955..8a0db83 100644 --- a/components/lottery/slotCounter/LotterySlotCounter.tsx +++ b/components/lottery/slotCounter/LotterySlotCounter.tsx @@ -1,24 +1,27 @@ -'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 (
@@ -62,21 +65,23 @@ const LotterySlotCounter = ({ numberString, isAnimating }: LotterySlotCounterPro 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%)", + }} + >
{ + const [wsStatus, setWsStatus] = useState< + "connecting" | "connected" | "error" + >("connecting"); + const wsRef = useRef(null); + const reconnectTimeoutRef = useRef(null); + + useEffect(() => { + const setupWebSocket = () => { + const socket = new WebSocket(url); + wsRef.current = socket; + + socket.addEventListener("open", () => setWsStatus("connected")); + socket.addEventListener("error", () => setWsStatus("error")); + socket.addEventListener("close", () => reconnectWebSocket()); + }; + + const reconnectWebSocket = () => { + reconnectTimeoutRef.current = setTimeout(() => { + setupWebSocket(); + }, reconnectInterval); + }; + + setupWebSocket(); + + return () => { + if (reconnectTimeoutRef.current) + clearTimeout(reconnectTimeoutRef.current); + if (wsRef.current) wsRef.current.close(); + }; + }, [url, reconnectInterval]); + + const sendMessage = (message: string) => { + if (wsRef.current?.readyState === WebSocket.OPEN) { + wsRef.current.send(message); + } + }; + + return { wsStatus, sendMessage }; +};