From bb083fa62fbbc89ed6da68ae2301ca894ad8eebd Mon Sep 17 00:00:00 2001 From: AKHIL Date: Wed, 22 Jul 2026 15:28:33 +0530 Subject: [PATCH] UI --- apps/web/src/components/github-stars.tsx | 82 ++++++++++++++++++++++++ apps/web/src/components/header.tsx | 5 +- 2 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 apps/web/src/components/github-stars.tsx diff --git a/apps/web/src/components/github-stars.tsx b/apps/web/src/components/github-stars.tsx new file mode 100644 index 00000000..ef4a62bd --- /dev/null +++ b/apps/web/src/components/github-stars.tsx @@ -0,0 +1,82 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { Github, Star } from "lucide-react"; +import { cn } from "@/lib/utils"; + +const REPO = "mydevtools-tech/mydevtools"; +const REPO_URL = `https://github.com/${REPO}`; +const CACHE_KEY = "mdt-gh-stars"; +const CACHE_TTL = 60 * 60 * 1000; // 1h — GitHub's unauth API is 60 req/hr/IP. + +function formatStars(n: number): string { + if (n >= 1000) return `${(n / 1000).toFixed(n >= 10000 ? 0 : 1)}k`; + return String(n); +} + +/** Header pill linking to the repo with a live stargazer count (client-fetched, localStorage-cached). */ +export function GithubStars({ className }: { className?: string }) { + const [stars, setStars] = useState(null); + + useEffect(() => { + try { + const cached = localStorage.getItem(CACHE_KEY); + if (cached) { + const { count, at } = JSON.parse(cached) as { count: number; at: number }; + setStars(count); + if (Date.now() - at < CACHE_TTL) return; // fresh — skip refetch + } + } catch { + /* ignore bad cache */ + } + + let cancelled = false; + fetch(`https://api.github.com/repos/${REPO}`, { + headers: { Accept: "application/vnd.github+json" }, + }) + .then((r) => (r.ok ? r.json() : Promise.reject(r.status))) + .then((d: { stargazers_count?: number }) => { + if (cancelled || typeof d.stargazers_count !== "number") return; + setStars(d.stargazers_count); + try { + localStorage.setItem( + CACHE_KEY, + JSON.stringify({ count: d.stargazers_count, at: Date.now() }) + ); + } catch { + /* ignore quota */ + } + }) + .catch(() => { + /* keep icon-only fallback */ + }); + return () => { + cancelled = true; + }; + }, []); + + return ( + + + Star + + + {stars != null ? formatStars(stars) : "—"} + + + ); +} diff --git a/apps/web/src/components/header.tsx b/apps/web/src/components/header.tsx index 71eff666..474483f7 100644 --- a/apps/web/src/components/header.tsx +++ b/apps/web/src/components/header.tsx @@ -7,6 +7,7 @@ import { ModeToggle } from "./modeToggle"; import useAuth from "@/utils/useAuth"; import { Logo } from "./logo"; import { AnnouncementBanner } from "./announcement-banner"; +import { GithubStars } from "./github-stars"; import { motion, AnimatePresence } from "framer-motion"; import { cn } from "@/lib/utils"; @@ -170,6 +171,7 @@ export function Header({ showThemeToggle = true }: HeaderProps) { {/* Desktop Actions */}
+ {showThemeToggle ? : null} + setIsMenuOpen(false)}