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]);
return (
<div className="container">
<div className="flex justify-center items-center min-h-[50vh] py-[200px]">
<LotteryAuthForm />
</div>
</div>
);
};

View File

@ -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 (
<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 && (
<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}
/>
)}
{lotteryData ? (
status === 'not-started' ? (
{status === "not-started" ? (
<div className="container">
<LotteryCountDown
lotteryStatus={status}
setLotteryStatus={setStatus}
endDate={lotteryData?.data.end_time}
startDate={lotteryData?.data.start_time}
endDate={lotteryData.data.end_time}
startDate={lotteryData.data.start_time}
/>
</div>
) : (
<div className="container">
<LotteryCountDownAllert
lotteryStatus={status}
setLotteryStatus={setStatus}
endDate={lotteryData?.data.end_time}
startDate={lotteryData?.data.start_time}
/>
</div>
)
) : null}
</div>
)}
<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>
</ProtectedRoute>
);

View File

@ -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;

View File

@ -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<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 [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<LotteryWinnerDataSimplified | null>(null);
const [pendingWinner, setPendingWinner] =
useState<LotteryWinnerDataSimplified | null>(null);
// Refs
const wsRef = useRef<WebSocket | null>(null);
const pingIntervalRef = useRef<NodeJS.Timeout>();
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 (
<section>
{wsStatus === 'error' && (
{wsStatus === "error" && (
<div className="text-red-500 text-center mb-2">
Connection error. Please refresh the page.
</div>
@ -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",
]}
/>
</div>
@ -183,10 +196,13 @@ const LotteryWinnersSection = () => {
<div className="container">
<div className="flex flex-col items-center">
<div className="-mb-[90px] z-10">
<LotterySlotCounter numberString={currentNumber} isAnimating={isSlotCounterAnimating} />
<div className="md:-mb-[90px] sm:-mb-[40px] -mb-[20px] z-10">
<LotterySlotCounter
numberString={currentNumber}
isAnimating={isSlotCounterAnimating}
/>
</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} />
</div>
</div>

View File

@ -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<string | null>(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<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) {
// Limit to 11 digits (99363 + 6 digits)
setPhone(value);
@ -70,8 +70,11 @@ const LotteryAuthForm = () => {
return (
<form
onSubmit={handleSubmit}
className="bg-lightSurfaceContainer rounded-[24px] p-[40px] w-[530px] flex flex-col gap-[24px]">
<h1 className="text-display3 font-[500] leading-display3">Lotereýa giriş</h1>
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">
Lotereýa giriş
</h1>
<div className="flex flex-col gap-[16px]">
<div className="flex flex-col gap-[8px]">
<input
@ -89,17 +92,22 @@ const LotteryAuthForm = () => {
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
/>
</div>
{error && <p className="text-lightError text-textSmall leading-textSmall">{error}</p>}
{error && (
<p className="text-lightError text-textSmall leading-textSmall">
{error}
</p>
)}
</div>
<button
type="submit"
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">
{isLoading ? 'Ýüklenilýär...' : 'Giriş'}
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>
);

View File

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

View File

@ -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 = () => {
<section>
<div className="container">
<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:
</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">
<h3 className="md:font-heading-5-regular text-[20px] leading-[24px]">
<div className="flex sm:flex-row flex-col gap-6">
<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:
</h3>
<ul className="list-disc flex flex-col md:gap-4 gap-2 pl-[16px]">
{Array(5)
.fill(' ')
.fill(" ")
.map((item, i) => (
<li className="font-small-regular" key={i}>
Ilkinji we dogry jogap beren sanawda ilkinji ýeri eýelýär
@ -26,8 +27,10 @@ const LotteryRulesSection = () => {
</ul>
</div>
<div className="flex flex-col md:gap-4 gap-2 bg-lightSurfaceContainer py-4 md:px-8 px-6 rounded-[12px] w-full">
<h3 className="md:font-heading-5-regular text-[20px] leading-[24px]">Üns beriň:</h3>
<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]">
Üns beriň:
</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}>

View File

@ -1,23 +1,39 @@
'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 (
<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
src="/roller-triangle.svg"
alt="roller-triangle"
@ -25,6 +41,17 @@ const LotterySlotCounter = ({ numberString, isAnimating }: LotterySlotCounterPro
height={48}
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
src="/roller-triangle.svg"
alt="roller-triangle"
@ -32,20 +59,24 @@ const LotterySlotCounter = ({ numberString, isAnimating }: LotterySlotCounterPro
height={48}
className="absolute right-0 top-1/2 -translate-y-1/2 translate-x-1/2 z-20 rotate-180"
/>
)}
<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={{
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

View File

@ -1,6 +1,6 @@
'use client';
"use client";
import { motion } from 'framer-motion';
import { motion } from "framer-motion";
interface IProps {
phone: string;
@ -17,14 +17,15 @@ const LotteryWinner = ({ phone, ticket, isNew, winnerNumber }: IProps) => {
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]"
>
<h4 className="md:font-heading-6-regular text-[20px] leading-[28px]">
The winner of the {winnerNumber} stage:
</h4>
<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>
<p className="font-base-medium">{ticket}</p>
<p className="md:font-base-medium font-base-regular">{ticket}</p>
</div>
</motion.div>
);

View File

@ -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 (
<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]">
<h4 className="font-heading-3-regular">Results</h4>
<p className="font-base-medium">The results after each stage will be shown here.</p>
<div className="flex flex-col gap-2 w-full pb-4 border-b border-[#CECCFF]">
<h4 className="md:font-heading-3-regular text-[28px]">Results</h4>
<p className="md:font-base-medium text-[16px]">
The results after each stage will be shown here.
</p>
</div>
<motion.div
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">
{winners.map((item, index) => (
<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