diff --git a/app/(main)/lottery/auth/page.tsx b/app/(main)/lottery/auth/page.tsx index 37a2bf6..9731857 100644 --- a/app/(main)/lottery/auth/page.tsx +++ b/app/(main)/lottery/auth/page.tsx @@ -22,8 +22,10 @@ const LotteryAuthPage = () => { }, [logout]); return ( -
- +
+
+ +
); }; diff --git a/app/(main)/lottery/page.tsx b/app/(main)/lottery/page.tsx index f0e3495..fd598a0 100644 --- a/app/(main)/lottery/page.tsx +++ b/app/(main)/lottery/page.tsx @@ -1,56 +1,61 @@ -'use client'; +"use client"; -import { useState } from 'react'; -import { useLotteryAuth } from '@/store/useLotteryAuth'; -import ProtectedRoute from '@/components/lottery/auth/ProtectedRoute'; -import LotteryHeader from '@/components/lottery/LotteryHeader'; +import { 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 LotteryCountDownAllert from '@/components/lottery/countDown/countDownAllert/LotteryCountDownAllert'; +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"; const LotteryPage = () => { const { lotteryData } = useLotteryAuth(); - const [status, setStatus] = useState<'not-started' | 'started' | 'ended'>('not-started'); + const [status, setStatus] = useState<"not-started" | "started" | "ended">( + "not-started" + ); + + console.log(status); return ( -
+
{lotteryData && ( - - )} +
+ - {lotteryData ? ( - status === 'not-started' ? ( -
- -
- ) : ( -
- -
- ) - ) : null} + {status === "not-started" ? ( +
+ +
+ ) : null} +
+ )} - {(status === 'ended' || status === 'started') && } + {lotteryData && (status === "ended" || status === "started") && ( +
+ + +
+ )}
); diff --git a/app/globals.css b/app/globals.css index 684f5a8..00190b0 100644 --- a/app/globals.css +++ b/app/globals.css @@ -93,7 +93,8 @@ html { } .text-stroke { - text-shadow: -1px -1px 0 white, 1px -1px 0 white, -1px 1px 0 white, 1px 1px 0 white; + text-shadow: -1px -1px 0 white, 1px -1px 0 white, -1px 1px 0 white, + 1px 1px 0 white; } .big-swiper .swiper-pagination-bullet { @@ -121,20 +122,20 @@ html { .small-swiper .swiper-button-next:after { color: white; - content: url('/arrow-right-small.svg'); + content: url("/arrow-right-small.svg"); } .small-swiper .swiper-button-prev:after { color: white; - content: url('/arrow-left-small.svg'); + content: url("/arrow-left-small.svg"); } .big-swiper .swiper-button-next:after { color: white; - content: url('/arrow-right-big.svg'); + content: url("/arrow-right-big.svg"); } .big-swiper .swiper-button-prev:after { color: white; - content: url('/arrow-left-big.svg'); + content: url("/arrow-left-big.svg"); } video { @@ -187,15 +188,15 @@ big { @apply bg-[#E6E6FA] rounded-xl py-3 px-4 placeholder:text-[#BCBCD6] outline-none; } - .calendar [aria-label='Go to next month'] { + .calendar [aria-label="Go to next month"] { @apply shadow-sm transition-all; } - .calendar [aria-label='Go to previous month'] { + .calendar [aria-label="Go to previous month"] { @apply shadow-sm transition-all; } - .day-styles [name='day'] { + .day-styles [name="day"] { @apply p-4 text-textDarkt leading-[140%] bg-purple-600 rounded-full; } @@ -261,20 +262,12 @@ big { } .wheel-circle { - background: url('/wheel-circle.svg'); + background: url("/wheel-circle.svg"); background-position: center; background-size: cover; background-repeat: no-repeat; } -.rolling-number { - font-size: 80px; - letter-spacing: -1%; - padding: 0px 16px; - - @apply text-lightOnPrimary; -} - .index-module_slot__DpPgW { overflow: visible !important; /* height: auto !important; */ @@ -303,11 +296,53 @@ big { } */ .slot-seperator { - content: url('/dash.svg'); + content: url("/dash.svg"); background-position: center; background-size: cover; background-repeat: no-repeat; - margin: 0 20px; + @apply md:mx-5; +} + +.rolling-number { + @apply text-lightOnPrimary md:text-[80px] md:leading-[88px] -tracking-[1%] md:px-4; +} + +@media (max-width: 1025px) { + .rolling-number { + @apply text-[48px] px-2; + } +} + +@media (max-width: 1024px) { + .slot-seperator { + content: url("/dash-md.svg"); + @apply mx-2; + } +} + +@media (max-width: 1025px) { + .rolling-number { + @apply text-[48px] px-2; + } +} + +@media (max-width: 426px) { + .rolling-number { + @apply text-[24px] px-1; + } +} + +@media (max-width: 320px) { + .rolling-number { + @apply text-[16px] px-1; + } +} + +@media (max-width: 426px) { + .slot-seperator { + content: url("/dash-sm.svg"); + @apply mx-2; + } } .confetti-piece { @@ -324,7 +359,8 @@ big { transform: translateX(0) translateY(0) rotate(0deg); } 50% { - transform: translateX(calc(var(--end-x) / 2)) translateY(-30vh) rotate(180deg); + transform: translateX(calc(var(--end-x) / 2)) translateY(-30vh) + rotate(180deg); } 100% { opacity: 0; diff --git a/components/lottery/LotteryWinnersSection.tsx b/components/lottery/LotteryWinnersSection.tsx index 2488b88..91e4439 100644 --- a/components/lottery/LotteryWinnersSection.tsx +++ b/components/lottery/LotteryWinnersSection.tsx @@ -1,34 +1,43 @@ -'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 { 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"; -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 = () => { +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); - console.log(isConfettiActive, 'isConfettiActive'); + console.log(isConfettiActive, "isConfettiActive"); // Initialize winners from lottery data useEffect(() => { @@ -39,7 +48,9 @@ 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]); @@ -52,21 +63,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); @@ -82,7 +93,9 @@ const LotteryWinnersSection = () => { 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) + ); setIsConfettiActive(true); setWinners((prev) => [...prev, winnerData]); @@ -111,30 +124,30 @@ const LotteryWinnersSection = () => { }, 5000); } } catch (error) { - console.error('Error processing message:', error); + console.error("Error processing message:", error); setIsSlotCounterAnimating(false); setPendingWinner(null); } }); - 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"); } }; @@ -154,7 +167,7 @@ const LotteryWinnersSection = () => { return (
- {wsStatus === 'error' && ( + {wsStatus === "error" && (
Connection error. Please refresh the page.
@@ -170,12 +183,12 @@ 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", ]} />
@@ -183,10 +196,13 @@ const LotteryWinnersSection = () => {
-
- +
+
-
+
diff --git a/components/lottery/auth/LotteryAuthForm.tsx b/components/lottery/auth/LotteryAuthForm.tsx index 9f69a5f..3ea84ed 100644 --- a/components/lottery/auth/LotteryAuthForm.tsx +++ b/components/lottery/auth/LotteryAuthForm.tsx @@ -1,13 +1,13 @@ -'use client'; +"use client"; -import { Queries } from '@/api/queries'; -import { useState, FormEvent } from 'react'; -import { useRouter } from 'next/navigation'; -import { useLotteryAuth } from '@/store/useLotteryAuth'; +import { Queries } from "@/api/queries"; +import { useState, FormEvent } from "react"; +import { useRouter } from "next/navigation"; +import { useLotteryAuth } from "@/store/useLotteryAuth"; const LotteryAuthForm = () => { - const [phone, setPhone] = useState(''); - const [code, setCode] = useState(''); + const [phone, setPhone] = useState(""); + const [code, setCode] = useState(""); const [error, setError] = useState(null); const [isLoading, setIsLoading] = useState(false); const router = useRouter(); @@ -28,12 +28,12 @@ const LotteryAuthForm = () => { setError(null); if (!validatePhone(phone)) { - setError('Telefon belgisi nädogry formatda'); + setError("Telefon belgisi nädogry formatda"); return; } if (!validateCode(code)) { - setError('Kod nädogry formatda'); + setError("Kod nädogry formatda"); return; } @@ -42,17 +42,17 @@ const LotteryAuthForm = () => { try { const response = await Queries.authenticateLottery(phone, code); setAuth(response, phone, code); - router.replace('/lottery'); + router.replace("/lottery"); } catch (err) { - console.error('Authentication error:', err); - setError('Telefon belgisi ýa-da kod nädogry'); + console.error("Authentication error:", err); + setError("Telefon belgisi ýa-da kod nädogry"); } finally { setIsLoading(false); } }; const handlePhoneChange = (e: React.ChangeEvent) => { - const value = e.target.value.replace(/\D/g, ''); // Remove non-digits + const value = e.target.value.replace(/\D/g, ""); // Remove non-digits if (value.length <= 11) { // Limit to 11 digits (99363 + 6 digits) setPhone(value); @@ -70,8 +70,11 @@ const LotteryAuthForm = () => { return (
-

Lotereýa giriş

+ className="bg-lightSurfaceContainer rounded-[24px] md:p-[40px] sm:p-[24px] p-[16px] w-[530px] flex flex-col gap-[24px]" + > +

+ Lotereýa giriş +

{ value={code} onChange={handleCodeChange} className="px-[16px] py-[12px] bg-lightPrimaryContainer rounded-[12px] outline-none text-lightOnSurfaceVariant text-textSmall leading-textSmall" - placeholder="5-0102030408" + placeholder="5-0105639808" required />
- {error &&

{error}

} + {error && ( +

+ {error} +

+ )}
); diff --git a/components/lottery/countDown/LotteryCountDown.tsx b/components/lottery/countDown/LotteryCountDown.tsx index e8189bd..1a4667c 100644 --- a/components/lottery/countDown/LotteryCountDown.tsx +++ b/components/lottery/countDown/LotteryCountDown.tsx @@ -24,6 +24,8 @@ const LotteryCountDown: React.FC = ({ seconds: 0, }); + console.log(lotteryStatus); + useEffect(() => { const timer = setInterval(() => { if (lotteryStatus === "not-started") { @@ -57,8 +59,8 @@ const LotteryCountDown: React.FC = ({ console.log(lotteryStatus); return ( -
-

+
+

{lotteryStatus === "started" ? "Bije dowam edýär" : lotteryStatus === "ended" @@ -68,7 +70,7 @@ const LotteryCountDown: React.FC = ({ {/* LotteryCountDown */} {lotteryStatus === "not-started" && (
-
+

{timeLeft.hours}

@@ -77,12 +79,13 @@ const LotteryCountDown: React.FC = ({

-
-
-
+ {/* Dots */} +
+
+
-
+

{timeLeft.minutes}

@@ -91,12 +94,13 @@ const LotteryCountDown: React.FC = ({

-
-
-
+ {/* Dots */} +
+
+
-
+

{timeLeft.seconds}

diff --git a/components/lottery/rules/LotteryRulesSection.tsx b/components/lottery/rules/LotteryRulesSection.tsx index 7bde805..d9c699c 100644 --- a/components/lottery/rules/LotteryRulesSection.tsx +++ b/components/lottery/rules/LotteryRulesSection.tsx @@ -1,4 +1,4 @@ -import { useLotteryAuth } from '@/store/useLotteryAuth'; +import { useLotteryAuth } from "@/store/useLotteryAuth"; const LotteryRulesSection = () => { const { lotteryData } = useLotteryAuth(); @@ -7,17 +7,18 @@ const LotteryRulesSection = () => {
-

+

Lotereýanyň duzgunleri:

-
-
-

+ +
+
+

Umumy düzgünler:

    {Array(5) - .fill(' ') + .fill(" ") .map((item, i) => (
  • Ilkinji we dogry jogap beren sanawda ilkinji ýeri eýelýär @@ -26,8 +27,10 @@ const LotteryRulesSection = () => {
-
-

Üns beriň:

+
+

+ Üns beriň: +

    {lotteryData?.user_lottery_numbers.map((item, i) => (
  • diff --git a/components/lottery/slotCounter/LotterySlotCounter.tsx b/components/lottery/slotCounter/LotterySlotCounter.tsx index 5d546be..0b57ef6 100644 --- a/components/lottery/slotCounter/LotterySlotCounter.tsx +++ b/components/lottery/slotCounter/LotterySlotCounter.tsx @@ -1,51 +1,82 @@ -'use client'; -import Image from 'next/image'; -import React, { useEffect, useState } from 'react'; -import SlotCounter from 'react-slot-counter'; +"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)"); + return (
    - roller-triangle - roller-triangle + {mobile ? ( + roller-triangle + ) : ( + roller-triangle + )} + + {mobile ? ( + roller-triangle + ) : ( + roller-triangle + )} +
    + "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%)", + }} + >
    { animate={{ opacity: 1, translateY: 0 }} exit={{ opacity: 0, translateY: -20 }} transition={{ duration: 0.5 }} - className="flex flex-col gap-2 md:pb-4 pb-3 border-b w-full border-[#CECCFF]"> + className="flex flex-col gap-2 md:pb-4 pb-3 border-b w-full border-[#CECCFF]" + >

    The winner of the {winnerNumber} stage:

    -

    {phone}

    +

    {phone}

    -

    {ticket}

    +

    {ticket}

    ); diff --git a/components/lottery/winners/LotteryWinnersList.tsx b/components/lottery/winners/LotteryWinnersList.tsx index addd713..879fdfa 100644 --- a/components/lottery/winners/LotteryWinnersList.tsx +++ b/components/lottery/winners/LotteryWinnersList.tsx @@ -1,18 +1,28 @@ -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 (
    -
    -

    Results

    -

    The results after each stage will be shown here.

    +
    +

    Results

    +

    + The results after each stage will be shown here. +

    + className="grid md:grid-cols-3 sm:grid-cols-2 grid-cols-1 gap-x-2 gap-y-4 w-full h-[244px] overflow-y-auto lottery-scrollbar" + > {winners.map((item, index) => ( + + diff --git a/public/dash-sm.svg b/public/dash-sm.svg new file mode 100644 index 0000000..8d9b26e --- /dev/null +++ b/public/dash-sm.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/roller-triangle-sm.jpg b/public/roller-triangle-sm.jpg new file mode 100644 index 0000000..a9a2989 --- /dev/null +++ b/public/roller-triangle-sm.jpg @@ -0,0 +1,3 @@ + + + diff --git a/public/roller-triangle-sm.svg b/public/roller-triangle-sm.svg new file mode 100644 index 0000000..a9a2989 --- /dev/null +++ b/public/roller-triangle-sm.svg @@ -0,0 +1,3 @@ + + +