a tool for shared writing and social publishing

render known followers

+45 -24
+6 -15
app/(home-pages)/p/[didOrHandle]/layout.tsx
··· 7 7 import { DashboardLayout } from "components/PageLayouts/DashboardLayout"; 8 8 import { ProfileLayout } from "./ProfileLayout"; 9 9 import { Agent } from "@atproto/api"; 10 + import { get_profile_data } from "app/api/rpc/[command]/get_profile_data"; 10 11 11 12 export default async function ProfilePageLayout(props: { 12 13 params: Promise<{ didOrHandle: string }>; ··· 33 34 } 34 35 did = resolved; 35 36 } 36 - 37 - let agent = new Agent({ 38 - service: "https://public.api.bsky.app", 39 - }); 40 - let profileReq = agent.app.bsky.actor.getProfile({ actor: did }); 41 - 42 - let publicationsReq = supabaseServerClient 43 - .from("publications") 44 - .select("*") 45 - .eq("identity_did", did); 46 - 47 - let [{ data: profile }, { data: publications }] = await Promise.all([ 48 - profileReq, 49 - publicationsReq, 50 - ]); 37 + let profileData = await get_profile_data.handler( 38 + { didOrHandle: did }, 39 + { supabase: supabaseServerClient }, 40 + ); 41 + let { publications, profile } = profileData.result; 51 42 52 43 if (!profile) return null; 53 44
+22 -4
app/api/rpc/[command]/get_profile_data.ts
··· 4 4 import { idResolver } from "app/(home-pages)/reader/idResolver"; 5 5 import { supabaseServerClient } from "supabase/serverClient"; 6 6 import { Agent } from "@atproto/api"; 7 + import { getIdentityData } from "actions/getIdentityData"; 8 + import { createOauthClient } from "src/atproto-oauth"; 7 9 8 10 export type GetProfileDataReturnType = Awaited< 9 11 ReturnType<(typeof get_profile_data)["handler"]> ··· 25 27 } 26 28 did = resolved; 27 29 } 30 + let agent; 31 + let authed_identity = await getIdentityData(); 32 + if (authed_identity?.atp_did) { 33 + try { 34 + const oauthClient = await createOauthClient(); 35 + let credentialSession = await oauthClient.restore( 36 + authed_identity.atp_did, 37 + ); 38 + agent = new Agent(credentialSession); 39 + } catch (e) { 40 + agent = new Agent({ 41 + service: "https://public.api.bsky.app", 42 + }); 43 + } 44 + } else { 45 + agent = new Agent({ 46 + service: "https://public.api.bsky.app", 47 + }); 48 + } 28 49 29 - let agent = new Agent({ 30 - service: "https://public.api.bsky.app", 31 - }); 32 50 let profileReq = agent.app.bsky.actor.getProfile({ actor: did }); 33 51 34 - let publicationsReq = supabaseServerClient 52 + let publicationsReq = supabase 35 53 .from("publications") 36 54 .select("*") 37 55 .eq("identity_did", did);
+17 -5
components/ProfilePopover.tsx
··· 6 6 import { ProfileHeader } from "app/(home-pages)/p/[didOrHandle]/ProfileHeader"; 7 7 import { SpeedyLink } from "./SpeedyLink"; 8 8 import { Tooltip } from "./Tooltip"; 9 + import { ProfileViewDetailed } from "@atproto/api/dist/client/types/app/bsky/actor/defs"; 9 10 10 11 export const ProfilePopover = (props: { 11 12 trigger: React.ReactNode; ··· 64 65 publications={data.publications} 65 66 popover 66 67 /> 67 - <hr className="border-border" /> 68 - <div className="py-2 text-sm text-tertiary"> 69 - Followed by{" "} 70 - <button className="hover:underline">celine and 4 others</button> 71 - </div> 68 + <KnownFollowers viewer={data.profile.viewer} /> 72 69 </div> 73 70 ) : ( 74 71 <div className="text-secondary py-2 px-4">Profile not found</div> ··· 76 73 </Tooltip> 77 74 ); 78 75 }; 76 + 77 + let KnownFollowers = (props: { viewer: ProfileViewDetailed["viewer"] }) => { 78 + if (!props.viewer?.knownFollowers) return null; 79 + let count = props.viewer.knownFollowers.count; 80 + return ( 81 + <> 82 + <hr className="border-border" /> 83 + Followed by{" "} 84 + <button className="hover:underline"> 85 + {props.viewer?.knownFollowers?.followers[0]?.displayName}{" "} 86 + {count > 1 ? `and ${count - 1} other${count > 2 ? "s" : ""}` : ""} 87 + </button> 88 + </> 89 + ); 90 + };