https://altly.madebydanny.uk
at main 65 lines 1.7 kB view raw
1import { useEffect, useState } from "react"; 2import { StatsCard } from "@/components/StatsCard"; 3import { supabase } from "@/integrations/supabase/client"; 4 5export const Stats = () => { 6 const [stats, setStats] = useState({ 7 imagesUploaded: 0, 8 happyUsers: 0, 9 avgResponseMs: 0, 10 }); 11 12 useEffect(() => { 13 const fetchStats = async () => { 14 // Use secure stats function that only exposes aggregates 15 const { data, error } = await supabase.rpc('get_alt_text_stats'); 16 17 if (error) { 18 console.error('Error fetching stats:', error); 19 return; 20 } 21 22 if (data && data.length > 0) { 23 const statsData = data[0]; 24 setStats({ 25 imagesUploaded: Number(statsData.total_images) || 0, 26 happyUsers: Number(statsData.happy_users) || 0, 27 avgResponseMs: Number(statsData.avg_response_ms) || 0, 28 }); 29 } 30 }; 31 32 fetchStats(); 33 34 // Subscribe to real-time updates 35 const channel = supabase 36 .channel('stats-updates') 37 .on( 38 'postgres_changes', 39 { 40 event: 'INSERT', 41 schema: 'public', 42 table: 'alt_text_generations' 43 }, 44 () => { 45 fetchStats(); 46 } 47 ) 48 .subscribe(); 49 50 return () => { 51 supabase.removeChannel(channel); 52 }; 53 }, []); 54 55 return ( 56 <div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-12 max-w-4xl mx-auto"> 57 <StatsCard label="Images Uploaded" value={stats.imagesUploaded} /> 58 <StatsCard label="Happy Users" value={stats.happyUsers} /> 59 <StatsCard 60 label="Avg Response (ms)" 61 value={stats.avgResponseMs.toLocaleString()} 62 /> 63 </div> 64 ); 65};