diff --git a/app/(home-pages)/p/[didOrHandle]/ProfileHeader.tsx b/app/(home-pages)/p/[didOrHandle]/ProfileHeader.tsx index 50e5c21e..ce5c5cd6 100644 --- a/app/(home-pages)/p/[didOrHandle]/ProfileHeader.tsx +++ b/app/(home-pages)/p/[didOrHandle]/ProfileHeader.tsx @@ -46,6 +46,7 @@ export const ProfileHeader = (props: { {/*
*/} {props.publications.map((p) => ( diff --git a/app/api/rpc/[command]/get_profile_data.ts b/app/api/rpc/[command]/get_profile_data.ts new file mode 100644 index 00000000..1e3dceb6 --- /dev/null +++ b/app/api/rpc/[command]/get_profile_data.ts @@ -0,0 +1,61 @@ +import { z } from "zod"; +import { makeRoute } from "../lib"; +import type { Env } from "./route"; +import { idResolver } from "app/(home-pages)/reader/idResolver"; + +export type GetProfileDataReturnType = Awaited< + ReturnType<(typeof get_profile_data)["handler"]> +>; + +export const get_profile_data = makeRoute({ + route: "get_profile_data", + input: z.object({ + didOrHandle: z.string(), + }), + handler: async ({ didOrHandle }, { supabase }: Pick) => { + // Resolve handle to DID if necessary + let did = didOrHandle; + + if (!didOrHandle.startsWith("did:")) { + const resolved = await idResolver.handle.resolve(didOrHandle); + if (!resolved) { + throw new Error("Could not resolve handle to DID"); + } + did = resolved; + } + + // Fetch profile + const { data: profile, error: profileError } = await supabase + .from("bsky_profiles") + .select("*") + .eq("did", did) + .single(); + + if (profileError) { + throw new Error(`Failed to fetch profile: ${profileError.message}`); + } + + if (!profile) { + throw new Error("Profile not found"); + } + + // Fetch publications for the DID + const { data: publications, error: publicationsError } = await supabase + .from("publications") + .select("*") + .eq("identity_did", did); + + if (publicationsError) { + throw new Error( + `Failed to fetch publications: ${publicationsError.message}`, + ); + } + + return { + result: { + profile, + publications: publications || [], + }, + }; + }, +}); diff --git a/app/api/rpc/[command]/route.ts b/app/api/rpc/[command]/route.ts index 4767ffb4..df7a865b 100644 --- a/app/api/rpc/[command]/route.ts +++ b/app/api/rpc/[command]/route.ts @@ -13,6 +13,7 @@ import { get_leaflet_data } from "./get_leaflet_data"; import { get_publication_data } from "./get_publication_data"; import { search_publication_names } from "./search_publication_names"; import { search_publication_documents } from "./search_publication_documents"; +import { get_profile_data } from "./get_profile_data"; let supabase = createClient( process.env.NEXT_PUBLIC_SUPABASE_API_URL as string, @@ -39,6 +40,7 @@ let Routes = [ get_publication_data, search_publication_names, search_publication_documents, + get_profile_data, ]; export async function POST( req: Request, diff --git a/app/lish/[did]/[publication]/[rkey]/Interactions/Comments/index.tsx b/app/lish/[did]/[publication]/[rkey]/Interactions/Comments/index.tsx index d82aeea4..b848aa3f 100644 --- a/app/lish/[did]/[publication]/[rkey]/Interactions/Comments/index.tsx +++ b/app/lish/[did]/[publication]/[rkey]/Interactions/Comments/index.tsx @@ -18,11 +18,12 @@ import { usePathname } from "next/navigation"; import { QuoteContent } from "../Quotes"; import { timeAgo } from "src/utils/timeAgo"; import { useLocalizedDate } from "src/hooks/useLocalizedDate"; +import { ProfilePopover } from "components/ProfilePopover"; export type Comment = { record: Json; uri: string; - bsky_profiles: { record: Json } | null; + bsky_profiles: { record: Json; did: string } | null; }; export function Comments(props: { document_uri: string; @@ -109,17 +110,25 @@ const Comment = (props: { document: string; comment: Comment; comments: Comment[]; - profile?: AppBskyActorProfile.Record; + profile: AppBskyActorProfile.Record; record: PubLeafletComment.Record; pageId?: string; }) => { + const did = props.comment.bsky_profiles?.did; + return (
- {props.profile && ( - + {did && ( + + {props.profile.displayName} +
+ } + /> )} -
{props.record.attachment && PubLeafletComment.isLinearDocumentQuote(props.record.attachment) && ( @@ -291,96 +300,3 @@ const DatePopover = (props: { date: string }) => { ); }; - -const ProfilePopover = (props: { - profile: AppBskyActorProfile.Record; - comment: string; -}) => { - let commenterId = new AtUri(props.comment).host; - - return ( - <> - - {props.profile.displayName} - - {/* - { - setHovering(true); - hoverTimeout.current = window.setTimeout(() => { - setLoadProfile(true); - }, 500); - }} - onMouseOut={() => { - setHovering(false); - clearTimeout(hoverTimeout.current); - }} - className="font-bold text-tertiary text-sm hover:underline" - > - {props.profile.displayName} -
- } - className="max-w-sm" - > - {profile && ( - <> -
-
-
-
-
-
celine
- - @{profile.handle} - -
-
- -
- {profile.description} -
-
- {!profile.viewer?.following ? ( -
- Following -
- ) : ( - - Follow - - )} - {profile.viewer?.followedBy && ( -
Follows You
- )} -
-
-
- -
-
-
- {profile.viewer?.knownFollowers?.followers.map((follower) => { - return ( -
- ); - })} -
-
-
-
- - )} - - */} - - ); -}; diff --git a/components/ProfilePopover.tsx b/components/ProfilePopover.tsx new file mode 100644 index 00000000..1a890cc5 --- /dev/null +++ b/components/ProfilePopover.tsx @@ -0,0 +1,42 @@ +"use client"; +import { ProfileHeader } from "app/p/[didOrHandle]/(profile)/ProfileHeader"; +import { Popover } from "./Popover"; +import useSWR from "swr"; +import { callRPC } from "app/api/rpc/client"; +import { useState } from "react"; + +export const ProfilePopover = (props: { + trigger: React.ReactNode; + didOrHandle: string; +}) => { + const [isOpen, setIsOpen] = useState(false); + + const { data, isLoading } = useSWR( + isOpen ? ["profile-data", props.didOrHandle] : null, + async () => { + const response = await callRPC("get_profile_data", { + didOrHandle: props.didOrHandle, + }); + return response.result; + }, + ); + + return ( + + {isLoading ? ( +
Loading...
+ ) : data ? ( + + ) : ( +
Profile not found
+ )} +
+ ); +};