Skip to content

Commit

Permalink
streak animations
Browse files Browse the repository at this point in the history
  • Loading branch information
hussaino03 committed Dec 25, 2024
1 parent c068a7f commit 3938079
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 6 deletions.
40 changes: 40 additions & 0 deletions client/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"date-fns": "^4.1.0",
"date-fns-tz": "^3.2.0",
"dotenv": "^16.4.5",
"framer-motion": "^11.15.0",
"lodash": "^4.17.21",
"lucide-react": "^0.456.0",
"react": "^18.2.0",
Expand Down
33 changes: 27 additions & 6 deletions client/src/components/Streak Management/StreakTracker.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,27 @@
import React, { useCallback } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import Dashboard from '../Analytics/Dashboard';
import { ChartBarIcon } from '@heroicons/react/24/outline';
import { Bot } from 'lucide-react';
import Chat from '../AI/Chat';
import { createFireElement } from '../../utils/other/animationsUtils';

const StreakTracker = ({ completedTasks, streakData }) => {
const [openDashboard, setOpenDashboard] = React.useState(null);
const [isChatOpen, setIsChatOpen] = React.useState(false);
const [isChatOpen, setIsChatOpen] = useState(false);
const [showFireAnimation, setShowFireAnimation] = useState(false);
const [prevStreak, setPrevStreak] = useState(streakData.current);

useEffect(() => {
if (streakData.current > prevStreak) {
setShowFireAnimation(true);
const fireTimer = setTimeout(() => {
setShowFireAnimation(false);
}, 2000);
return () => clearTimeout(fireTimer);
}
setPrevStreak(streakData.current);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [prevStreak, streakData?.current]);

const handleOpenDashboard = useCallback((opener) => {
setOpenDashboard(() => opener);
Expand All @@ -15,13 +30,19 @@ const StreakTracker = ({ completedTasks, streakData }) => {
return (
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-sm p-6 transition-colors duration-200">
<div className="grid grid-cols-2 gap-4 mb-6">
<div className="text-center p-3 bg-gray-50 dark:bg-gray-700 rounded-lg">
<div className="relative text-center p-3 bg-gray-50 dark:bg-gray-700 rounded-lg">
<p className="text-sm text-gray-600 dark:text-gray-400 mb-1">Current Streak</p>
<p className="text-2xl font-bold text-blue-600 dark:text-blue-400">{streakData.current}</p>
<div className="relative">
<p className="text-2xl font-bold text-blue-600 dark:text-blue-400">{streakData.current}</p>
{createFireElement(showFireAnimation)}
</div>
</div>
<div className="text-center p-3 bg-gray-50 dark:bg-gray-700 rounded-lg">
<div className="relative text-center p-3 bg-gray-50 dark:bg-gray-700 rounded-lg">
<p className="text-sm text-gray-600 dark:text-gray-400 mb-1">Longest Streak</p>
<p className="text-2xl font-bold" style={{ color: '#77dd77' }}>{streakData.longest}</p>
<div className="relative">
<p className="text-2xl font-bold" style={{ color: '#77dd77' }}>{streakData.longest}</p>
{createFireElement(showFireAnimation && streakData.current >= streakData.longest)}
</div>
</div>
</div>
<div className="mt-6 pt-6 border-t border-gray-200 dark:border-gray-700">
Expand Down
36 changes: 36 additions & 0 deletions client/src/utils/other/animationsUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { motion, AnimatePresence } from 'framer-motion';
import React from 'react';

/**
* Creates an animated fire emoji element for streak celebrations
* @param {boolean} isVisible - Controls the visibility of the fire animation
* @returns {JSX.Element|null} A motion-animated fire emoji that scales and fades
*/
export const createFireElement = (isVisible) => {
return (
<AnimatePresence>
{isVisible && (
<motion.div
className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 pointer-events-none"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
>
<motion.span
className="text-2xl inline-block"
animate={{
scale: [1, 1.1, 1],
}}
transition={{
duration: 0.4,
repeat: 3,
ease: "easeInOut"
}}
>
🔥
</motion.span>
</motion.div>
)}
</AnimatePresence>
);
};

0 comments on commit 3938079

Please sign in to comment.