From 0614ecb7bedf90b144d11572241e58bcb96f3083 Mon Sep 17 00:00:00 2001 From: Aly Raffauf Date: Sat, 25 Jul 2026 12:28:40 -0400 Subject: [PATCH] ProfilePage: lift out logic --- src/lib/profilePage.ts | 79 +++++++++++++++++++++++++++++++++++ src/pages/ProfilePage.tsx | 88 ++++----------------------------------- 2 files changed, 87 insertions(+), 80 deletions(-) create mode 100644 src/lib/profilePage.ts diff --git a/src/lib/profilePage.ts b/src/lib/profilePage.ts new file mode 100644 index 0000000..a3f21ca --- /dev/null +++ b/src/lib/profilePage.ts @@ -0,0 +1,79 @@ +import type { AppBskyActorProfile } from '@atcute/bluesky' +import { parseResourceUri, type Did, type Handle } from '@atcute/lexicons' +import type { $output as MiniDoc } from '@atcute/microcosm/types/blue/microcosm/identity/resolveMiniDoc' +import { getProfile as getBskyProfile } from './bsky/actor' +import { getMiniDoc } from './microcosm' +import { getProfile, listStrings, type Profile, type StringList } from './tangled' +import { getRepoByRepoDid, listRepos, type Repo, type RepoList } from './tangled/repo' +import { getRepoDidsFromStars, listStarsBy } from './tangled/feed' +import { listVouches, type VouchList } from './tangled/graph' + +export type StarredRepo = { + repo: Repo + handle: Handle +} + +export type ProfilePageData = { + identity: MiniDoc + profile: Profile + repos: RepoList + strings: StringList + pinnedRepos: Repo[] + starredRepos: StarredRepo[] + vouches: VouchList + bskyProfile: AppBskyActorProfile.Main | null +} + +export async function loadProfilePage(handle: Handle): Promise { + const identity = await getMiniDoc(handle) + const stars = await listStarsBy(identity.did) + const repoDids = getRepoDidsFromStars(stars) + + const [profile, repos, vouches, strings, bskyProfile] = await Promise.all([ + getProfile(identity.did), + listRepos(identity.did), + listVouches(identity.did), + listStrings(identity.did), + getBskyProfile(identity).catch(() => null), + ]) + + const ownerDocs = new Map>() + const getOwner = (identifier: string) => { + const existing = ownerDocs.get(identifier) + if (existing !== undefined) return existing + + const request = getMiniDoc(identifier) + ownerDocs.set(identifier, request) + return request + } + + const starredRepos = ( + await Promise.allSettled( + repoDids.map(async (repoDid): Promise => { + const repo = await getRepoByRepoDid(repoDid) + const owner = await getOwner(parseResourceUri(repo.uri).repo) + + return { repo, handle: owner.handle } + }), + ) + ).flatMap((result) => (result.status === 'fulfilled' ? [result.value] : [])) + + const pinnedResults = await Promise.allSettled( + (profile.value.pinnedRepositories ?? []).map((repoDid) => getRepoByRepoDid(repoDid as Did)), + ) + + const pinnedRepos = pinnedResults.flatMap((result) => + result.status === 'fulfilled' ? [result.value] : [], + ) + + return { + identity, + profile, + repos, + strings, + pinnedRepos, + starredRepos, + vouches, + bskyProfile, + } +} diff --git a/src/pages/ProfilePage.tsx b/src/pages/ProfilePage.tsx index 8ac4e63..593b507 100644 --- a/src/pages/ProfilePage.tsx +++ b/src/pages/ProfilePage.tsx @@ -1,7 +1,5 @@ -import type { AppBskyActorProfile } from '@atcute/bluesky' -import { parseResourceUri, type Did, type Handle } from '@atcute/lexicons' +import { type Handle } from '@atcute/lexicons' import { isHandle } from '@atcute/lexicons/syntax' -import type { $output as MiniDoc } from '@atcute/microcosm/types/blue/microcosm/identity/resolveMiniDoc' import { IconPin, IconThumbUp } from '@tabler/icons-react' import { type ReactNode, useEffect, useState } from 'react' import { useParams, useSearchParams } from 'react-router-dom' @@ -12,13 +10,8 @@ import { ProfileTabs } from '../components/profile/ProfileTabs' import { RepoListItem } from '../components/repo/RepoListItem' import { StringListItem } from '../components/string/StringListItem' import { VouchList } from '../components/vouch/VouchList' -import { getProfile as getBskyProfile } from '../lib/bsky/actor' -import { getMiniDoc } from '../lib/microcosm' import { parseProfileSection } from '../lib/profile' -import { getProfile, listStrings, type Profile, type StringList } from '../lib/tangled' -import { getRepoByRepoDid, listRepos, type Repo, type RepoList } from '../lib/tangled/repo' -import { getRepoDidsFromStars, listStarsBy } from '../lib/tangled/feed' -import { listVouches, type VouchList as VouchListData } from '../lib/tangled/graph' +import { loadProfilePage, type ProfilePageData } from '../lib/profilePage' const MAX_RECENT_VOUCHES = 4 @@ -26,14 +19,7 @@ export function ProfilePage() { const { handle: routeHandle } = useParams() const [searchParams] = useSearchParams() const handle = parseHandle(routeHandle) - const [identity, setIdentity] = useState(null) - const [profile, setProfile] = useState(null) - const [repos, setRepos] = useState(null) - const [strings, setStrings] = useState(null) - const [pinnedRepos, setPinnedRepos] = useState(null) - const [starredRepos, setStarredRepos] = useState(null) - const [vouches, setVouches] = useState(null) - const [bskyProfile, setBskyProfile] = useState(null) + const [pageData, setPageData] = useState(null) const [error, setError] = useState(null) useEffect(() => { @@ -43,58 +29,10 @@ export function ProfilePage() { async function loadPage(handle: Handle) { try { setError(null) - const miniDoc = await getMiniDoc(handle) - const stars = await listStarsBy(miniDoc.did) - const repoDids = getRepoDidsFromStars(stars) - - const [profile, repos, vouchList, strings, bskyProfile] = await Promise.all([ - getProfile(miniDoc.did), - listRepos(miniDoc.did), - listVouches(miniDoc.did), - listStrings(miniDoc.did), - getBskyProfile(miniDoc).catch(() => null), - ]) - - const ownerDocs = new Map>() - const getOwner = (identifier: string) => { - const existing = ownerDocs.get(identifier) - if (existing !== undefined) return existing - - const request = getMiniDoc(identifier) - ownerDocs.set(identifier, request) - return request - } - - const starredRepos = ( - await Promise.allSettled( - repoDids.map(async (repoDid): Promise => { - const repo = await getRepoByRepoDid(repoDid) - const owner = await getOwner(parseResourceUri(repo.uri).repo) - - return { repo, handle: owner.handle } - }), - ) - ).flatMap((result) => (result.status === 'fulfilled' ? [result.value] : [])) - - const pinnedResults = await Promise.allSettled( - (profile.value.pinnedRepositories ?? []).map((repoDid) => - getRepoByRepoDid(repoDid as Did), - ), - ) - - const pinnedRepos = pinnedResults.flatMap((result) => - result.status === 'fulfilled' ? [result.value] : [], - ) + const data = await loadProfilePage(handle) if (!cancelled) { - setIdentity(miniDoc) - setProfile(profile) - setRepos(repos) - setPinnedRepos(pinnedRepos) - setStarredRepos(starredRepos) - setVouches(vouchList) - setStrings(strings) - setBskyProfile(bskyProfile) + setPageData(data) } } catch (caught) { if (!cancelled) { @@ -118,17 +56,12 @@ export function ProfilePage() { return

Could not load profile: {error.message}

} - if ( - identity === null || - profile === null || - repos === null || - strings === null || - starredRepos === null || - vouches === null - ) { + if (pageData === null) { return } + const { identity, profile, repos, strings, pinnedRepos, starredRepos, vouches, bskyProfile } = + pageData const activeSection = parseProfileSection(searchParams.get('view')) const recentVouches = vouches.items.slice(0, MAX_RECENT_VOUCHES) @@ -234,11 +167,6 @@ type ProfileCollectionProps = { children: ReactNode } -type StarredRepo = { - repo: Repo - handle: Handle -} - function ProfileCollection({ title, children }: ProfileCollectionProps) { const headingId = `${title.toLowerCase()}-heading` -- 2.51.2