diff --git a/app/page.tsx b/app/page.tsx index ac7044b..eb40aab 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,10 +1,12 @@ import Link from "next/link"; -import { ArrowRight, Shield, LineChart, Zap, Package, Users, CreditCard } from "lucide-react"; +import { ArrowRight, Shield, LineChart, Zap } from "lucide-react"; import { Button } from "@/components/ui/button"; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { fetchPlatformStats } from "@/lib/stats-service"; import { HomeNavbar } from "@/components/home-navbar"; import { Suspense } from "react"; +import { AnimatedStatsSection } from "@/components/animated-stats-section"; +import Image from "next/image"; +import { TextTyper } from "@/components/text-typer"; // Constants const PY_20 = 20; @@ -64,26 +66,7 @@ export default async function Home() {
Loading statistics...
}> -
-
- -
{formatNumberValue(stats.orders.completed)}+
-
Orders Processed
-

Successfully delivered to customers around the world

-
-
- -
{formatNumberValue(stats.vendors.total)}+
-
Registered Vendors
-

Trusted merchants selling on our platform

-
-
- -
{formatCurrencyValue(stats.transactions.volume)}
-
Transaction Volume
-

Securely processed by our platform

-
-
+
@@ -187,7 +170,7 @@ export default async function Home() {

-

{new Date().getFullYear()} Ember. All rights reserved.

+

© {new Date().getFullYear()} Ember. All rights reserved.

diff --git a/components/animated-counter.tsx b/components/animated-counter.tsx new file mode 100644 index 0000000..c3ce52a --- /dev/null +++ b/components/animated-counter.tsx @@ -0,0 +1,71 @@ +"use client"; + +import { useState, useEffect, useRef } from "react"; + +export interface AnimatedCounterProps { + value: number; + duration?: number; + prefix?: string; + suffix?: string; + formatter?: (value: number) => string; +} + +export function AnimatedCounter({ + value, + duration = 2000, + prefix = "", + suffix = "", + formatter +}: AnimatedCounterProps) { + const [displayValue, setDisplayValue] = useState(0); + const startTimeRef = useRef(null); + const frameRef = useRef(null); + + // Easing function for smoother animation + const easeOutQuad = (t: number): number => t * (2 - t); + + useEffect(() => { + // Reset start time on new value + startTimeRef.current = null; + + // Cancel any ongoing animation + if (frameRef.current) { + cancelAnimationFrame(frameRef.current); + } + + // Animation function + const animate = (timestamp: number) => { + if (!startTimeRef.current) { + startTimeRef.current = timestamp; + } + + const elapsed = timestamp - startTimeRef.current; + const progress = Math.min(elapsed / duration, 1); + const easedProgress = easeOutQuad(progress); + + const nextValue = Math.floor(easedProgress * value); + setDisplayValue(nextValue); + + if (progress < 1) { + frameRef.current = requestAnimationFrame(animate); + } else { + setDisplayValue(value); + } + }; + + // Start the animation + frameRef.current = requestAnimationFrame(animate); + + return () => { + if (frameRef.current) { + cancelAnimationFrame(frameRef.current); + } + }; + }, [value, duration]); + + const formattedValue = formatter + ? formatter(displayValue) + : `${prefix}${displayValue.toLocaleString()}${suffix}`; + + return {formattedValue}; +} \ No newline at end of file diff --git a/components/animated-stats-section.tsx b/components/animated-stats-section.tsx new file mode 100644 index 0000000..fbabc76 --- /dev/null +++ b/components/animated-stats-section.tsx @@ -0,0 +1,105 @@ +"use client"; + +import { Package, Users, CreditCard } from "lucide-react"; +import { AnimatedCounter } from "./animated-counter"; +import { TextTyper } from "./text-typer"; +import { PlatformStats } from "@/lib/stats-service"; + +const formatCurrency = (value: number) => { + return new Intl.NumberFormat('en-GB', { + style: 'currency', + currency: 'GBP', + minimumFractionDigits: 0, + }).format(value); +}; + +interface AnimatedStatsProps { + stats: PlatformStats; +} + +export function AnimatedStatsSection({ stats }: AnimatedStatsProps) { + return ( +
+
+
+ +
+
+ +
+

+ +

+

+ +

+
+ +
+
+ +
+
+ +
+

+ +

+

+ +

+
+ +
+
+ +
+
+ +
+

+ +

+

+ +

+
+
+ ); +} \ No newline at end of file diff --git a/components/text-typer.tsx b/components/text-typer.tsx new file mode 100644 index 0000000..329fc46 --- /dev/null +++ b/components/text-typer.tsx @@ -0,0 +1,87 @@ +"use client"; + +import { useState, useEffect, useRef } from "react"; + +interface TextTyperProps { + text: string; + typingSpeed?: number; + delay?: number; + className?: string; + cursor?: boolean; + onComplete?: () => void; +} + +export function TextTyper({ + text, + typingSpeed = 50, + delay = 0, + className = "", + cursor = true, + onComplete +}: TextTyperProps) { + // Hide component if text is empty + if (!text) return null; + + const [displayedText, setDisplayedText] = useState(""); + const [cursorVisible, setCursorVisible] = useState(true); + const [isComplete, setIsComplete] = useState(false); + const timeoutRef = useRef(null); + const cursorIntervalRef = useRef(null); + + useEffect(() => { + // Clear any previous text + setDisplayedText(""); + setIsComplete(false); + + // Reset typing when text changes + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + + // Start typing after delay + const startTypingTimeout = setTimeout(() => { + let currentIndex = 0; + + const typeNextChar = () => { + if (currentIndex < text.length) { + setDisplayedText(text.substring(0, currentIndex + 1)); + currentIndex++; + timeoutRef.current = setTimeout(typeNextChar, typingSpeed); + } else { + setIsComplete(true); + if (onComplete) { + onComplete(); + } + } + }; + + typeNextChar(); + }, delay); + + // Store the start timeout + timeoutRef.current = startTypingTimeout; + + // Cursor blinking effect + if (cursor) { + cursorIntervalRef.current = setInterval(() => { + setCursorVisible(prev => !prev); + }, 500); + } + + return () => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + if (cursorIntervalRef.current) { + clearInterval(cursorIntervalRef.current); + } + }; + }, [text, typingSpeed, delay, cursor, onComplete]); + + return ( + + {displayedText} + {cursor && !isComplete && cursorVisible && |} + + ); +} \ No newline at end of file diff --git a/lib/stats-service.ts b/lib/stats-service.ts index d2dac03..0ebf82d 100644 --- a/lib/stats-service.ts +++ b/lib/stats-service.ts @@ -14,6 +14,8 @@ export interface PlatformStats { export async function fetchPlatformStats(): Promise { const BASE_API_URL = process.env.SERVER_API_URL || 'http://localhost:3001/api'; + console.log('Fetching platform stats from:', BASE_API_URL); + try { const response = await fetch(`${BASE_API_URL}/stats/platform`, { method: 'GET', @@ -27,7 +29,9 @@ export async function fetchPlatformStats(): Promise { throw new Error(`API error: ${response.status}`); } - return await response.json(); + const data = await response.json(); + console.log('Fetched stats:', data); + return data; } catch (error) { console.error('Error fetching platform stats:', error); // Return fallback data if API fails