()
| 5 | import { Skeleton } from "@/components/ui/skeleton"; |
| 6 | |
| 7 | export function GitHubStatsSection() { |
| 8 | const [stats, setStats] = useState<GitHubStats | null>(null); |
| 9 | const [loading, setLoading] = useState(true); |
| 10 | |
| 11 | useEffect(() => { |
| 12 | async function fetchStats() { |
| 13 | try { |
| 14 | const githubStats = await getGitHubStats(); |
| 15 | setStats(githubStats); |
| 16 | } catch (error) { |
| 17 | console.error("Failed to fetch GitHub stats:", error); |
| 18 | } finally { |
| 19 | setLoading(false); |
| 20 | } |
| 21 | } |
| 22 | |
| 23 | fetchStats(); |
| 24 | }, []); |
| 25 | |
| 26 | const formatNumber = (num: number): string => { |
| 27 | if (num >= 1000) { |
| 28 | return `${(num / 1000).toFixed(1)}k`; |
| 29 | } |
| 30 | return num.toString(); |
| 31 | }; |
| 32 | |
| 33 | return ( |
| 34 | <section className="w-full max-w-4xl space-y-6"> |
| 35 | <h2 className="text-xl font-semibold text-center">GitHub Stats</h2> |
| 36 | <div className="grid grid-cols-2 md:grid-cols-4 gap-4"> |
| 37 | <div className="border rounded-lg p-4 text-center"> |
| 38 | {loading ? ( |
| 39 | <div className="space-y-2"> |
| 40 | <Skeleton className="h-8 w-16 mx-auto" /> |
| 41 | <Skeleton className="h-4 w-20 mx-auto" /> |
| 42 | </div> |
| 43 | ) : ( |
| 44 | <> |
| 45 | <p className="text-2xl font-semibold"> |
| 46 | {formatNumber(stats?.stargazers || 0)} |
| 47 | </p> |
| 48 | <p className="text-muted-foreground text-sm">Stargazers</p> |
| 49 | </> |
| 50 | )} |
| 51 | </div> |
| 52 | <div className="border rounded-lg p-4 text-center"> |
| 53 | {loading ? ( |
| 54 | <div className="space-y-2"> |
| 55 | <Skeleton className="h-8 w-16 mx-auto" /> |
| 56 | <Skeleton className="h-4 w-12 mx-auto" /> |
| 57 | </div> |
| 58 | ) : ( |
| 59 | <> |
| 60 | <p className="text-2xl font-semibold"> |
| 61 | {formatNumber(stats?.forks || 0)} |
| 62 | </p> |
| 63 | <p className="text-muted-foreground text-sm">Forks</p> |
| 64 | </> |
nothing calls this directly
no test coverage detected