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}