From ebf7ea65a696f22458bccf756855f1e86ab3d96a Mon Sep 17 00:00:00 2001 From: Kakabay <2kakabayashyrberdyew@gmail.com> Date: Mon, 6 Jan 2025 16:43:35 +0500 Subject: [PATCH] added confetti and rolling digits animation to voting --- app/globals.css | 2 +- components/common/Confetti.tsx | 32 +++ components/common/RollingCounter.tsx | 27 +++ .../lottery/RollingCounter/RollingCounter.tsx | 191 ------------------ components/lottery/confetti/Confetti.tsx | 68 ------- components/vote/ParticipantCard.tsx | 17 +- components/vote/ParticipantsList.tsx | 8 +- 7 files changed, 81 insertions(+), 264 deletions(-) create mode 100644 components/common/Confetti.tsx create mode 100644 components/common/RollingCounter.tsx delete mode 100644 components/lottery/RollingCounter/RollingCounter.tsx delete mode 100644 components/lottery/confetti/Confetti.tsx diff --git a/app/globals.css b/app/globals.css index ecd4cd3..bb40758 100644 --- a/app/globals.css +++ b/app/globals.css @@ -268,7 +268,7 @@ big { } .index-module_slot__DpPgW { - overflow: visible !important; + /* overflow: visible !important; */ /* height: auto !important; */ } diff --git a/components/common/Confetti.tsx b/components/common/Confetti.tsx new file mode 100644 index 0000000..431f436 --- /dev/null +++ b/components/common/Confetti.tsx @@ -0,0 +1,32 @@ +'use client'; + +import ReactConfetti from 'react-confetti'; +import { useWindowSize } from 'react-use'; + +const Confetti = () => { + const { width, height } = useWindowSize(); + const colors = [ + 'linear-gradient(45deg, #5D5D72, #8589DE)', + 'linear-gradient(45deg, #E1E0FF, #575992)', + '#8589DE', + '#575992', + '#E1E0FF', + '#FF3131', + ]; + + return ( +
+ +
+ ); +}; + +export default Confetti; diff --git a/components/common/RollingCounter.tsx b/components/common/RollingCounter.tsx new file mode 100644 index 0000000..6bbb5e5 --- /dev/null +++ b/components/common/RollingCounter.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import SlotCounter from 'react-slot-counter'; + +interface IProps { + value: string | number; + startValueOnce?: boolean; + animateOnVisible?: boolean; + autoAnimationStart?: boolean; +} + +const RollingCounter = ({ + value, + startValueOnce = false, + animateOnVisible = false, + autoAnimationStart = false, +}: IProps) => { + return ( + + ); +}; + +export default RollingCounter; diff --git a/components/lottery/RollingCounter/RollingCounter.tsx b/components/lottery/RollingCounter/RollingCounter.tsx deleted file mode 100644 index 39d19ed..0000000 --- a/components/lottery/RollingCounter/RollingCounter.tsx +++ /dev/null @@ -1,191 +0,0 @@ -'use client'; -import { motion } from 'framer-motion'; -import { useCallback, useEffect, useMemo, useState, useRef } from 'react'; - -interface RollingCounterProps { - numberString: string; -} - -// Move constants outside component -const ROLLS = 2; -const DIGIT_HEIGHT = 104; -const EXTRA_NUMBERS_BEFORE = 2; -const EXTRA_NUMBERS_AFTER = 5; -const CONTAINER_HEIGHT = 180; -const INITIAL_OFFSET = (CONTAINER_HEIGHT - DIGIT_HEIGHT) / 2 - DIGIT_HEIGHT * 2; - -// Memoize number generation function -const getNumbers = (targetValue: number) => { - const numbers = []; - - // Add extra numbers before - for (let n = 10 - EXTRA_NUMBERS_BEFORE; n < 10; n++) { - numbers.push(n); - } - - // Start with zeros - for (let n = 0; n <= targetValue; n++) { - numbers.push(n); - } - - // Add complete rolls after the initial sequence - for (let i = 0; i < ROLLS; i++) { - for (let n = 0; n < 10; n++) { - numbers.push(n); - } - } - - // Add sequence to target - for (let n = 0; n <= targetValue; n++) { - numbers.push(n); - } - - // Add extra numbers after target - for (let n = (targetValue + 1) % 10; n < ((targetValue + 1) % 10) + EXTRA_NUMBERS_AFTER; n++) { - numbers.push(n % 10); - } - - return numbers; -}; - -const RollingDigit = ({ - targetValue, - index, - onAnimationComplete, - isStopped, - showHyphen, - totalDigits, - isRollingBack, -}: { - targetValue: number; - index: number; - onAnimationComplete: () => void; - isStopped: boolean; - showHyphen: boolean; - totalDigits: number; - isRollingBack: boolean; -}) => { - const numbers = useMemo(() => getNumbers(targetValue), [targetValue]); - - return ( -
-
- - {numbers.map((num, i) => ( -
- {num} -
- ))} -
-
- {showHyphen &&
} -
- ); -}; - -const RollingCounter: React.FC = ({ numberString }) => { - const [isStopped, setIsStopped] = useState([]); - const [isRollingBack, setIsRollingBack] = useState(false); - const [isTransitioning, setIsTransitioning] = useState(false); - const [currentNumbers, setCurrentNumbers] = useState([]); - const prevNumberStringRef = useRef(numberString); - - const { numbers, isInitialLoading } = useMemo(() => { - if (!numberString) { - return { numbers: [], isInitialLoading: true }; - } - - const parsed = numberString.split('-').flatMap((pair) => { - return pair.split('').map((char) => parseInt(char, 10)); - }); - - return { - numbers: parsed, - isInitialLoading: false, - }; - }, [numberString]); - - // Initialize currentNumbers - useEffect(() => { - if (!currentNumbers.length && numbers.length) { - setCurrentNumbers(numbers); - } - }, [numbers, currentNumbers.length]); - - // Handle number changes - useEffect(() => { - if (prevNumberStringRef.current !== numberString && !isRollingBack) { - setIsTransitioning(true); - setIsRollingBack(true); - setIsStopped(new Array(numbers.length).fill(false)); - - setTimeout(() => { - setIsRollingBack(false); - setCurrentNumbers(numbers); - prevNumberStringRef.current = numberString; - }, 2000); - } - }, [numberString, numbers, isRollingBack]); - - const handleAnimationComplete = useCallback( - (index: number) => { - if (!isRollingBack) { - setIsStopped((prev) => { - const newState = [...prev]; - newState[index] = true; - - if (newState.every((stopped) => stopped)) { - setIsTransitioning(false); - } - - return newState; - }); - } - }, - [isRollingBack], - ); - - if (isInitialLoading) { - return ( -
- Loading... -
- ); - } - - return ( -
- {currentNumbers.map((num, index) => ( - handleAnimationComplete(index)} - isStopped={isStopped[index] && !isTransitioning} - showHyphen={(index + 1) % 2 === 0 && index !== numbers.length - 1} - totalDigits={numbers.length} - isRollingBack={isRollingBack} - /> - ))} -
- ); -}; - -export default RollingCounter; diff --git a/components/lottery/confetti/Confetti.tsx b/components/lottery/confetti/Confetti.tsx deleted file mode 100644 index 371b4ed..0000000 --- a/components/lottery/confetti/Confetti.tsx +++ /dev/null @@ -1,68 +0,0 @@ -"use client"; - -import React, { useEffect, useState } from "react"; -import { cn } from "@/lib/utils"; // Assuming `cn` is defined in `lib/utils` - -const Confetti: React.FC = () => { - const [confetti, setConfetti] = useState([]); - - useEffect(() => { - const confettiArray = Array.from({ length: 300 }, (_, i) => i); // Generate 300 confetti pieces - setConfetti(confettiArray); - - const timer = setTimeout(() => { - setConfetti([]); - }, 5000); - - return () => clearTimeout(timer); - }, []); - - const randomColor = () => { - const colors = [ - "linear-gradient(45deg, #5D5D72, #8589DE)", - "linear-gradient(45deg, #E1E0FF, #575992)", - "#8589DE", - "#575992", - "#E1E0FF", - ]; - return colors[Math.floor(Math.random() * colors.length)]; - }; - - if (confetti.length === 0) return null; - - return ( -
- {confetti.map((_, i) => { - const randomSize = Math.random() * 10 + 8; // Size between 8px and 18px - const randomStartX = Math.random() * 100; // Start anywhere across the screen - const randomStartY = Math.random() < 0.5 ? -10 : 110; // Start above or below the screen - const randomDuration = Math.random() * 1 + 4; // Duration between 4s and 5s - const randomEndX = Math.random() * 200 - 100; // Drift horizontally (-100vw to +100vw) - const randomRotation = Math.random() * 360; // Start with a random rotation - const randomDelay = Math.random() * 0.5; // Delay between 0-0.5s - - return ( -
- ); - })} -
- ); -}; - -export default Confetti; diff --git a/components/vote/ParticipantCard.tsx b/components/vote/ParticipantCard.tsx index 8b7c7e9..34ff6af 100644 --- a/components/vote/ParticipantCard.tsx +++ b/components/vote/ParticipantCard.tsx @@ -4,6 +4,7 @@ import Image from 'next/image'; import placeholder from '@/public/person placeholder.svg'; import clsx from 'clsx'; import { motion, usePresence, Variant, Transition } from 'framer-motion'; +import RollingCounter from 'react-slot-counter'; interface IProps { number: number; @@ -128,7 +129,13 @@ const ParticipantCard = ({

- {votes ? votes : 0} + {/* {votes ? votes : 0} */} +

ses @@ -210,7 +217,13 @@ const ParticipantCard = ({

- {votes ? votes : 0} + {/* {votes ? votes : 0} */} +

ses diff --git a/components/vote/ParticipantsList.tsx b/components/vote/ParticipantsList.tsx index e2c3550..8f2aa8d 100644 --- a/components/vote/ParticipantsList.tsx +++ b/components/vote/ParticipantsList.tsx @@ -12,7 +12,8 @@ import Image from 'next/image'; import { useMediaQuery } from 'usehooks-ts'; import Countdown from './Countdown'; import Link from 'next/link'; -import { AnimatePresence } from 'framer-motion'; +import { useWindowSize } from 'react-use'; +import Confetti from '../common/Confetti'; interface IParams { vote_id?: string; @@ -30,7 +31,7 @@ const ParticipantsList = ({ vote_id }: IParams) => { const [data, setData] = useState(); const [participantsData, setParticipantsData] = useState([]); const [voteStatus, setVoteStatus] = useState(); - const [eventStatus, setEventStatus] = useState('Finished'); + const [eventStatus, setEventStatus] = useState('Not started'); const [manualClose, setManualClose] = useState(false); // Track manual closure const [winnersCount, setWinnersCount] = useState(0); @@ -41,6 +42,7 @@ const ParticipantsList = ({ vote_id }: IParams) => { const [isConnected, setIsConnected] = useState(false); const mobile = useMediaQuery('(max-width: 768px)'); + const { width, height } = useWindowSize(); const { setVoteDescription } = useContext(VoteContext).voteDescriptionContext; @@ -209,6 +211,8 @@ const ParticipantsList = ({ vote_id }: IParams) => {
{data.data.description ? : null} + {eventStatus === 'Finished' && } + {data.data.banner ? (
{mobile ? (