responsive design done

This commit is contained in:
Kakabay 2024-12-29 23:02:23 +05:00
parent 007902bb6d
commit bb9db81d69
14 changed files with 309 additions and 181 deletions

View File

@ -22,9 +22,11 @@ const LotteryAuthPage = () => {
}, [logout]); }, [logout]);
return ( return (
<div className="container">
<div className="flex justify-center items-center min-h-[50vh] py-[200px]"> <div className="flex justify-center items-center min-h-[50vh] py-[200px]">
<LotteryAuthForm /> <LotteryAuthForm />
</div> </div>
</div>
); );
}; };

View File

@ -1,56 +1,61 @@
'use client'; "use client";
import { useState } from 'react'; import { useState } from "react";
import { useLotteryAuth } from '@/store/useLotteryAuth'; import { useLotteryAuth } from "@/store/useLotteryAuth";
import ProtectedRoute from '@/components/lottery/auth/ProtectedRoute'; import ProtectedRoute from "@/components/lottery/auth/ProtectedRoute";
import LotteryHeader from '@/components/lottery/LotteryHeader'; import LotteryHeader from "@/components/lottery/LotteryHeader";
import LotteryWinnersSection from '@/components/lottery/LotteryWinnersSection'; import LotteryWinnersSection from "@/components/lottery/LotteryWinnersSection";
import LotteryRulesSection from '@/components/lottery/rules/LotteryRulesSection'; import LotteryRulesSection from "@/components/lottery/rules/LotteryRulesSection";
import LotteryCountDown from '@/components/lottery/countDown/LotteryCountDown'; import LotteryCountDown from "@/components/lottery/countDown/LotteryCountDown";
import LotteryCountDownAllert from '@/components/lottery/countDown/countDownAllert/LotteryCountDownAllert'; import LotteryCountDownAllert from "@/components/lottery/countDown/countDownAllert/LotteryCountDownAllert";
const LotteryPage = () => { const LotteryPage = () => {
const { lotteryData } = useLotteryAuth(); 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 ( return (
<ProtectedRoute> <ProtectedRoute>
<div className="flex flex-col md:gap-[128px] gap-[80px] font-roboto md:pt-[64px] sm:pt-[48px] pt-[40px] pb-[128px] text-lightOnSurface"> <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 && ( {lotteryData && (
<div className="flex flex-col sm:gap-[64px] gap-[40px]">
<LotteryHeader <LotteryHeader
title={lotteryData.data.title} title={lotteryData.data.title}
description={lotteryData.data.description} description={lotteryData.data.description}
image={lotteryData.data.image} image={lotteryData.data.image}
smsCode={lotteryData.data.sms_code} smsCode={lotteryData.data.sms_code}
/> />
)}
{lotteryData ? ( {status === "not-started" ? (
status === 'not-started' ? (
<div className="container"> <div className="container">
<LotteryCountDown <LotteryCountDown
lotteryStatus={status} lotteryStatus={status}
setLotteryStatus={setStatus} setLotteryStatus={setStatus}
endDate={lotteryData?.data.end_time} endDate={lotteryData.data.end_time}
startDate={lotteryData?.data.start_time} startDate={lotteryData.data.start_time}
/> />
</div> </div>
) : (
<div className="container">
<LotteryCountDownAllert
lotteryStatus={status}
setLotteryStatus={setStatus}
endDate={lotteryData?.data.end_time}
startDate={lotteryData?.data.start_time}
/>
</div>
)
) : null} ) : null}
</div>
)}
<LotteryRulesSection /> <LotteryRulesSection />
{(status === 'ended' || status === 'started') && <LotteryWinnersSection />} {lotteryData && (status === "ended" || status === "started") && (
<div className="flex flex-col sm:gap-[100px] gap-[40px]">
<LotteryCountDownAllert
lotteryStatus={status}
setLotteryStatus={setStatus}
endDate={lotteryData.data.end_time}
startDate={lotteryData.data.start_time}
/>
<LotteryWinnersSection lotteryStatus={status} />
</div>
)}
</div> </div>
</ProtectedRoute> </ProtectedRoute>
); );

View File

@ -93,7 +93,8 @@ html {
} }
.text-stroke { .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 { .big-swiper .swiper-pagination-bullet {
@ -121,20 +122,20 @@ html {
.small-swiper .swiper-button-next:after { .small-swiper .swiper-button-next:after {
color: white; color: white;
content: url('/arrow-right-small.svg'); content: url("/arrow-right-small.svg");
} }
.small-swiper .swiper-button-prev:after { .small-swiper .swiper-button-prev:after {
color: white; color: white;
content: url('/arrow-left-small.svg'); content: url("/arrow-left-small.svg");
} }
.big-swiper .swiper-button-next:after { .big-swiper .swiper-button-next:after {
color: white; color: white;
content: url('/arrow-right-big.svg'); content: url("/arrow-right-big.svg");
} }
.big-swiper .swiper-button-prev:after { .big-swiper .swiper-button-prev:after {
color: white; color: white;
content: url('/arrow-left-big.svg'); content: url("/arrow-left-big.svg");
} }
video { video {
@ -187,15 +188,15 @@ big {
@apply bg-[#E6E6FA] rounded-xl py-3 px-4 placeholder:text-[#BCBCD6] outline-none; @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; @apply shadow-sm transition-all;
} }
.calendar [aria-label='Go to previous month'] { .calendar [aria-label="Go to previous month"] {
@apply shadow-sm transition-all; @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; @apply p-4 text-textDarkt leading-[140%] bg-purple-600 rounded-full;
} }
@ -261,20 +262,12 @@ big {
} }
.wheel-circle { .wheel-circle {
background: url('/wheel-circle.svg'); background: url("/wheel-circle.svg");
background-position: center; background-position: center;
background-size: cover; background-size: cover;
background-repeat: no-repeat; background-repeat: no-repeat;
} }
.rolling-number {
font-size: 80px;
letter-spacing: -1%;
padding: 0px 16px;
@apply text-lightOnPrimary;
}
.index-module_slot__DpPgW { .index-module_slot__DpPgW {
overflow: visible !important; overflow: visible !important;
/* height: auto !important; */ /* height: auto !important; */
@ -303,11 +296,53 @@ big {
} */ } */
.slot-seperator { .slot-seperator {
content: url('/dash.svg'); content: url("/dash.svg");
background-position: center; background-position: center;
background-size: cover; background-size: cover;
background-repeat: no-repeat; 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 { .confetti-piece {
@ -324,7 +359,8 @@ big {
transform: translateX(0) translateY(0) rotate(0deg); transform: translateX(0) translateY(0) rotate(0deg);
} }
50% { 50% {
transform: translateX(calc(var(--end-x) / 2)) translateY(-30vh) rotate(180deg); transform: translateX(calc(var(--end-x) / 2)) translateY(-30vh)
rotate(180deg);
} }
100% { 100% {
opacity: 0; opacity: 0;

View File

@ -1,34 +1,43 @@
'use client'; "use client";
import { useState, useEffect, useRef } from 'react'; import { useState, useEffect, useRef } from "react";
import { useLotteryAuth } from '@/store/useLotteryAuth'; import { useLotteryAuth } from "@/store/useLotteryAuth";
import { LotteryWinnerDataSimplified } from '@/typings/lottery/lottery.types'; import { LotteryWinnerDataSimplified } from "@/typings/lottery/lottery.types";
import LotteryWinnersList from './winners/LotteryWinnersList'; import LotteryWinnersList from "./winners/LotteryWinnersList";
import LotterySlotCounter from './slotCounter/LotterySlotCounter'; import LotterySlotCounter from "./slotCounter/LotterySlotCounter";
import ReactConfetti from 'react-confetti'; import ReactConfetti from "react-confetti";
import { useWindowSize } from 'react-use'; 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 PING_INTERVAL = 25000;
const SLOT_COUNTER_DURATION = 20000; const SLOT_COUNTER_DURATION = 20000;
const LotteryWinnersSection = () => { const LotteryWinnersSection = ({
lotteryStatus,
}: {
lotteryStatus: string;
}) => {
// UI States // UI States
const [winners, setWinners] = useState<LotteryWinnerDataSimplified[]>([]); const [winners, setWinners] = useState<LotteryWinnerDataSimplified[]>([]);
const [currentNumber, setCurrentNumber] = useState<string>('00-00-00-00-00'); const [currentNumber, setCurrentNumber] = useState<string>("00-00-00-00-00");
const [isConfettiActive, setIsConfettiActive] = useState(false); const [isConfettiActive, setIsConfettiActive] = useState(false);
const [wsStatus, setWsStatus] = useState<'connecting' | 'connected' | 'error'>('connecting');
const [wsStatus, setWsStatus] = useState<
"connecting" | "connected" | "error"
>("connecting");
const { width, height } = useWindowSize(); const { width, height } = useWindowSize();
const { lotteryData } = useLotteryAuth(); const { lotteryData } = useLotteryAuth();
const [isSlotCounterAnimating, setIsSlotCounterAnimating] = useState(false); const [isSlotCounterAnimating, setIsSlotCounterAnimating] = useState(false);
const [pendingWinner, setPendingWinner] = useState<LotteryWinnerDataSimplified | null>(null); const [pendingWinner, setPendingWinner] =
useState<LotteryWinnerDataSimplified | null>(null);
// Refs // Refs
const wsRef = useRef<WebSocket | null>(null); const wsRef = useRef<WebSocket | null>(null);
const pingIntervalRef = useRef<NodeJS.Timeout>(); const pingIntervalRef = useRef<NodeJS.Timeout>();
const mountedRef = useRef(false); const mountedRef = useRef(false);
console.log(isConfettiActive, 'isConfettiActive'); console.log(isConfettiActive, "isConfettiActive");
// Initialize winners from lottery data // Initialize winners from lottery data
useEffect(() => { useEffect(() => {
@ -39,7 +48,9 @@ const LotteryWinnersSection = () => {
ticket: winner.ticket, ticket: winner.ticket,
})); }));
setWinners(simplifiedWinners); 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]); }, [lotteryData]);
@ -52,21 +63,21 @@ const LotteryWinnersSection = () => {
const socket = new WebSocket(WEBSOCKET_URL); const socket = new WebSocket(WEBSOCKET_URL);
wsRef.current = socket; wsRef.current = socket;
socket.addEventListener('open', () => { socket.addEventListener("open", () => {
if (!mountedRef.current) return; if (!mountedRef.current) return;
console.log('WebSocket Connected'); console.log("WebSocket Connected");
setWsStatus('connected'); setWsStatus("connected");
pingIntervalRef.current = setInterval(() => { pingIntervalRef.current = setInterval(() => {
if (socket.readyState === WebSocket.OPEN) { if (socket.readyState === WebSocket.OPEN) {
socket.send(JSON.stringify({ type: 'ping' })); socket.send(JSON.stringify({ type: "ping" }));
} }
}, PING_INTERVAL); }, PING_INTERVAL);
}); });
socket.addEventListener('message', async (event) => { socket.addEventListener("message", async (event) => {
if (!mountedRef.current) return; if (!mountedRef.current) return;
console.log('Message received:', event.data); console.log("Message received:", event.data);
try { try {
const newWinner = JSON.parse(event.data); const newWinner = JSON.parse(event.data);
@ -82,7 +93,9 @@ const LotteryWinnersSection = () => {
setCurrentNumber(winnerData.ticket); setCurrentNumber(winnerData.ticket);
// Wait for slot counter animation // 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); setIsConfettiActive(true);
setWinners((prev) => [...prev, winnerData]); setWinners((prev) => [...prev, winnerData]);
@ -111,30 +124,30 @@ const LotteryWinnersSection = () => {
}, 5000); }, 5000);
} }
} catch (error) { } catch (error) {
console.error('Error processing message:', error); console.error("Error processing message:", error);
setIsSlotCounterAnimating(false); setIsSlotCounterAnimating(false);
setPendingWinner(null); setPendingWinner(null);
} }
}); });
socket.addEventListener('error', (error) => { socket.addEventListener("error", (error) => {
if (!mountedRef.current) return; if (!mountedRef.current) return;
console.error('WebSocket Error:', error); console.error("WebSocket Error:", error);
setWsStatus('error'); setWsStatus("error");
}); });
socket.addEventListener('close', () => { socket.addEventListener("close", () => {
if (!mountedRef.current) return; if (!mountedRef.current) return;
console.log('WebSocket Closed'); console.log("WebSocket Closed");
setWsStatus('error'); setWsStatus("error");
if (pingIntervalRef.current) { if (pingIntervalRef.current) {
clearInterval(pingIntervalRef.current); clearInterval(pingIntervalRef.current);
} }
}); });
} catch (error) { } catch (error) {
console.error('Error creating WebSocket:', error); console.error("Error creating WebSocket:", error);
setWsStatus('error'); setWsStatus("error");
} }
}; };
@ -154,7 +167,7 @@ const LotteryWinnersSection = () => {
return ( return (
<section> <section>
{wsStatus === 'error' && ( {wsStatus === "error" && (
<div className="text-red-500 text-center mb-2"> <div className="text-red-500 text-center mb-2">
Connection error. Please refresh the page. Connection error. Please refresh the page.
</div> </div>
@ -170,12 +183,12 @@ const LotteryWinnersSection = () => {
tweenDuration={10000} tweenDuration={10000}
run={true} run={true}
colors={[ colors={[
'linear-gradient(45deg, #5D5D72, #8589DE)', "linear-gradient(45deg, #5D5D72, #8589DE)",
'linear-gradient(45deg, #E1E0FF, #575992)', "linear-gradient(45deg, #E1E0FF, #575992)",
'#8589DE', "#8589DE",
'#575992', "#575992",
'#E1E0FF', "#E1E0FF",
'#BA1A1A', "#BA1A1A",
]} ]}
/> />
</div> </div>
@ -183,10 +196,13 @@ const LotteryWinnersSection = () => {
<div className="container"> <div className="container">
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">
<div className="-mb-[90px] z-10"> <div className="md:-mb-[90px] sm:-mb-[40px] -mb-[20px] z-10">
<LotterySlotCounter numberString={currentNumber} isAnimating={isSlotCounterAnimating} /> <LotterySlotCounter
numberString={currentNumber}
isAnimating={isSlotCounterAnimating}
/>
</div> </div>
<div className="flex gap-6 bg-lightPrimaryContainer rounded-[12px] flex-1 w-full items-center justify-center pt-[122px] pb-[62px]"> <div className="flex gap-6 bg-lightPrimaryContainer rounded-[12px] flex-1 w-full items-center justify-center md:pt-[122px] sm:pt-[90px] pt-[40px] sm:pb-[62px] pb-[32px] md:px-0 px-4">
<LotteryWinnersList winners={winners} /> <LotteryWinnersList winners={winners} />
</div> </div>
</div> </div>

View File

@ -1,13 +1,13 @@
'use client'; "use client";
import { Queries } from '@/api/queries'; import { Queries } from "@/api/queries";
import { useState, FormEvent } from 'react'; import { useState, FormEvent } from "react";
import { useRouter } from 'next/navigation'; import { useRouter } from "next/navigation";
import { useLotteryAuth } from '@/store/useLotteryAuth'; import { useLotteryAuth } from "@/store/useLotteryAuth";
const LotteryAuthForm = () => { const LotteryAuthForm = () => {
const [phone, setPhone] = useState(''); const [phone, setPhone] = useState("");
const [code, setCode] = useState(''); const [code, setCode] = useState("");
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const router = useRouter(); const router = useRouter();
@ -28,12 +28,12 @@ const LotteryAuthForm = () => {
setError(null); setError(null);
if (!validatePhone(phone)) { if (!validatePhone(phone)) {
setError('Telefon belgisi nädogry formatda'); setError("Telefon belgisi nädogry formatda");
return; return;
} }
if (!validateCode(code)) { if (!validateCode(code)) {
setError('Kod nädogry formatda'); setError("Kod nädogry formatda");
return; return;
} }
@ -42,17 +42,17 @@ const LotteryAuthForm = () => {
try { try {
const response = await Queries.authenticateLottery(phone, code); const response = await Queries.authenticateLottery(phone, code);
setAuth(response, phone, code); setAuth(response, phone, code);
router.replace('/lottery'); router.replace("/lottery");
} catch (err) { } catch (err) {
console.error('Authentication error:', err); console.error("Authentication error:", err);
setError('Telefon belgisi ýa-da kod nädogry'); setError("Telefon belgisi ýa-da kod nädogry");
} finally { } finally {
setIsLoading(false); setIsLoading(false);
} }
}; };
const handlePhoneChange = (e: React.ChangeEvent<HTMLInputElement>) => { const handlePhoneChange = (e: React.ChangeEvent<HTMLInputElement>) => {
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) { if (value.length <= 11) {
// Limit to 11 digits (99363 + 6 digits) // Limit to 11 digits (99363 + 6 digits)
setPhone(value); setPhone(value);
@ -70,8 +70,11 @@ const LotteryAuthForm = () => {
return ( return (
<form <form
onSubmit={handleSubmit} onSubmit={handleSubmit}
className="bg-lightSurfaceContainer rounded-[24px] p-[40px] w-[530px] flex flex-col gap-[24px]"> className="bg-lightSurfaceContainer rounded-[24px] md:p-[40px] sm:p-[24px] p-[16px] w-[530px] flex flex-col gap-[24px]"
<h1 className="text-display3 font-[500] leading-display3">Lotereýa giriş</h1> >
<h1 className="md:text-display3 sm:text-[32px] text-[24px] font-[500] md:leading-display3">
Lotereýa giriş
</h1>
<div className="flex flex-col gap-[16px]"> <div className="flex flex-col gap-[16px]">
<div className="flex flex-col gap-[8px]"> <div className="flex flex-col gap-[8px]">
<input <input
@ -89,17 +92,22 @@ const LotteryAuthForm = () => {
value={code} value={code}
onChange={handleCodeChange} onChange={handleCodeChange}
className="px-[16px] py-[12px] bg-lightPrimaryContainer rounded-[12px] outline-none text-lightOnSurfaceVariant text-textSmall leading-textSmall" className="px-[16px] py-[12px] bg-lightPrimaryContainer rounded-[12px] outline-none text-lightOnSurfaceVariant text-textSmall leading-textSmall"
placeholder="5-0102030408" placeholder="5-0105639808"
required required
/> />
</div> </div>
{error && <p className="text-lightError text-textSmall leading-textSmall">{error}</p>} {error && (
<p className="text-lightError text-textSmall leading-textSmall">
{error}
</p>
)}
</div> </div>
<button <button
type="submit" type="submit"
disabled={isLoading || !phone || !code} disabled={isLoading || !phone || !code}
className="text-textLarge leading-textLarge py-[12px] w-full flex justify-center items-center rounded-[12px] bg-lightPrimary font-medium text-lightOnPrimary disabled:opacity-50"> 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ş'} >
{isLoading ? "Ýüklenilýär..." : "Giriş"}
</button> </button>
</form> </form>
); );

View File

@ -24,6 +24,8 @@ const LotteryCountDown: React.FC<LotteryCountDownProps> = ({
seconds: 0, seconds: 0,
}); });
console.log(lotteryStatus);
useEffect(() => { useEffect(() => {
const timer = setInterval(() => { const timer = setInterval(() => {
if (lotteryStatus === "not-started") { if (lotteryStatus === "not-started") {
@ -57,8 +59,8 @@ const LotteryCountDown: React.FC<LotteryCountDownProps> = ({
console.log(lotteryStatus); console.log(lotteryStatus);
return ( return (
<div className="bg-lightPrimaryContainer sm:p-6 p-2 flex flex-col w-full md:gap-2 rounded-[12px] text-lightOnPrimaryContainer"> <div className="bg-lightPrimaryContainer sm:p-6 py-3 flex flex-col w-full md:gap-2 rounded-[12px] sm:gap-3 gap-0 text-lightOnPrimaryContainer">
<h3 className="text-center md:font-heading-1-regular text-[32px] leading-[40px] text-lightOnSurface"> <h3 className="text-center md:font-heading-1-regular sm:text-[32px] sm:leading-[40px] text-[20px] leading-[28px] text-lightOnSurface">
{lotteryStatus === "started" {lotteryStatus === "started"
? "Bije dowam edýär" ? "Bije dowam edýär"
: lotteryStatus === "ended" : lotteryStatus === "ended"
@ -68,7 +70,7 @@ const LotteryCountDown: React.FC<LotteryCountDownProps> = ({
{/* LotteryCountDown */} {/* LotteryCountDown */}
{lotteryStatus === "not-started" && ( {lotteryStatus === "not-started" && (
<div className="flex items-center sm:gap-6 gap-2 justify-between"> <div className="flex items-center sm:gap-6 gap-2 justify-between">
<div className="flex flex-col items-center justify-center flex-1 p-6"> <div className="flex flex-col items-center justify-center flex-1 sm:p-6 p-4 sm:pb-3">
<h3 className="md:text-[80px] sm:text-[56px] text-[28px] md:leading-[88px] sm:leading-[64px] leading-[36px] -tracking-[1%]"> <h3 className="md:text-[80px] sm:text-[56px] text-[28px] md:leading-[88px] sm:leading-[64px] leading-[36px] -tracking-[1%]">
{timeLeft.hours} {timeLeft.hours}
</h3> </h3>
@ -77,12 +79,13 @@ const LotteryCountDown: React.FC<LotteryCountDownProps> = ({
</h4> </h4>
</div> </div>
<div className="flex flex-col gap-3"> {/* Dots */}
<div className="w-3 h-3 rounded-full bg-lightOnSurfaceVariant"></div> <div className="flex flex-col sm:gap-3 gap-2">
<div className="w-3 h-3 rounded-full bg-lightOnSurfaceVariant"></div> <div className="sm:w-3 sm:h-3 w-1 h-1 rounded-full bg-lightOnSurfaceVariant"></div>
<div className="sm:w-3 sm:h-3 w-1 h-1 rounded-full bg-lightOnSurfaceVariant"></div>
</div> </div>
<div className="flex flex-col items-center justify-center flex-1 sm:p-6 p-4 pb-3"> <div className="flex flex-col items-center justify-center flex-1 sm:p-6 p-4 sm:pb-3">
<h3 className="md:text-[80px] sm:text-[56px] text-[28px] md:leading-[88px] sm:leading-[64px] leading-[36px] -tracking-[1%]"> <h3 className="md:text-[80px] sm:text-[56px] text-[28px] md:leading-[88px] sm:leading-[64px] leading-[36px] -tracking-[1%]">
{timeLeft.minutes} {timeLeft.minutes}
</h3> </h3>
@ -91,12 +94,13 @@ const LotteryCountDown: React.FC<LotteryCountDownProps> = ({
</h4> </h4>
</div> </div>
<div className="flex flex-col gap-3"> {/* Dots */}
<div className="w-3 h-3 rounded-full bg-lightOnSurfaceVariant"></div> <div className="flex flex-col sm:gap-3 gap-2">
<div className="w-3 h-3 rounded-full bg-lightOnSurfaceVariant"></div> <div className="sm:w-3 sm:h-3 w-1 h-1 rounded-full bg-lightOnSurfaceVariant"></div>
<div className="sm:w-3 sm:h-3 w-1 h-1 rounded-full bg-lightOnSurfaceVariant"></div>
</div> </div>
<div className="flex flex-col items-center justify-center flex-1 p-6"> <div className="flex flex-col items-center justify-center flex-1 sm:p-6 p-4 sm:pb-3">
<h3 className="md:text-[80px] sm:text-[56px] text-[28px] md:leading-[88px] sm:leading-[64px] leading-[36px] -tracking-[1%]"> <h3 className="md:text-[80px] sm:text-[56px] text-[28px] md:leading-[88px] sm:leading-[64px] leading-[36px] -tracking-[1%]">
{timeLeft.seconds} {timeLeft.seconds}
</h3> </h3>

View File

@ -1,4 +1,4 @@
import { useLotteryAuth } from '@/store/useLotteryAuth'; import { useLotteryAuth } from "@/store/useLotteryAuth";
const LotteryRulesSection = () => { const LotteryRulesSection = () => {
const { lotteryData } = useLotteryAuth(); const { lotteryData } = useLotteryAuth();
@ -7,17 +7,18 @@ const LotteryRulesSection = () => {
<section> <section>
<div className="container"> <div className="container">
<div className="flex flex-col md:gap-8 gap-6"> <div className="flex flex-col md:gap-8 gap-6">
<h2 className="md:font-heading-1-regular text-[32px] leading-[40px]"> <h2 className="md:font-heading-1-regular sm:text-[32px] text-[26px] sm:leading-[40px] leading-[34px]">
Lotereýanyň duzgunleri: Lotereýanyň duzgunleri:
</h2> </h2>
<div className="flex gap-6">
<div className="flex flex-col md:gap-4 gap-2 bg-lightSurfaceContainer py-4 md:px-8 px-6 rounded-[12px] w-full"> <div className="flex sm:flex-row flex-col gap-6">
<h3 className="md:font-heading-5-regular text-[20px] leading-[24px]"> <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]">
Umumy düzgünler: Umumy düzgünler:
</h3> </h3>
<ul className="list-disc flex flex-col md:gap-4 gap-2 pl-[16px]"> <ul className="list-disc flex flex-col md:gap-4 gap-2 pl-[16px]">
{Array(5) {Array(5)
.fill(' ') .fill(" ")
.map((item, i) => ( .map((item, i) => (
<li className="font-small-regular" key={i}> <li className="font-small-regular" key={i}>
Ilkinji we dogry jogap beren sanawda ilkinji ýeri eýelýär Ilkinji we dogry jogap beren sanawda ilkinji ýeri eýelýär
@ -26,8 +27,10 @@ const LotteryRulesSection = () => {
</ul> </ul>
</div> </div>
<div className="flex flex-col md:gap-4 gap-2 bg-lightSurfaceContainer py-4 md:px-8 px-6 rounded-[12px] w-full"> <div className="flex flex-col md:gap-4 sm:gap-2 gap-4 bg-lightSurfaceContainer sm:py-4 md:px-8 sm:px-6 py-3 px-4 rounded-[12px] w-full">
<h3 className="md:font-heading-5-regular text-[20px] leading-[24px]">Üns beriň:</h3> <h3 className="md:font-heading-5-regular sm:text-[20px] text-[18px] sm:leading-[24px] leading-[28px]">
Üns beriň:
</h3>
<ul className="list-disc flex flex-col md:gap-4 gap-2 pl-[16px]"> <ul className="list-disc flex flex-col md:gap-4 gap-2 pl-[16px]">
{lotteryData?.user_lottery_numbers.map((item, i) => ( {lotteryData?.user_lottery_numbers.map((item, i) => (
<li className="font-small-regular" key={i}> <li className="font-small-regular" key={i}>

View File

@ -1,23 +1,39 @@
'use client'; "use client";
import Image from 'next/image'; import Image from "next/image";
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from "react";
import SlotCounter from 'react-slot-counter'; import SlotCounter from "react-slot-counter";
import { useMediaQuery } from "usehooks-ts";
interface LotterySlotCounterProps { interface LotterySlotCounterProps {
numberString: string; numberString: string;
isAnimating: boolean; isAnimating: boolean;
} }
const LotterySlotCounter = ({ numberString, isAnimating }: LotterySlotCounterProps) => { const LotterySlotCounter = ({
numberString,
isAnimating,
}: LotterySlotCounterProps) => {
const [formattedNumber, setFormattedNumber] = useState(numberString); const [formattedNumber, setFormattedNumber] = useState(numberString);
useEffect(() => { useEffect(() => {
const formatted = numberString.replace(/-/g, ','); const formatted = numberString.replace(/-/g, ",");
setFormattedNumber(formatted); setFormattedNumber(formatted);
}, [numberString]); }, [numberString]);
const tablet = useMediaQuery("(max-width: 769px)");
const mobile = useMediaQuery("(max-width: 426px)");
return ( return (
<div className="relative w-fit"> <div className="relative w-fit">
{mobile ? (
<Image
src="/roller-triangle-sm.svg"
alt="roller-triangle"
width={24}
height={24}
className="absolute left-0 top-1/2 -translate-y-1/2 -translate-x-1/2 z-20"
/>
) : (
<Image <Image
src="/roller-triangle.svg" src="/roller-triangle.svg"
alt="roller-triangle" alt="roller-triangle"
@ -25,6 +41,17 @@ const LotterySlotCounter = ({ numberString, isAnimating }: LotterySlotCounterPro
height={48} height={48}
className="absolute left-0 top-1/2 -translate-y-1/2 -translate-x-1/2 z-20" className="absolute left-0 top-1/2 -translate-y-1/2 -translate-x-1/2 z-20"
/> />
)}
{mobile ? (
<Image
src="/roller-triangle-sm.svg"
alt="roller-triangle"
width={24}
height={24}
className="absolute right-0 top-1/2 -translate-y-1/2 translate-x-1/2 z-20 rotate-180"
/>
) : (
<Image <Image
src="/roller-triangle.svg" src="/roller-triangle.svg"
alt="roller-triangle" alt="roller-triangle"
@ -32,20 +59,24 @@ const LotterySlotCounter = ({ numberString, isAnimating }: LotterySlotCounterPro
height={48} height={48}
className="absolute right-0 top-1/2 -translate-y-1/2 translate-x-1/2 z-20 rotate-180" className="absolute right-0 top-1/2 -translate-y-1/2 translate-x-1/2 z-20 rotate-180"
/> />
)}
<div <div
className="flex w-full items-center h-[180px] max-w-[1132px] justify-center text-white py-4 px-6 rounded-full overflow-y-hidden overflow-x-visible relative border-4 border-lightPrimary" className="flex items-center md:h-[180px] sm:h-[100px] h-[50px] 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={{ style={{
background: background:
'linear-gradient(180deg, #454673 0%, #575992 10.5%, #575992 90%, #454673 100%)', "linear-gradient(180deg, #454673 0%, #575992 10.5%, #575992 90%, #454673 100%)",
boxShadow: '0px 4px 4px 0px #00000040', boxShadow: "0px 4px 4px 0px #00000040",
}}> }}
>
{/* Highlight */} {/* Highlight */}
<div <div
className="absolute top-[50%] -translate-y-1/2 left-0 w-full h-full" className="absolute top-[50%] -translate-y-1/2 left-0 w-full h-full"
style={{ style={{
background: background:
'linear-gradient(180deg, rgba(87, 89, 146, 0) 0%, #7274AB 50%, rgba(87, 89, 146, 0) 100%)', "linear-gradient(180deg, rgba(87, 89, 146, 0) 0%, #7274AB 50%, rgba(87, 89, 146, 0) 100%)",
}}></div> }}
></div>
<div className="z-10"> <div className="z-10">
<SlotCounter <SlotCounter

View File

@ -1,6 +1,6 @@
'use client'; "use client";
import { motion } from 'framer-motion'; import { motion } from "framer-motion";
interface IProps { interface IProps {
phone: string; phone: string;
@ -17,14 +17,15 @@ const LotteryWinner = ({ phone, ticket, isNew, winnerNumber }: IProps) => {
animate={{ opacity: 1, translateY: 0 }} animate={{ opacity: 1, translateY: 0 }}
exit={{ opacity: 0, translateY: -20 }} exit={{ opacity: 0, translateY: -20 }}
transition={{ duration: 0.5 }} 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]"
>
<h4 className="md:font-heading-6-regular text-[20px] leading-[28px]"> <h4 className="md:font-heading-6-regular text-[20px] leading-[28px]">
The winner of the {winnerNumber} stage: The winner of the {winnerNumber} stage:
</h4> </h4>
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<p className="font-base-medium">{phone}</p> <p className="md:font-base-medium font-base-regular">{phone}</p>
<div className="w-1 h-1 rounded-full bg-lightOnSurfaceVariant"></div> <div className="w-1 h-1 rounded-full bg-lightOnSurfaceVariant"></div>
<p className="font-base-medium">{ticket}</p> <p className="md:font-base-medium font-base-regular">{ticket}</p>
</div> </div>
</motion.div> </motion.div>
); );

View File

@ -1,18 +1,28 @@
import { LotteryWinnerData, LotteryWinnerDataSimplified } from '@/typings/lottery/lottery.types'; import {
import LotteryWinner from './LotteryWinner'; LotteryWinnerData,
import { motion, AnimatePresence } from 'framer-motion'; LotteryWinnerDataSimplified,
import { v4 } from 'uuid'; } 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 ( return (
<div className="flex flex-col gap-4 w-full max-w-[1028px]"> <div className="flex flex-col gap-4 w-full max-w-[1028px]">
<div className="flex flex-col gap-2 w-full md:pb-4 pb-3 border-b border-[#CECCFF]"> <div className="flex flex-col gap-2 w-full pb-4 border-b border-[#CECCFF]">
<h4 className="font-heading-3-regular">Results</h4> <h4 className="md:font-heading-3-regular text-[28px]">Results</h4>
<p className="font-base-medium">The results after each stage will be shown here.</p> <p className="md:font-base-medium text-[16px]">
The results after each stage will be shown here.
</p>
</div> </div>
<motion.div <motion.div
layout layout
className="grid grid-cols-3 gap-x-2 gap-y-4 w-full h-[244px] overflow-y-auto lottery-scrollbar"> 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"
>
<AnimatePresence mode="popLayout"> <AnimatePresence mode="popLayout">
{winners.map((item, index) => ( {winners.map((item, index) => (
<LotteryWinner <LotteryWinner

3
public/dash-md.svg Normal file
View File

@ -0,0 +1,3 @@
<svg width="25" height="4" viewBox="0 0 25 4" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="24" height="4" transform="translate(0.25)" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 172 B

3
public/dash-sm.svg Normal file
View File

@ -0,0 +1,3 @@
<svg width="12" height="2" viewBox="0 0 12 2" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="12" height="2" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 144 B

View File

@ -0,0 +1,3 @@
<svg width="18" height="22" viewBox="0 0 18 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1 2.33975L16 11L1 19.6603L1 2.33975Z" fill="#575992" stroke="#D8D6FF" stroke-width="2"/>
</svg>

After

Width:  |  Height:  |  Size: 202 B

View File

@ -0,0 +1,3 @@
<svg width="18" height="22" viewBox="0 0 18 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1 2.33975L16 11L1 19.6603L1 2.33975Z" fill="#575992" stroke="#D8D6FF" stroke-width="2"/>
</svg>

After

Width:  |  Height:  |  Size: 202 B