👁️
5
fork

Configure Feed

Select the types of activity you want to include in your feed.

check for dns records to determine if url is a website

+60 -7
+4 -2
src/components/profile/ProfileHeader.tsx
··· 14 14 isOwner: boolean; 15 15 onUpdate: (profile: Profile) => void; 16 16 isSaving: boolean; 17 + showHandleLink: boolean; 17 18 } 18 19 19 20 export function ProfileHeader({ ··· 23 24 isOwner, 24 25 onUpdate, 25 26 isSaving, 27 + showHandleLink, 26 28 }: ProfileHeaderProps) { 27 29 const pronounsId = useId(); 28 30 const [isEditing, setIsEditing] = useState(false); ··· 92 94 > 93 95 {displayHandle} 94 96 </h1> 95 - {handleUrl && ( 97 + {showHandleLink && handleUrl && ( 96 98 <a 97 99 href={handleUrl} 98 100 target="_blank" ··· 165 167 > 166 168 {displayHandle} 167 169 </h1> 168 - {handleUrl && ( 170 + {showHandleLink && handleUrl && ( 169 171 <a 170 172 href={handleUrl} 171 173 target="_blank"
+40 -1
src/components/profile/ProfileLayout.tsx
··· 1 1 import type { Did } from "@atcute/lexicons"; 2 - import { useQuery } from "@tanstack/react-query"; 2 + import { queryOptions, useQuery } from "@tanstack/react-query"; 3 3 import { Link } from "@tanstack/react-router"; 4 4 import { ProfileHeader } from "@/components/profile/ProfileHeader"; 5 5 import { didDocumentQueryOptions, extractHandle } from "@/lib/did-to-handle"; ··· 9 9 } from "@/lib/profile-queries"; 10 10 import { useAuth } from "@/lib/useAuth"; 11 11 12 + interface DoHResponse { 13 + Answer?: { type: number; data: string }[]; 14 + } 15 + 16 + async function requireDnsRecord( 17 + handle: string, 18 + type: "A" | "AAAA" | "CNAME", 19 + ): Promise<true> { 20 + const response = await fetch( 21 + `https://cloudflare-dns.com/dns-query?name=${encodeURIComponent(handle)}&type=${type}`, 22 + { headers: { Accept: "application/dns-json" } }, 23 + ); 24 + if (!response.ok) throw new Error("DNS query failed"); 25 + const data: DoHResponse = await response.json(); 26 + if ((data.Answer?.length ?? 0) === 0) throw new Error("No records"); 27 + return true; 28 + } 29 + 30 + export const domainResolvesQueryOptions = (handle: string | null) => 31 + queryOptions({ 32 + queryKey: ["domain-resolves", handle] as const, 33 + queryFn: async (): Promise<boolean> => { 34 + if (!handle) return false; 35 + try { 36 + await Promise.any([ 37 + requireDnsRecord(handle, "A"), 38 + requireDnsRecord(handle, "AAAA"), 39 + requireDnsRecord(handle, "CNAME"), 40 + ]); 41 + return true; 42 + } catch { 43 + return false; 44 + } 45 + }, 46 + staleTime: 10 * 60 * 1000, // 10 minutes 47 + }); 48 + 12 49 interface ProfileLayoutProps { 13 50 did: string; 14 51 children: React.ReactNode; ··· 21 58 const updateProfileMutation = useUpdateProfileMutation(); 22 59 23 60 const handle = extractHandle(didDocument ?? null); 61 + const { data: domainResolves } = useQuery(domainResolvesQueryOptions(handle)); 24 62 const isOwner = session?.info.sub === did; 25 63 26 64 return ( ··· 34 72 isOwner={isOwner} 35 73 onUpdate={(profile) => updateProfileMutation.mutate(profile)} 36 74 isSaving={updateProfileMutation.isPending} 75 + showHandleLink={domainResolves ?? false} 37 76 /> 38 77 39 78 {/* Tab Navigation */}
+8 -2
src/routes/profile/$did/index.tsx
··· 4 4 import { Plus } from "lucide-react"; 5 5 import { useMemo } from "react"; 6 6 import { DeckPreview } from "@/components/DeckPreview"; 7 - import { ProfileLayout } from "@/components/profile/ProfileLayout"; 7 + import { 8 + domainResolvesQueryOptions, 9 + ProfileLayout, 10 + } from "@/components/profile/ProfileLayout"; 8 11 import { 9 12 type DeckListRecord, 10 13 listUserDecksQueryOptions, ··· 39 42 getProfileQueryOptions(params.did as Did), 40 43 ), 41 44 ]); 42 - return { handle: extractHandle(didDocument) }; 45 + const handle = extractHandle(didDocument); 46 + // Prefetch DNS check (don't await - not critical for render) 47 + context.queryClient.prefetchQuery(domainResolvesQueryOptions(handle)); 48 + return { handle }; 43 49 }, 44 50 head: ({ loaderData }) => { 45 51 const display = loaderData?.handle ? `@${loaderData.handle}` : "Profile";
+8 -2
src/routes/profile/$did/lists.tsx
··· 3 3 import { createFileRoute } from "@tanstack/react-router"; 4 4 import { Plus } from "lucide-react"; 5 5 import { ListPreview } from "@/components/ListPreview"; 6 - import { ProfileLayout } from "@/components/profile/ProfileLayout"; 6 + import { 7 + domainResolvesQueryOptions, 8 + ProfileLayout, 9 + } from "@/components/profile/ProfileLayout"; 7 10 import { 8 11 listUserCollectionListsQueryOptions, 9 12 useCreateCollectionListMutation, ··· 26 29 getProfileQueryOptions(params.did as Did), 27 30 ), 28 31 ]); 29 - return { handle: extractHandle(didDocument) }; 32 + const handle = extractHandle(didDocument); 33 + // Prefetch DNS check (don't await - not critical for render) 34 + context.queryClient.prefetchQuery(domainResolvesQueryOptions(handle)); 35 + return { handle }; 30 36 }, 31 37 head: ({ loaderData }) => { 32 38 const display = loaderData?.handle ? `@${loaderData.handle}` : "Profile";