Skip to content

Commit bb083fa

Browse files
committed
UI
1 parent 9da0aac commit bb083fa

2 files changed

Lines changed: 86 additions & 1 deletion

File tree

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
"use client";
2+
3+
import { useEffect, useState } from "react";
4+
import { Github, Star } from "lucide-react";
5+
import { cn } from "@/lib/utils";
6+
7+
const REPO = "mydevtools-tech/mydevtools";
8+
const REPO_URL = `https://github.com/${REPO}`;
9+
const CACHE_KEY = "mdt-gh-stars";
10+
const CACHE_TTL = 60 * 60 * 1000; // 1h — GitHub's unauth API is 60 req/hr/IP.
11+
12+
function formatStars(n: number): string {
13+
if (n >= 1000) return `${(n / 1000).toFixed(n >= 10000 ? 0 : 1)}k`;
14+
return String(n);
15+
}
16+
17+
/** Header pill linking to the repo with a live stargazer count (client-fetched, localStorage-cached). */
18+
export function GithubStars({ className }: { className?: string }) {
19+
const [stars, setStars] = useState<number | null>(null);
20+
21+
useEffect(() => {
22+
try {
23+
const cached = localStorage.getItem(CACHE_KEY);
24+
if (cached) {
25+
const { count, at } = JSON.parse(cached) as { count: number; at: number };
26+
setStars(count);
27+
if (Date.now() - at < CACHE_TTL) return; // fresh — skip refetch
28+
}
29+
} catch {
30+
/* ignore bad cache */
31+
}
32+
33+
let cancelled = false;
34+
fetch(`https://api.github.com/repos/${REPO}`, {
35+
headers: { Accept: "application/vnd.github+json" },
36+
})
37+
.then((r) => (r.ok ? r.json() : Promise.reject(r.status)))
38+
.then((d: { stargazers_count?: number }) => {
39+
if (cancelled || typeof d.stargazers_count !== "number") return;
40+
setStars(d.stargazers_count);
41+
try {
42+
localStorage.setItem(
43+
CACHE_KEY,
44+
JSON.stringify({ count: d.stargazers_count, at: Date.now() })
45+
);
46+
} catch {
47+
/* ignore quota */
48+
}
49+
})
50+
.catch(() => {
51+
/* keep icon-only fallback */
52+
});
53+
return () => {
54+
cancelled = true;
55+
};
56+
}, []);
57+
58+
return (
59+
<a
60+
href={REPO_URL}
61+
target="_blank"
62+
rel="noopener noreferrer"
63+
aria-label={`Star ${REPO} on GitHub${stars != null ? ` — ${stars} stars` : ""}`}
64+
className={cn(
65+
"group inline-flex h-10 items-center gap-2 rounded-full border border-border/60 bg-background/40 px-3.5 text-sm font-medium",
66+
"backdrop-blur-sm transition-all duration-200",
67+
"hover:-translate-y-0.5 hover:border-border hover:bg-muted/60 hover:shadow-[0_10px_30px_-12px_rgba(0,0,0,0.5)]",
68+
className
69+
)}
70+
>
71+
<Github className="h-[18px] w-[18px] shrink-0" aria-hidden />
72+
<span className="hidden sm:inline">Star</span>
73+
<span className="flex items-center gap-1 tabular-nums text-muted-foreground group-hover:text-foreground">
74+
<Star
75+
className="h-3.5 w-3.5 shrink-0 fill-amber-400 text-amber-400 transition-transform duration-200 group-hover:scale-110"
76+
aria-hidden
77+
/>
78+
{stars != null ? formatStars(stars) : "—"}
79+
</span>
80+
</a>
81+
);
82+
}

apps/web/src/components/header.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { ModeToggle } from "./modeToggle";
77
import useAuth from "@/utils/useAuth";
88
import { Logo } from "./logo";
99
import { AnnouncementBanner } from "./announcement-banner";
10+
import { GithubStars } from "./github-stars";
1011
import { motion, AnimatePresence } from "framer-motion";
1112
import { cn } from "@/lib/utils";
1213

@@ -170,6 +171,7 @@ export function Header({ showThemeToggle = true }: HeaderProps) {
170171

171172
{/* Desktop Actions */}
172173
<div className="hidden md:flex items-center gap-3">
174+
<GithubStars />
173175
{showThemeToggle ? <ModeToggle /> : null}
174176
<Link
175177
href={cta.href}
@@ -237,8 +239,9 @@ export function Header({ showThemeToggle = true }: HeaderProps) {
237239
variants={menuItemVariants}
238240
initial="closed"
239241
animate="open"
240-
className="pt-2 border-t border-border/40 mt-2"
242+
className="pt-2 border-t border-border/40 mt-2 space-y-2"
241243
>
244+
<GithubStars className="flex h-12 w-full justify-center rounded-xl text-base" />
242245
<Link
243246
href={cta.href}
244247
onClick={() => setIsMenuOpen(false)}

0 commit comments

Comments
 (0)