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}