/** * The user a profile page is about comes through this hook, which loads them * by the name in the URL and keeps them live. Each tab of the profile loads * its own lists, by the user's id. * * @module */ import { useEffect, useState } from "react"; import type { PublicUser } from "../../../shared/user.ts"; import { fetchPublicUser } from "../../utils/shared/fetchPublicUser.ts"; import { useAuth } from "../auth/useAuth.ts"; import { useWS } from "../ws/useWS.ts"; import { type FetchState, useFetchState } from "../shared/useFetchState.ts"; /** Returned by {@link useProfile} to `UserPublicProfile`. */ export interface UseProfileResult { state: FetchState; /** It is false while the profile loads. */ isOwnProfile: boolean; /** It shows the header's edits, which the server echoes to other browsers. */ setUser: (fn: (user: PublicUser) => PublicUser) => void; } /** * Returns the user named `username`, from `GET /api/users/@:username`, which * answers a former name or another case too. The profile then puts the * current name in its URL, and that path, naming the user on screen, fetches * nothing; another user's does. The socket's `user_updated` keeps them live. */ export function useProfile(username: string | undefined): UseProfileResult { const { user: me } = useAuth(); const { lastUserEvent } = useWS(); // The name last fetched, which the URL may leave for the current one. const [target, setTarget] = useState(username); const { state, setData } = useFetchState( target, (signal) => fetchPublicUser(target!, signal), ); const user = state.status === "loaded" ? state.data : null; if ( target !== username && user?.username.toLowerCase() !== username?.toLowerCase() ) { setTarget(username); } useEffect(() => { if (!lastUserEvent) return; setData((u) => u.id === lastUserEvent.user.id ? lastUserEvent.user : u); }, [lastUserEvent, setData]); return { state, isOwnProfile: !!me && me.id === user?.id, setUser: setData, }; }