From 76fb942bdefee49e2d7e094da412f0917fca3669 Mon Sep 17 00:00:00 2001 From: Foysal Ahamed Date: Sat, 23 Nov 2024 23:48:31 +0000 Subject: [PATCH] :sparkles: Add people tab on search page --- app/search/page-content.tsx | 111 ++++++++++++------ components/repositories/AccountView.tsx | 55 +++++---- .../search-content/useContentSearch.tsx | 54 ++++++++- 3 files changed, 153 insertions(+), 67 deletions(-) diff --git a/app/search/page-content.tsx b/app/search/page-content.tsx index f7b1471b..5be86a23 100644 --- a/app/search/page-content.tsx +++ b/app/search/page-content.tsx @@ -4,9 +4,15 @@ import { Loading } from '@/common/Loader' import { LoadMoreButton } from '@/common/LoadMoreButton' import { PostAsCard } from '@/common/posts/PostsFeed' import { useWorkspaceOpener } from '@/common/useWorkspaceOpener' +import { ProfileCard } from '@/repositories/AccountView' import { WorkspacePanel } from '@/workspace/Panel' +import { AppBskyActorDefs, AppBskyFeedDefs } from '@atproto/api' import { MagnifyingGlassIcon } from '@heroicons/react/20/solid' -import { useContentSearch } from 'components/search-content/useContentSearch' +import { + isActorData, + SearchContentSection, + useContentSearch, +} from 'components/search-content/useContentSearch' import { SectionHeader } from 'components/SectionHeader' import { useSearchParams } from 'next/navigation' import { useTitle } from 'react-use' @@ -22,12 +28,17 @@ const TABS = [ name: 'Latest', href: `/search?section=latest`, }, + { + key: 'people', + name: 'People', + href: `/search?section=people`, + }, ] export const SearchPageContent = () => { const searchParams = useSearchParams() const term = searchParams.get('term') ?? '' - const section = searchParams.get('section') ?? 'top' + const section = (searchParams.get('section') ?? 'top') as SearchContentSection let pageTitle = `Search Content` if (term) { @@ -35,12 +46,37 @@ export const SearchPageContent = () => { } useTitle(pageTitle) - const { posts, isLoading, fetchNextPage, hasNextPage, addToWorkspace } = + const { data, isLoading, fetchNextPage, hasNextPage, addToWorkspace } = useContentSearch({ term, section, }) const { toggleWorkspacePanel, isWorkspaceOpen } = useWorkspaceOpener() + const workspaceOptions = [ + { + id: 'add_users_to_workspace', + text: section === 'people' ? 'Add all people' : 'Add all posters', + onClick: () => { + if (isActorData(data)) { + addToWorkspace(data.map((item) => item.did)) + } else { + addToWorkspace(data.map((item) => item.author.did)) + } + }, + }, + ] + + if (section !== 'people') { + workspaceOptions.push({ + id: 'add_posts_to_workspace', + text: 'Add all posts', + onClick: () => { + if (!isActorData(data)) { + addToWorkspace(data.map((item) => item.uri)) + } + }, + }) + } return ( <> @@ -50,48 +86,49 @@ export const SearchPageContent = () => { containerClassName="inline-block" rightAligned className="inline-flex justify-center rounded-md border border-gray-300 dark:border-gray-400 bg-white dark:bg-slate-800 dark:text-gray-100 dark px-4 py-2 text-sm text-gray-700 shadow-sm hover:bg-gray-50 dark:hover:bg-slate-700" - items={[ - { - id: 'add_users_to_workspace', - text: 'Add all posters', - onClick: () => - addToWorkspace(posts.map((post) => post.author.did)), - }, - { - id: 'add_posts_to_workspace', - text: 'Add all posts', - onClick: () => addToWorkspace(posts.map((post) => post.uri)), - }, - ]} + items={workspaceOptions} > Add to workspace - -
- {!term && ( + {!term ? ( +
- )} - - {!!term && isLoading && } - {posts.map((post) => ( -
- -
- ))} - {hasNextPage && ( -
- fetchNextPage()} /> -
- )} -
+
+ ) : ( +
+ {isLoading && } + {isActorData(data) ? ( +
+ {data.map((item) => { + return + })} +
+ ) : ( +
+ {data.map((item) => { + return ( +
+ +
+ ) + })} +
+ )} + {hasNextPage && ( +
+ fetchNextPage()} /> +
+ )} +
+ )} {accounts?.map((account) => ( -
-
- -
-
- -
-
+ ))} ) } + +export const ProfileCard = ({ + profile, +}: { + profile: AppBskyActorDefs.ProfileView | AppBskyActorDefs.ProfileViewBasic +}) => { + return ( +
+
+ +
+
+ +
+
+ ) +} + enum EventViews { ByUser, ForUser, diff --git a/components/search-content/useContentSearch.tsx b/components/search-content/useContentSearch.tsx index 7613b876..f3e07064 100644 --- a/components/search-content/useContentSearch.tsx +++ b/components/search-content/useContentSearch.tsx @@ -1,22 +1,53 @@ -import { useLabelerAgent } from '@/shell/ConfigurationContext' +import { useAppviewAgent, useLabelerAgent } from '@/shell/ConfigurationContext' import { useWorkspaceAddItemsMutation } from '@/workspace/hooks' +import { + AppBskyActorDefs, + AppBskyActorSearchActors, + AppBskyFeedDefs, + AppBskyFeedSearchPosts, +} from '@atproto/api' import { useInfiniteQuery } from '@tanstack/react-query' +type ActorResult = AppBskyActorSearchActors.OutputSchema +type PostResult = AppBskyFeedSearchPosts.OutputSchema +export type SearchContentSection = 'people' | 'top' | 'latest' +type QueryData = S extends 'people' + ? ActorResult + : PostResult + export const useContentSearch = ({ term, section, }: { term: string - section: string + section: SearchContentSection }) => { const labelerAgent = useLabelerAgent() + const appviewAgent = useAppviewAgent() const { mutate: addToWorkspace } = useWorkspaceAddItemsMutation() - const searchInfo = useInfiniteQuery({ + const searchInfo = useInfiniteQuery>({ queryKey: ['searchContent', { term, section }], enabled: !!term, queryFn: async ({ pageParam }) => { + if (section === 'people') { + // This is actually never hit, we always have appview configured + if (!appviewAgent) { + throw new Error( + 'Can not search actors without appview agent configured', + ) + } + + const { data } = await appviewAgent.app.bsky.actor.searchActors({ + q: term, + limit: 30, + cursor: pageParam, + }) + + return data + } + const { data } = await labelerAgent.app.bsky.feed.searchPosts({ q: term, limit: 30, @@ -28,11 +59,24 @@ export const useContentSearch = ({ getNextPageParam: (lastPage) => lastPage.cursor, }) - const posts = searchInfo.data?.pages.flatMap((page) => page.posts) || [] + const data = + (section === 'people' + ? (searchInfo.data?.pages.flatMap( + (page) => page.actors, + ) as ActorResult['actors']) + : (searchInfo.data?.pages.flatMap( + (page) => page.posts, + ) as PostResult['posts'])) || [] return { ...searchInfo, - posts, + data, addToWorkspace, } } + +export function isActorData( + data: ActorResult['actors'] | PostResult['posts'], +): data is ActorResult['actors'] { + return data.length === 0 || 'did' in data[0] +}