diff --git a/src/app/page.jsx b/src/app/page.jsx
index de178e3..6249c44 100644
--- a/src/app/page.jsx
+++ b/src/app/page.jsx
@@ -5,6 +5,7 @@ import Features from '@/components/features'
import Team from '@/components/team'
import Footer from '@/components/footer'
import Updates from '@/components/updates' // Import Blog component
+import HomeTickers from '@/components/HomeTickers'
export default function Home() {
return (
@@ -13,6 +14,7 @@ export default function Home() {
+
diff --git a/src/components/HomeTickers.jsx b/src/components/HomeTickers.jsx
new file mode 100644
index 0000000..70c807f
--- /dev/null
+++ b/src/components/HomeTickers.jsx
@@ -0,0 +1,207 @@
+"use client";
+
+import { useEffect, useState, useRef } from "react";
+import { Skeleton } from "@/components/ui/skeleton";
+import { TrendingUp, PhoneCall, CalendarDays } from "lucide-react";
+import { format } from "date-fns";
+
+function formatNumber(num) {
+ return num?.toLocaleString() ?? '0';
+}
+
+const tickerStyles = {
+ blue: 'bg-blue-600/20 border-blue-600/30 text-blue-400 group-hover:bg-blue-600/30',
+ green: 'bg-green-600/20 border-green-600/30 text-green-400 group-hover:bg-green-600/30',
+ purple: 'bg-purple-600/20 border-purple-600/30 text-purple-400 group-hover:bg-purple-600/30',
+};
+
+function useAnimatedNumber(target, duration = 1200, shouldAnimate = true) {
+ const [display, setDisplay] = useState(0);
+ const rafRef = useRef();
+
+ useEffect(() => {
+ if (!shouldAnimate || typeof target !== "number") {
+ setDisplay(target || 0);
+ return;
+ }
+ let start = null;
+ let from = 0;
+ let to = target;
+ if (from === to) {
+ setDisplay(to);
+ return;
+ }
+
+ function tick(ts) {
+ if (!start) start = ts;
+ const progress = Math.min((ts - start) / duration, 1);
+ const value = Math.round(from + (to - from) * progress);
+ setDisplay(value);
+ if (progress < 1) {
+ rafRef.current = requestAnimationFrame(tick);
+ }
+ }
+ rafRef.current = requestAnimationFrame(tick);
+ return () => rafRef.current && cancelAnimationFrame(rafRef.current);
+ // eslint-disable-next-line
+ }, [target, shouldAnimate]);
+ return display;
+}
+
+function AnimatedNumber({ value, animate, ...props }) {
+ const animated = useAnimatedNumber(typeof value === "number" ? value : 0, 1200, animate);
+ return {animated.toLocaleString()};
+}
+
+function formatMonthLabel(monthStr) {
+ // monthStr is like "2025-07"
+ const [year, month] = monthStr.split("-");
+ if (!year || !month) return monthStr;
+ const date = new Date(Number(year), Number(month) - 1, 1);
+ return format(date, "MMMM yyyy");
+}
+
+function formatRecordDate(dateStr) {
+ // dateStr is like "2024-10-28"
+ if (!dateStr) return "";
+ const [year, month, day] = dateStr.split("-");
+ if (!year || !month || !day) return dateStr;
+ const date = new Date(Number(year), Number(month) - 1, Number(day));
+ return format(date, "MMMM do, yyyy");
+}
+
+function TickerCard({ color, icon, title, value, subtitle, loading, animate, children }) {
+ return (
+
+ {/* Animated background gradient */}
+
+
+
+ {icon}
+
+
{title}
+ {loading ? (
+
+ ) : (
+ value !== undefined && value !== "" && value !== null ? (
+
+ ) : null
+ )}
+ {subtitle &&
{subtitle}}
+ {children}
+
+ {/* Hover glow effect */}
+
+
+ );
+}
+
+export default function HomeTickers() {
+ const [stats, setStats] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [animateNumbers, setAnimateNumbers] = useState(false);
+ const sectionRef = useRef();
+
+ useEffect(() => {
+ async function fetchStats() {
+ setLoading(true);
+ try {
+ const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/system/records`);
+ const data = await res.json();
+ setStats(data.records);
+ } catch (err) {
+ setStats(null);
+ } finally {
+ setLoading(false);
+ }
+ }
+ fetchStats();
+ }, []);
+
+ useEffect(() => {
+ const ref = sectionRef.current;
+ if (!ref) return;
+ let observer;
+ if (typeof window !== "undefined" && "IntersectionObserver" in window) {
+ observer = new window.IntersectionObserver(
+ ([entry]) => {
+ if (entry.isIntersecting) {
+ setAnimateNumbers(true);
+ }
+ },
+ { threshold: 0.4 }
+ );
+ observer.observe(ref);
+ }
+ return () => observer && observer.disconnect();
+ }, []);
+
+ const monthlyStats = stats
+ ? Object.entries(stats)
+ .filter(([k, v]) => k.startsWith("monthly_total_") && v > 0)
+ .sort(([a], [b]) => a.localeCompare(b))
+ : [];
+
+ // Get current month in YYYY-MM format
+ const now = new Date();
+ const currentMonthKey = `monthly_total_${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
+ const currentMonthCount = stats?.[currentMonthKey];
+
+ return (
+
+
+
+ Network Activity
+
+
+ Real-time call statistics from the LiteNet PBX.
+
+
+
+ }
+ title="This Month's Calls"
+ value={currentMonthCount}
+ subtitle={format(now, "MMMM yyyy")}
+ loading={loading}
+ animate={animateNumbers}
+ />
+ }
+ title="Total Calls"
+ value={stats?.total_calls_ever_placed}
+ subtitle="Since launch"
+ loading={loading}
+ animate={animateNumbers}
+ />
+ }
+ title="Single-Day Call Record"
+ value={stats?.record_calls?.count}
+ subtitle={
+ stats?.record_calls?.date
+ ? `Record set on ${formatRecordDate(stats.record_calls.date)}`
+ : ""
+ }
+ loading={loading}
+ animate={animateNumbers}
+ />
+
+
+
+ );
+}