diff --git a/apps/website/app/api/github-stars/route.ts b/apps/website/app/api/github-stars/route.ts new file mode 100644 index 0000000..57c7a4d --- /dev/null +++ b/apps/website/app/api/github-stars/route.ts @@ -0,0 +1,77 @@ +import { NextResponse } from "next/server"; + +// Cache the result for 5 minutes to avoid rate limiting +let cachedStars: { count: number; timestamp: number } | null = null; +const CACHE_DURATION = 5 * 60 * 1000; // 5 minutes in milliseconds + +export async function GET(request: Request) { + const { searchParams } = new URL(request.url); + const owner = searchParams.get("owner"); + const repo = searchParams.get("repo"); + + if (!owner || !repo) { + return NextResponse.json( + { error: "Owner and repo parameters are required" }, + { status: 400 }, + ); + } + + // Check if we have a valid cached result + if ( + cachedStars && + Date.now() - cachedStars.timestamp < CACHE_DURATION + ) { + return NextResponse.json( + { stargazers_count: cachedStars.count }, + { + headers: { + "Cache-Control": "public, s-maxage=300, stale-while-revalidate=600", + }, + }, + ); + } + + try { + const response = await fetch( + `https://api.github.com/repos/${owner}/${repo}`, + { + headers: { + Accept: "application/vnd.github.v3+json", + "User-Agent": "Dokploy-Website", + }, + }, + ); + + if (!response.ok) { + return NextResponse.json( + { error: "Failed to fetch repository data" }, + { status: response.status }, + ); + } + + const data = await response.json(); + const starCount = data.stargazers_count; + + // Cache the result + cachedStars = { + count: starCount, + timestamp: Date.now(), + }; + + return NextResponse.json( + { stargazers_count: starCount }, + { + headers: { + "Cache-Control": "public, s-maxage=300, stale-while-revalidate=600", + }, + }, + ); + } catch (error) { + console.error("Error fetching GitHub stars:", error); + return NextResponse.json( + { error: "Internal server error" }, + { status: 500 }, + ); + } +} + diff --git a/apps/website/components/GithubStars.tsx b/apps/website/components/GithubStars.tsx index 1fc9579..f8b8bd9 100644 --- a/apps/website/components/GithubStars.tsx +++ b/apps/website/components/GithubStars.tsx @@ -1,6 +1,7 @@ "use client"; import Link from "next/link"; +import { useEffect, useState } from "react"; import { cn } from "@/lib/utils"; type GithubStarsProps = { @@ -10,17 +11,73 @@ type GithubStarsProps = { count?: string; }; +// Function to format star count (e.g., 26400 -> "26.4k") +function formatStarCount(count: number): string { + if (count >= 1000000) { + return `${(count / 1000000).toFixed(1)}M`; + } + if (count >= 1000) { + return `${(count / 1000).toFixed(1)}k`; + } + return count.toString(); +} + +// Extract owner and repo from GitHub URL +function extractRepoInfo(url: string): { owner: string; repo: string } | null { + try { + const match = url.match(/github\.com\/([^\/]+)\/([^\/]+)/); + if (match) { + return { owner: match[1], repo: match[2].replace(/\.git$/, "") }; + } + } catch (error) { + console.error("Error extracting repo info:", error); + } + return null; +} + export function GithubStars({ className, repoUrl = "https://github.com/dokploy/dokploy", label = "GitHub Stars", - count = "26.4k", + count: defaultCount = "26.4k", }: GithubStarsProps) { + const [starCount, setStarCount] = useState(defaultCount); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + const fetchStarCount = async () => { + const repoInfo = extractRepoInfo(repoUrl); + if (!repoInfo) { + setIsLoading(false); + return; + } + + try { + const response = await fetch( + `/api/github-stars?owner=${encodeURIComponent(repoInfo.owner)}&repo=${encodeURIComponent(repoInfo.repo)}`, + ); + + if (response.ok) { + const data = await response.json(); + const formattedCount = formatStarCount(data.stargazers_count); + setStarCount(formattedCount); + } + } catch (error) { + console.error("Error fetching GitHub stars:", error); + // Keep default count on error + } finally { + setIsLoading(false); + } + }; + + fetchStarCount(); + }, [repoUrl]); + return ( Stars - {count} + + {isLoading ? "..." : starCount} + {/* subtle ring on hover */} diff --git a/apps/website/components/stats.tsx b/apps/website/components/stats.tsx index 9255416..3d38010 100644 --- a/apps/website/components/stats.tsx +++ b/apps/website/components/stats.tsx @@ -1,24 +1,48 @@ +"use client"; + import { HandCoins, Users } from "lucide-react"; -import React from "react"; +import React, { useEffect, useState } from "react"; import { useId } from "react"; import NumberTicker from "./ui/number-ticker"; const statsValues = { githubStars: 26000, - dockerDownloads: 3500000, + dockerDownloads: 4000000, contributors: 200, sponsors: 50, }; export function StatsSection() { + const [githubStars, setGithubStars] = useState(statsValues.githubStars); + + useEffect(() => { + const fetchGitHubStars = async () => { + try { + const response = await fetch( + "/api/github-stars?owner=dokploy&repo=dokploy", + ); + + if (response.ok) { + const data = await response.json(); + setGithubStars(data.stargazers_count); + } + } catch (error) { + console.error("Error fetching GitHub stars:", error); + // Keep default value on error + } + }; + + fetchGitHubStars(); + }, []); + return (

- Stats You Didn’t Ask For (But Secretly Love to See) + Stats You Didn't Ask For (But Secretly Love to See)

- Just a few numbers to show we’re not *completely* making this up. + Just a few numbers to show we're not *completely* making this up. Turns out, Dokploy has actually helped a few people—who knew?

@@ -35,9 +59,13 @@ export function StatsSection() { {feature.icon}

- {feature.description} + {typeof feature.description === "function" + ? feature.description(githubStars) + : feature.description}

- {feature.component} + {typeof feature.component === "function" + ? feature.component(githubStars) + : feature.component}
))} @@ -48,15 +76,16 @@ export function StatsSection() { const grid = [ { title: "GitHub Stars", - description: `With over ${(statsValues.githubStars / 1000).toFixed(1)}k stars on GitHub, Dokploy is trusted by developers worldwide. Explore our repositories and join our community!`, + description: (stars: number) => + `With over ${(stars / 1000).toFixed(1)}k stars on GitHub, Dokploy is trusted by developers worldwide. Explore our repositories and join our community!`, icon: ( ), - component: ( + component: (stars: number) => (

- + + +

), },