Graphical PDS migrator for AT Protocol
1import { useEffect, useState } from "preact/hooks";
2import * as Icon from "npm:preact-feather";
3
4/**
5 * The GitHub repository.
6 * @type {GitHubRepo}
7 */
8interface GitHubRepo {
9 stargazers_count: number;
10}
11
12/**
13 * The social links component.
14 * @returns The social links component
15 * @component
16 */
17export default function SocialLinks() {
18 const [starCount, setStarCount] = useState<number | null>(null);
19
20 useEffect(() => {
21 const CACHE_KEY = "github_stars";
22 const CACHE_DURATION = 15 * 60 * 1000; // 15 minutes in milliseconds
23
24 const fetchRepoInfo = async () => {
25 try {
26 const response = await fetch(
27 "https://api.github.com/repos/knotbin/airport",
28 );
29 const data: GitHubRepo = await response.json();
30 const cacheData = {
31 count: data.stargazers_count,
32 timestamp: Date.now(),
33 };
34 localStorage.setItem(CACHE_KEY, JSON.stringify(cacheData));
35 setStarCount(data.stargazers_count);
36 } catch (error) {
37 console.error("Failed to fetch GitHub repo info:", error);
38 }
39 };
40
41 const getCachedStars = () => {
42 const cached = localStorage.getItem(CACHE_KEY);
43 if (cached) {
44 const { count, timestamp } = JSON.parse(cached);
45 if (Date.now() - timestamp < CACHE_DURATION) {
46 setStarCount(count);
47 return true;
48 }
49 }
50 return false;
51 };
52
53 if (!getCachedStars()) {
54 fetchRepoInfo();
55 }
56 }, []);
57
58 const formatStarCount = (count: number | null) => {
59 if (count === null) return "...";
60 if (count >= 1000) {
61 return `${(count / 1000).toFixed(1)}k`;
62 }
63 return count.toString();
64 };
65
66 return (
67 <div class="mt-8 flex justify-center items-center gap-6">
68 <a
69 href="https://bsky.app/profile/knotbin.com"
70 class="text-gray-600 hover:text-blue-500 dark:text-gray-400 dark:hover:text-blue-400 transition-colors"
71 target="_blank"
72 rel="noopener noreferrer"
73 >
74 <svg
75 class="w-6 h-6"
76 viewBox="-20 -20 296 266"
77 fill="none"
78 stroke="currentColor"
79 stroke-width="25"
80 stroke-linejoin="round"
81 xmlns="http://www.w3.org/2000/svg"
82 >
83 <path d="M55.491 15.172c29.35 22.035 60.917 66.712 72.509 90.686 11.592-23.974 43.159-68.651 72.509-90.686C221.686-.727 256-13.028 256 26.116c0 7.818-4.482 65.674-7.111 75.068-9.138 32.654-42.436 40.983-72.057 35.942 51.775 8.812 64.946 38 36.501 67.187-54.021 55.433-77.644-13.908-83.696-31.676-1.11-3.257-1.63-4.78-1.637-3.485-.008-1.296-.527.228-1.637 3.485-6.052 17.768-29.675 87.11-83.696 31.676-28.445-29.187-15.274-58.375 36.5-67.187-29.62 5.041-62.918-3.288-72.056-35.942C4.482 91.79 0 33.934 0 26.116 0-13.028 34.314-.727 55.491 15.172Z" />
84 </svg>
85 </a>
86 <a
87 href="https://ko-fi.com/knotbin"
88 class="text-gray-600 hover:text-red-500 dark:text-gray-400 dark:hover:text-red-400 transition-colors"
89 target="_blank"
90 rel="noopener noreferrer"
91 >
92 <Icon.Coffee class="w-6 h-6" />
93 </a>
94 <a
95 href="https://github.com/knotbin/airport"
96 class="text-gray-600 hover:text-purple-500 dark:text-gray-400 dark:hover:text-purple-400 transition-colors flex items-center gap-1"
97 target="_blank"
98 rel="noopener noreferrer"
99 >
100 <Icon.Github class="w-6 h-6" />
101 <span class="text-sm font-mono">{formatStarCount(starCount)}</span>
102 </a>
103 </div>
104 );
105}