Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/components/dashboard/Dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -321,7 +322,8 @@ export default function Dashboard({
</div>
</div>
)}


<ImpactStats />
{/* Recent Activities / Activity History (UPDATED SECTION) */}
{activityHistory.length > 0 && (
<div className="bg-white rounded-xl shadow-lg p-8">
Expand Down
68 changes: 68 additions & 0 deletions src/components/impactStats.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div
className="stat-card bg-green-50 shadow-lg rounded-2xl p-6 w-full max-w-xs text-center transform hover:scale-[1.05] transition duration-300 border-t-4 border-green-500"
>
<p
className="stat-value text-6xl font-extrabold text-green-700 mb-2"
>
{finalDisplay}
</p>
<p
className="stat-label text-base font-medium text-gray-700"
>
{stat.suffix}
</p>
</div>
);
};

const ImpactStats: React.FC = () => {
return (
<section
className="py-16 px-4 bg-white rounded-xl shadow-md my-8"
>
<h2
className="text-center text-3xl md:text-4xl font-bold mb-12 text-gray-800"
>
<span className="text-green-600">🌱 </span>
{INSPIRATIONAL_TAGLINE}
</h2>

<div
className="flex justify-center flex-wrap gap-8"
>
{IMPACT_STATS.map(stat => (
<StatCard key={stat.id} stat={stat} />
))}
</div>
</section>
);
};

export default ImpactStats;
32 changes: 32 additions & 0 deletions src/constants/impactStats.ts
Original file line number Diff line number Diff line change
@@ -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,
},
];

38 changes: 38 additions & 0 deletions src/hooks/useCountUp.ts
Original file line number Diff line number Diff line change
@@ -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<number | null>(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;