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 ? (