diff --git a/backend/analytics.php b/backend/analytics.php new file mode 100644 index 0000000..47547d6 --- /dev/null +++ b/backend/analytics.php @@ -0,0 +1,107 @@ + 0, + 'todayViews' => 0, + 'lastDate' => date('Y-m-d'), + 'lastPing' => time(), + 'visitors' => [], + 'devices' => ['Desktop' => 0, 'Mobile' => 0] +]; + +// Load existing data +if (file_exists($dataFile)) { + $content = @file_get_contents($dataFile); + if ($content) { + $loadedData = @json_decode($content, true); + if (is_array($loadedData)) { + $data = array_merge($data, $loadedData); + } + } +} + +// Reset daily stats if day changed +$today = date('Y-m-d'); +if ($data['lastDate'] !== $today) { + $data['todayViews'] = 0; + $data['lastDate'] = $today; + $data['visitors'] = []; +} + +// Localhost / Development check +$referer = $_SERVER['HTTP_REFERER'] ?? ''; +$ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0'; + +$isLocalhost = ( + strpos($origin, 'localhost') !== false || + strpos($referer, 'localhost') !== false || + $ip === '127.0.0.1' || + $ip === '::1' +); + +$lastTrackTime = $_SESSION['last_track_time'] ?? 0; +$currentTime = time(); +$isSpam = ($currentTime - $lastTrackTime) < 30; + +// Track view +if (isset($_GET['track']) && $_GET['track'] === 'true' && !$isLocalhost && !$isSpam) { + $_SESSION['last_track_time'] = $currentTime; + + $data['totalViews']++; + $data['todayViews']++; + $data['lastPing'] = $currentTime; + + $ua = $_SERVER['HTTP_USER_AGENT'] ?? ''; + $isMobile = preg_match('/mobile|android|iphone|ipad|tablet/i', $ua); + $deviceType = $isMobile ? 'Mobile' : 'Desktop'; + $data['devices'][$deviceType] = ($data['devices'][$deviceType] ?? 0) + 1; + + $visitorHash = md5($ip . $today); + if (!in_array($visitorHash, $data['visitors'])) { + $data['visitors'][] = $visitorHash; + } + + @file_put_contents($dataFile, json_encode($data), LOCK_EX); +} + +// Calculate server execution time (ms) +$responseTime = round((microtime(true) - $startTime) * 1000, 2); + +echo json_encode([ + 'totalViews' => $data['totalViews'], + 'todayViews' => $data['todayViews'], + 'uniqueVisitors' => count($data['visitors']), + 'devices' => $data['devices'], + 'lastPing' => $data['lastPing'] ?? time(), + 'latencyMs' => $responseTime +]); \ No newline at end of file diff --git a/src/components/AnalyticsWidget.tsx b/src/components/AnalyticsWidget.tsx new file mode 100644 index 0000000..47fb763 --- /dev/null +++ b/src/components/AnalyticsWidget.tsx @@ -0,0 +1,55 @@ +"use client"; + +import { useEffect, useState } from "react"; + +export interface AnalyticsData { + totalViews: number; + todayViews: number; + uniqueVisitors: number; + devices: { + Desktop: number; + Mobile: number; + }; +} + +export default function AnalyticsWidget() { + const [stats, setStats] = useState(null); + + useEffect(() => { + fetch("https://andre-kempf.com/backend/analytics.php?track=true") + .then((res) => res.json()) + .then((data) => { + if (!data.error) setStats(data); + }) + .catch(() => null); + }, []); + + if (!stats) { + return ( +
+ + $ + loading_stats... +
+ ); + } + + return ( +
+ + $ + sys.metrics + [ + + today: + {stats.todayViews} + + | + + total: + {stats.totalViews} + + ] +
+ ); +} diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx index 1dbf520..570bde8 100644 --- a/src/components/Footer.tsx +++ b/src/components/Footer.tsx @@ -1,18 +1,31 @@ +"use client"; + import Link from "next/link"; +import AnalyticsWidget from "./AnalyticsWidget"; export default function Footer() { return ( -