import { useState, useEffect, useCallback } from "react" import { BarChart3, ShieldAlert, ShieldCheck, Globe, RefreshCw, Loader2, AlertTriangle, Calendar, MapPin, } from "lucide-react" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table" import { CaptchaAnalyticsSummary, recaptchaLocations } from "../../config" // Helper to format location IDs to readable names function formatLocation(locationId: string): string { const loc = recaptchaLocations.find((l) => l.id === locationId) return loc ? loc.name : locationId } // Helper to format dates function formatDate(dateStr: string): string { const d = new Date(dateStr) return d.toLocaleDateString(undefined, { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit", }) } function formatShortDate(dateStr: string): string { const d = new Date(dateStr) return d.toLocaleDateString(undefined, { month: "short", day: "numeric" }) } export function AnalyticsTab() { const [analytics, setAnalytics] = useState(null) const [loading, setLoading] = useState(true) const [period, setPeriod] = useState<"7d" | "30d">("30d") const loadAnalytics = useCallback(async () => { setLoading(true) try { const apiUrl = window.swiftCommerceData?.apiUrl || "/wp-json/swift-commerce/v1" const response = await fetch( `${apiUrl}/recaptcha/analytics?period=${period}`, { headers: { "X-WP-Nonce": window.swiftCommerceData?.restNonce || "", }, } ) if (response.ok) { const result = await response.json() if (result.success && result.data) { setAnalytics(result.data) } } } catch (error) { console.error("Failed to load CAPTCHA analytics:", error) } finally { setLoading(false) } }, [period]) useEffect(() => { loadAnalytics() }, [loadAnalytics]) if (loading) { return (
) } if (!analytics) { return (

No analytics data yet

Analytics will populate once CAPTCHA verifications start being logged.

) } const filteredDays = period === "7d" ? analytics.byDay.slice(-7) : analytics.byDay return (
{/* Header with controls */}

CAPTCHA Analytics

Monitor verification attempts and blocked threats.

{/* Summary Cards */}
{/* Daily Trend */} {filteredDays.length > 0 && ( Daily Trend Passed vs failed verifications over time.
{filteredDays.map((day) => { const total = day.passed + day.failed const passedPct = total > 0 ? (day.passed / total) * 100 : 100 const failedPct = total > 0 ? (day.failed / total) * 100 : 0 return (
{formatShortDate(day.date)}
{passedPct > 0 && (
)} {failedPct > 0 && (
)}
{day.passed} / {day.failed}
) })}
Passed
Failed
)}
{/* By Location */} By Location Verification results per form location. {Object.keys(analytics.byLocation).length > 0 ? (
{Object.entries(analytics.byLocation).map( ([locationId, data]) => { const total = data.passed + data.failed const failPct = total > 0 ? (data.failed / total) * 100 : 0 return (
{formatLocation(locationId)} {data.passed} passed / {data.failed} failed
0 ? 2 : 0)}%`, }} />
) } )}
) : (

No location data available yet.

)} {/* Top Blocked IPs */} Top Blocked IPs IP addresses with the most failed attempts. {analytics.topBlockedIPs.length > 0 ? (
{analytics.topBlockedIPs.map((entry, idx) => (
{idx + 1}. {entry.ip}
{entry.count} {entry.count === 1 ? "failure" : "failures"}
))}
) : (

No blocked IPs recorded yet.

)}
{/* Recent Failures */} {analytics.recentFailures.length > 0 && ( Recent Failed Attempts Latest CAPTCHA verification failures.
Time Location IP Address Provider Reason {analytics.recentFailures.map((entry) => ( {formatDate(entry.created_at)} {formatLocation(entry.location)} {entry.ip_address} {entry.provider || "google-recaptcha"} {entry.fail_reason || "Verification failed"} ))}
)}
) } // --- Sub-components --- function StatCard({ title, value, icon: Icon, description, }: { title: string value: string | number icon: React.ComponentType<{ className?: string }> description: string }) { return ( {title}
{value}

{description}

) } function PeriodSelector({ period, onChange, }: { period: "7d" | "30d" onChange: (p: "7d" | "30d") => void }) { return (
) }