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("https://api.github.com/repos/knotbin/airport");
27 const data: GitHubRepo = await response.json();
28 const cacheData = {
29 count: data.stargazers_count,
30 timestamp: Date.now()
31 };
32 localStorage.setItem(CACHE_KEY, JSON.stringify(cacheData));
33 setStarCount(data.stargazers_count);
34 } catch (error) {
35 console.error("Failed to fetch GitHub repo info:", error);
36 }
37 };
38
39 const getCachedStars = () => {
40 const cached = localStorage.getItem(CACHE_KEY);
41 if (cached) {
42 const { count, timestamp } = JSON.parse(cached);
43 if (Date.now() - timestamp < CACHE_DURATION) {
44 setStarCount(count);
45 return true;
46 }
47 }
48 return false;
49 };
50
51 if (!getCachedStars()) {
52 fetchRepoInfo();
53 }
54 }, []);
55
56 const formatStarCount = (count: number | null) => {
57 if (count === null) return "...";
58 if (count >= 1000) {
59 return `${(count / 1000).toFixed(1)}k`;
60 }
61 return count.toString();
62 };
63
64 return (
65 <div class="mt-8 flex justify-center items-center gap-6">
66 <a
67 href="https://bsky.app/profile/knotbin.com"
68 class="text-gray-600 hover:text-blue-500 dark:text-gray-400 dark:hover:text-blue-400 transition-colors"
69 target="_blank"
70 rel="noopener noreferrer"
71 >
72 <svg
73 class="w-6 h-6"
74 viewBox="-20 -20 296 266"
75 fill="none"
76 stroke="currentColor"
77 stroke-width="25"
78 stroke-linejoin="round"
79 xmlns="http://www.w3.org/2000/svg"
80 >
81 <path
82 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"
83 />
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}