Fork of atp.tools as a universal profile for people on the ATmosphere
at main 186 lines 5.9 kB view raw
1import { useEffect, useState } from "react"; 2import { 3 Card, 4 CardContent, 5 CardDescription, 6 CardHeader, 7 CardTitle, 8} from "@/components/ui/card"; 9import { Skeleton } from "@/components/ui/skeleton"; 10import { Link } from "@tanstack/react-router"; 11 12interface LinkData { 13 links: { 14 [key: string]: { 15 [key: string]: { 16 records: number; 17 distinct_dids: number; 18 }; 19 }; 20 }; 21} 22 23export function AllBacklinksViewer({ aturi }: { aturi: string }) { 24 const [data, setData] = useState<LinkData | null>(null); 25 const [loading, setLoading] = useState(true); 26 const [error, setError] = useState<string | null>(null); 27 28 useEffect(() => { 29 const fetchData = async () => { 30 try { 31 const response = await fetch( 32 `https://constellation.microcosm.blue/links/all?target=${encodeURIComponent(aturi)}`, 33 ); 34 const jsonData = await response.json(); 35 setData(jsonData); 36 setLoading(false); 37 } catch (err) { 38 setError("Error fetching data"); 39 setLoading(false); 40 } 41 }; 42 43 fetchData(); 44 }, [aturi]); 45 46 if (loading) { 47 return ( 48 <> 49 <h2 className="text-xl font-bold">Backlinks</h2> 50 <div className="grid gap-4 mt-4 md:grid-cols-1 lg:grid-cols-2"> 51 {[...Array(6)].map((_, i) => ( 52 <Card key={i}> 53 <CardHeader> 54 <Skeleton className="h-4 w-[250px]" /> 55 <Skeleton className="h-4 w-[200px]" /> 56 </CardHeader> 57 <CardContent> 58 <Skeleton className="h-20 w-full" /> 59 </CardContent> 60 </Card> 61 ))} 62 </div> 63 </> 64 ); 65 } 66 67 if (error) { 68 return ( 69 <Card className="m-4"> 70 <CardHeader> 71 <CardTitle className="text-red-500">Error</CardTitle> 72 <CardDescription>{error}</CardDescription> 73 </CardHeader> 74 </Card> 75 ); 76 } 77 78 if (!data) return null; 79 80 return ( 81 <> 82 <h2 className="text-2xl pt-6 font-semibold leading-3">Backlinks</h2> 83 <div className="text-sm text-muted-foreground"> 84 Interaction Statistics from{" "} 85 <a 86 className="text-blue-500 hover:underline" 87 href="https://constellation.microcosm.blue/" 88 target="_blank" 89 rel="noopener noreferrer" 90 > 91 constellation 92 </a> 93 </div> 94 <div className="grid gap-4 md:grid-cols-1 lg:grid-cols-2 leading-snug"> 95 {Object.entries(data.links).map(([category, stats]) => ( 96 <Card key={category} className="flex flex-col"> 97 <CardContent className="flex-1 mt-4"> 98 <CardTitle className="mb-2"> 99 {formatCategoryName(category)} 100 </CardTitle> 101 <div className="space-y-4"> 102 {Object.entries(stats).map(([stat, values]) => ( 103 <div key={stat} className="space-y-2"> 104 <h4 className="font-medium text-sm text-muted-foreground"> 105 {formatStatName(stat)} 106 </h4> 107 <div className="grid grid-cols-2 gap-2 text-sm"> 108 <Link 109 to={"/constellation/links/$collection"} 110 params={{ 111 collection: category, 112 }} 113 search={{ 114 path: stat, 115 target: aturi, 116 }} 117 className="flex justify-between text-blue-700 dark:text-blue-300" 118 > 119 <span>Records:</span> 120 <span> 121 <span className="font-medium">{values.records}</span> 122 <span className="border-l w-0 ml-2" /> 123 </span> 124 </Link> 125 <div className="flex justify-between"> 126 <Link 127 to={"/constellation/dids/$collection"} 128 params={{ 129 collection: category, 130 }} 131 search={{ 132 path: stat, 133 target: aturi, 134 }} 135 className="flex justify-between w-full text-blue-700 dark:text-blue-300" 136 > 137 <span>Distinct DIDs:</span> 138 <span className="font-medium"> 139 {values.distinct_dids} 140 </span> 141 </Link> 142 </div> 143 </div> 144 </div> 145 ))} 146 </div> 147 </CardContent> 148 </Card> 149 ))} 150 {Object.entries(data.links).length == 0 && ( 151 <div className="flex flex-col items-start justify-start"> 152 <p className="text-muted-foreground w-max"> 153 Nothing doing! No links indexed for this target! 154 </p> 155 <span className="text-muted-foreground text-xs"> 156 You can{" "} 157 <a 158 href={`https://constellation.microcosm.blue/links/all?target=${encodeURIComponent(aturi)}`} 159 className="text-blue-500 hover:underline" 160 > 161 view the api response 162 </a> 163 . 164 </span> 165 </div> 166 )} 167 </div> 168 </> 169 ); 170} 171 172// Helper function to format category names 173const formatCategoryName = (name: string) => { 174 return name 175 .split(".") 176 .pop() 177 ?.replace(/([A-Z])/g, " $1") 178 .trim(); 179}; 180 181// Helper function to format stat names 182const formatStatName = (name: string) => { 183 return name.split(".").filter(Boolean).join("."); 184}; 185 186export default AllBacklinksViewer;