diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts index 7466e35..eb45a89 100644 --- a/src/routeTree.gen.ts +++ b/src/routeTree.gen.ts @@ -18,6 +18,8 @@ import { Route as IndexRouteImport } from './routes/index' import { Route as CallbackIndexRouteImport } from './routes/callback/index' import { Route as PathlessLayoutNestedLayoutRouteImport } from './routes/_pathlessLayout/_nested-layout' import { Route as ProfileDidIndexRouteImport } from './routes/profile.$did/index' +import { Route as ProfileDidFollowsRouteImport } from './routes/profile.$did/follows' +import { Route as ProfileDidFollowersRouteImport } from './routes/profile.$did/followers' import { Route as PathlessLayoutNestedLayoutRouteBRouteImport } from './routes/_pathlessLayout/_nested-layout/route-b' import { Route as PathlessLayoutNestedLayoutRouteARouteImport } from './routes/_pathlessLayout/_nested-layout/route-a' import { Route as ProfileDidPostRkeyRouteImport } from './routes/profile.$did/post.$rkey' @@ -71,6 +73,16 @@ const ProfileDidIndexRoute = ProfileDidIndexRouteImport.update({ path: '/profile/$did/', getParentRoute: () => rootRouteImport, } as any) +const ProfileDidFollowsRoute = ProfileDidFollowsRouteImport.update({ + id: '/profile/$did/follows', + path: '/profile/$did/follows', + getParentRoute: () => rootRouteImport, +} as any) +const ProfileDidFollowersRoute = ProfileDidFollowersRouteImport.update({ + id: '/profile/$did/followers', + path: '/profile/$did/followers', + getParentRoute: () => rootRouteImport, +} as any) const PathlessLayoutNestedLayoutRouteBRoute = PathlessLayoutNestedLayoutRouteBRouteImport.update({ id: '/route-b', @@ -127,6 +139,8 @@ export interface FileRoutesByFullPath { '/callback': typeof CallbackIndexRoute '/route-a': typeof PathlessLayoutNestedLayoutRouteARoute '/route-b': typeof PathlessLayoutNestedLayoutRouteBRoute + '/profile/$did/followers': typeof ProfileDidFollowersRoute + '/profile/$did/follows': typeof ProfileDidFollowsRoute '/profile/$did': typeof ProfileDidIndexRoute '/profile/$did/feed/$rkey': typeof ProfileDidFeedRkeyRoute '/profile/$did/post/$rkey': typeof ProfileDidPostRkeyRouteWithChildren @@ -144,6 +158,8 @@ export interface FileRoutesByTo { '/callback': typeof CallbackIndexRoute '/route-a': typeof PathlessLayoutNestedLayoutRouteARoute '/route-b': typeof PathlessLayoutNestedLayoutRouteBRoute + '/profile/$did/followers': typeof ProfileDidFollowersRoute + '/profile/$did/follows': typeof ProfileDidFollowsRoute '/profile/$did': typeof ProfileDidIndexRoute '/profile/$did/feed/$rkey': typeof ProfileDidFeedRkeyRoute '/profile/$did/post/$rkey': typeof ProfileDidPostRkeyRouteWithChildren @@ -164,6 +180,8 @@ export interface FileRoutesById { '/callback/': typeof CallbackIndexRoute '/_pathlessLayout/_nested-layout/route-a': typeof PathlessLayoutNestedLayoutRouteARoute '/_pathlessLayout/_nested-layout/route-b': typeof PathlessLayoutNestedLayoutRouteBRoute + '/profile/$did/followers': typeof ProfileDidFollowersRoute + '/profile/$did/follows': typeof ProfileDidFollowsRoute '/profile/$did/': typeof ProfileDidIndexRoute '/profile/$did/feed/$rkey': typeof ProfileDidFeedRkeyRoute '/profile/$did/post/$rkey': typeof ProfileDidPostRkeyRouteWithChildren @@ -183,6 +201,8 @@ export interface FileRouteTypes { | '/callback' | '/route-a' | '/route-b' + | '/profile/$did/followers' + | '/profile/$did/follows' | '/profile/$did' | '/profile/$did/feed/$rkey' | '/profile/$did/post/$rkey' @@ -200,6 +220,8 @@ export interface FileRouteTypes { | '/callback' | '/route-a' | '/route-b' + | '/profile/$did/followers' + | '/profile/$did/follows' | '/profile/$did' | '/profile/$did/feed/$rkey' | '/profile/$did/post/$rkey' @@ -219,6 +241,8 @@ export interface FileRouteTypes { | '/callback/' | '/_pathlessLayout/_nested-layout/route-a' | '/_pathlessLayout/_nested-layout/route-b' + | '/profile/$did/followers' + | '/profile/$did/follows' | '/profile/$did/' | '/profile/$did/feed/$rkey' | '/profile/$did/post/$rkey' @@ -236,6 +260,8 @@ export interface RootRouteChildren { SearchRoute: typeof SearchRoute SettingsRoute: typeof SettingsRoute CallbackIndexRoute: typeof CallbackIndexRoute + ProfileDidFollowersRoute: typeof ProfileDidFollowersRoute + ProfileDidFollowsRoute: typeof ProfileDidFollowsRoute ProfileDidIndexRoute: typeof ProfileDidIndexRoute ProfileDidFeedRkeyRoute: typeof ProfileDidFeedRkeyRoute ProfileDidPostRkeyRoute: typeof ProfileDidPostRkeyRouteWithChildren @@ -306,6 +332,20 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ProfileDidIndexRouteImport parentRoute: typeof rootRouteImport } + '/profile/$did/follows': { + id: '/profile/$did/follows' + path: '/profile/$did/follows' + fullPath: '/profile/$did/follows' + preLoaderRoute: typeof ProfileDidFollowsRouteImport + parentRoute: typeof rootRouteImport + } + '/profile/$did/followers': { + id: '/profile/$did/followers' + path: '/profile/$did/followers' + fullPath: '/profile/$did/followers' + preLoaderRoute: typeof ProfileDidFollowersRouteImport + parentRoute: typeof rootRouteImport + } '/_pathlessLayout/_nested-layout/route-b': { id: '/_pathlessLayout/_nested-layout/route-b' path: '/route-b' @@ -420,6 +460,8 @@ const rootRouteChildren: RootRouteChildren = { SearchRoute: SearchRoute, SettingsRoute: SettingsRoute, CallbackIndexRoute: CallbackIndexRoute, + ProfileDidFollowersRoute: ProfileDidFollowersRoute, + ProfileDidFollowsRoute: ProfileDidFollowsRoute, ProfileDidIndexRoute: ProfileDidIndexRoute, ProfileDidFeedRkeyRoute: ProfileDidFeedRkeyRoute, ProfileDidPostRkeyRoute: ProfileDidPostRkeyRouteWithChildren, diff --git a/src/routes/notifications.tsx b/src/routes/notifications.tsx index 8404415..aff944f 100644 --- a/src/routes/notifications.tsx +++ b/src/routes/notifications.tsx @@ -132,20 +132,24 @@ function MentionsTab() { ); } -function FollowsTab() { +export function FollowsTab({did}:{did?:string}) { const { agent } = useAuth(); + const userdidunsafe = did ?? agent?.did; + const { data: identity} = useQueryIdentity(userdidunsafe); + const userdid = identity?.did; + const [constellationurl] = useAtom(constellationURLAtom); const infinitequeryresults = useInfiniteQuery({ ...yknowIReallyHateThisButWhateverGuardedConstructConstellationInfiniteQueryLinks( { constellation: constellationurl, method: "/links", - target: agent?.did, + target: userdid, collection: "app.bsky.graph.follow", path: ".subject", } ), - enabled: !!agent?.did, + enabled: !!userdid, }); const { diff --git a/src/routes/profile.$did/feed.$rkey.tsx b/src/routes/profile.$did/feed.$rkey.tsx index f8acb09..23fe1b1 100644 --- a/src/routes/profile.$did/feed.$rkey.tsx +++ b/src/routes/profile.$did/feed.$rkey.tsx @@ -13,6 +13,7 @@ export const Route = createFileRoute("/profile/$did/feed/$rkey")({ component: FeedRoute, }); +// todo: scroll restoration function FeedRoute() { const { did, rkey } = Route.useParams(); const { agent, status } = useAuth(); diff --git a/src/routes/profile.$did/followers.tsx b/src/routes/profile.$did/followers.tsx new file mode 100644 index 0000000..39485bf --- /dev/null +++ b/src/routes/profile.$did/followers.tsx @@ -0,0 +1,30 @@ +import { createFileRoute } from "@tanstack/react-router"; + +import { Header } from "~/components/Header"; + +import { FollowsTab } from "../notifications"; + +export const Route = createFileRoute("/profile/$did/followers")({ + component: RouteComponent, +}); + +// todo: scroll restoration +function RouteComponent() { + const params = Route.useParams(); + + return ( +
+
{ + if (window.history.length > 1) { + window.history.back(); + } else { + window.location.assign("/"); + } + }} + /> + +
+ ); +} diff --git a/src/routes/profile.$did/follows.tsx b/src/routes/profile.$did/follows.tsx new file mode 100644 index 0000000..b884220 --- /dev/null +++ b/src/routes/profile.$did/follows.tsx @@ -0,0 +1,79 @@ +import * as ATPAPI from "@atproto/api" +import { createFileRoute } from '@tanstack/react-router' +import React from 'react'; + +import { Header } from '~/components/Header'; +import { useReusableTabScrollRestore } from '~/components/ReusableTabRoute'; +import { useInfiniteQueryAuthorFeed, useQueryIdentity } from '~/utils/useQuery'; + +import { EmptyState, ErrorState, LoadingState, NotificationItem } from '../notifications'; + +export const Route = createFileRoute('/profile/$did/follows')({ + component: RouteComponent, +}) + +// todo: scroll restoration +function RouteComponent() { + const params = Route.useParams(); + return ( +
+
{ + if (window.history.length > 1) { + window.history.back(); + } else { + window.location.assign("/"); + } + }} + /> + +
+ ); +} + +function Follows({did}:{did:string}) { + const {data: identity} = useQueryIdentity(did); + const infinitequeryresults = useInfiniteQueryAuthorFeed(identity?.did, identity?.pds, "app.bsky.graph.follow"); + + const { + data: infiniteFollowsData, + fetchNextPage, + hasNextPage, + isFetchingNextPage, + isLoading, + isError, + error, + } = infinitequeryresults; + + const followsAturis = React.useMemo( + () => infiniteFollowsData?.pages.flatMap((page) => page.records) ?? [], + [infiniteFollowsData] + ); + + useReusableTabScrollRestore("Notifications"); + + if (isLoading) return ; + if (isError) return ; + + if (!followsAturis?.length) return ; + + return ( + <> + {followsAturis.map((m) => { + const record = m.value as unknown as ATPAPI.AppBskyGraphFollow.Record; + return + })} + + {hasNextPage && ( + + )} + + ); +} \ No newline at end of file diff --git a/src/routes/profile.$did/index.tsx b/src/routes/profile.$did/index.tsx index 8ee1aa2..f5dc0f1 100644 --- a/src/routes/profile.$did/index.tsx +++ b/src/routes/profile.$did/index.tsx @@ -27,6 +27,7 @@ import { useInfiniteQueryAuthorFeed, useQueryArbitrary, useQueryConstellation, + useQueryConstellationLinksCountDistinctDids, useQueryIdentity, useQueryProfile, } from "~/utils/useQuery"; @@ -77,6 +78,15 @@ function ProfileComponent() { const isReady = !!resolvedDid && !isIdentityLoading && !!profileRecord; + const resultwhateversure = useQueryConstellationLinksCountDistinctDids(resolvedDid ? { + method: "/links/count/distinct-dids", + collection: "app.bsky.graph.follow", + target: resolvedDid, + path: ".subject" + } : undefined) + + const followercount = resultwhateversure?.data?.total; + return (
{handle}
+
+ {followercount && ({followercount})}Followers + - + Follows +
{description && (
{/* {description} */} diff --git a/src/utils/useQuery.ts b/src/utils/useQuery.ts index 5f90e63..6991db5 100644 --- a/src/utils/useQuery.ts +++ b/src/utils/useQuery.ts @@ -284,6 +284,25 @@ export function constructConstellationQuery(query?:{ gcTime: /*0//*/5 * 60 * 1000, }); } +// todo do more of these instead of overloads since overloads sucks so much apparently +export function useQueryConstellationLinksCountDistinctDids(query?: { + method: "/links/count/distinct-dids"; + target: string; + collection: string; + path: string; + cursor?: string; +}): UseQueryResult | undefined { + //if (!query) return; + const [constellationurl] = useAtom(constellationURLAtom) + const queryres = useQuery( + constructConstellationQuery(query && {constellation: constellationurl, ...query}) + ) as unknown as UseQueryResult; + if (!query) { + return undefined as undefined; + } + return queryres as UseQueryResult; +} + export function useQueryConstellation(query: { method: "/links"; target: string;