diff --git a/src/components/dashboard/Dashboard.tsx b/src/components/dashboard/Dashboard.tsx index 39a8614..6fe7bef 100644 --- a/src/components/dashboard/Dashboard.tsx +++ b/src/components/dashboard/Dashboard.tsx @@ -11,6 +11,7 @@ import FootprintChart from "@/components/charts/FootprintChart"; import ComparisonSection from "@/components/dashboard/ComparisonSection"; import ShareButton from "@/components/ui/ShareButton"; import { getUserFootprints } from "@/lib/firebase/firestore"; +import ImpactStats from '../ImpactStats'; type SortOption = "newest" | "oldest" | "highest_impact" | "lowest_impact"; @@ -321,7 +322,8 @@ export default function Dashboard({ )} - + + {/* Recent Activities / Activity History (UPDATED SECTION) */} {activityHistory.length > 0 && (
diff --git a/src/components/impactStats.tsx b/src/components/impactStats.tsx new file mode 100644 index 0000000..cd9a49c --- /dev/null +++ b/src/components/impactStats.tsx @@ -0,0 +1,68 @@ +"use client"; + +import React from 'react'; +import { IMPACT_STATS, INSPIRATIONAL_TAGLINE, ImpactStat } from '../constants/impactStats'; +import useCountUp from '../hooks/useCountUp'; + +const StatCard: React.FC<{ stat: ImpactStat }> = ({ stat }) => { + const duration = stat.value > 1000 ? 2500 : 3000; + + const animatedValue = useCountUp(stat.value, duration, 0); + + let displayContent: string; + let unit: string = ''; + + if (stat.decimalPlaces !== undefined) { + displayContent = animatedValue.toFixed(stat.decimalPlaces); + unit = ' tons'; + } else { + const roundedValue = Math.round(animatedValue); + displayContent = roundedValue.toLocaleString(); + } + + const plusSign = stat.showPlus ? '+' : ''; + + const finalDisplay = `${plusSign}${displayContent}${unit}`; + + return ( +
+

+ {finalDisplay} +

+

+ {stat.suffix} +

+
+ ); +}; + +const ImpactStats: React.FC = () => { + return ( +
+

+ 🌱 + {INSPIRATIONAL_TAGLINE} +

+ +
+ {IMPACT_STATS.map(stat => ( + + ))} +
+
+ ); +}; + +export default ImpactStats; diff --git a/src/constants/impactStats.ts b/src/constants/impactStats.ts new file mode 100644 index 0000000..4b76773 --- /dev/null +++ b/src/constants/impactStats.ts @@ -0,0 +1,32 @@ +export interface ImpactStat { + id: number; + suffix: string; + value: number; + decimalPlaces?: number; + showPlus?: boolean; +} + +export const INSPIRATIONAL_TAGLINE = "Every action counts. Join the collective impact."; + +export const IMPACT_STATS: ImpactStat[] = [ + { + id: 1, + suffix: 'activities tracked', + value: 10500, + showPlus: true, // Show '+' for users + }, + { + id: 2, + suffix: 'Tons of CO2 awareness raised', + value: 2.5, + decimalPlaces: 1, + showPlus: true, + }, + { + id: 3, + suffix: 'eco-warriors joined', + value: 530, + showPlus:true, + }, +]; + diff --git a/src/hooks/useCountUp.ts b/src/hooks/useCountUp.ts new file mode 100644 index 0000000..ebf96fb --- /dev/null +++ b/src/hooks/useCountUp.ts @@ -0,0 +1,38 @@ +import { useState, useEffect, useRef } from 'react'; + +const useCountUp = (endValue: number, duration: number = 2000, startValue: number = 0): number => { + const [count, setCount] = useState(startValue); + const startTime = useRef(null); + + useEffect(() => { + + setCount(startValue); + startTime.current = null; + + const animateCount = (timestamp: number) => { + if (!startTime.current) { + startTime.current = timestamp; + } + + const progress = timestamp - startTime.current; + const easingProgress = Math.min(1, progress / duration); + + const easedProgress = 1 - Math.pow(1 - easingProgress, 3); + const nextValue = startValue + (endValue - startValue) * easedProgress; + + setCount(nextValue); + if (progress < duration) { + requestAnimationFrame(animateCount); + } else { + setCount(endValue); + } + }; + + const frameId = requestAnimationFrame(animateCount); + + return () => cancelAnimationFrame(frameId); + }, [endValue, duration, startValue]); + return Math.round(count * 100) / 100; +}; + +export default useCountUp; \ No newline at end of file