diff --git a/src/api/posts.ts b/src/api/posts.ts index 713d036c2..84ecf505a 100644 --- a/src/api/posts.ts +++ b/src/api/posts.ts @@ -1,27 +1,34 @@ import type { ErrorWithResponse, INatApiError } from "api/error"; import handleError from "api/error"; +import type { + ApiDefaultResult, ApiGetByIdParams, ApiOpts, ApiParams, ApiResponse, +} from "api/types"; import inatjs from "inaturalistjs"; -const fetchUserPosts = async ( - params: Record = {}, - opts: Record = {}, -): Promise | null | ErrorWithResponse | INatApiError> => { +const fetchBlogPosts = async ( + params: ApiParams = {}, + opts: ApiOpts = {}, +): Promise | null | ErrorWithResponse | INatApiError> => { try { - return await inatjs.posts.for_user( params, opts ); + const response = await inatjs.posts.for_user( params, opts ); + if ( !response ) { return null; } + return response; } catch ( e ) { return handleError( e as ErrorWithResponse, - { context: { functionName: "fetchUserPosts", opts } }, + { context: { functionName: "fetchBlogPosts", opts } }, ); } }; -const fetchProjectPosts = async ( - params: Record = {}, - opts: Record = {}, -): Promise | null | ErrorWithResponse | INatApiError> => { +const fetchProjectPosts = async ( + params: ApiGetByIdParams, + opts: ApiOpts = {}, +): Promise | null | ErrorWithResponse | INatApiError> => { try { - return await inatjs.projects.posts( params, opts ); + const response = await inatjs.projects.posts( params, opts ); + if ( !response ) { return null; } + return response; } catch ( e ) { return handleError( e as ErrorWithResponse, @@ -30,7 +37,24 @@ const fetchProjectPosts = async ( } }; +const fetchUserPosts = async ( + params: ApiGetByIdParams, + opts: ApiOpts = {}, +): Promise | null | ErrorWithResponse | INatApiError> => { + try { + const response = await inatjs.users.posts( params, opts ); + if ( !response ) { return null; } + return response; + } catch ( e ) { + return handleError( + e as ErrorWithResponse, + { context: { functionName: "fetchUserPosts", opts } }, + ); + } +}; + export { + fetchBlogPosts, fetchProjectPosts, fetchUserPosts, }; diff --git a/src/api/types.d.ts b/src/api/types.d.ts index 94e388fef..d6e4281b3 100644 --- a/src/api/types.d.ts +++ b/src/api/types.d.ts @@ -14,6 +14,10 @@ export interface ApiParams { ttl?: number; } +export interface ApiGetByIdParams extends ApiParams { + id: number; +} + export interface ApiPlace { id?: number; name?: string; @@ -21,7 +25,7 @@ export interface ApiPlace { place_type?: number | null; } -export interface ApiPostForProject { +export interface ApiPost { body: string; id: number; published_at: string; @@ -29,7 +33,7 @@ export interface ApiPostForProject { } // When using POST_FOR_USER_FIELDS -export interface ApiPostForUser extends ApiPostForProject { +export interface ApiPostForUser extends ApiPost { parent: { id: number; icon_url: string | null; diff --git a/src/api/usersTyped.ts b/src/api/usersTyped.ts index aa0cde902..fb93b7936 100644 --- a/src/api/usersTyped.ts +++ b/src/api/usersTyped.ts @@ -1,17 +1,12 @@ +import type { ErrorWithResponse, INatApiError } from "api/error"; +import handleError from "api/error"; +import type { + ApiDefaultResult, ApiGetByIdParams, ApiOpts, ApiResponse, +} from "api/types"; import inatjs from "inaturalistjs"; -import type { ErrorWithResponse, INatApiError } from "./error"; -import handleError from "./error"; -import type { - ApiDefaultResult, ApiOpts, ApiParams, ApiResponse, -} from "./types"; - -interface UsersProjectsParams extends ApiParams { - id: number; -} - const fetchUserProjects = async ( - params: UsersProjectsParams, + params: ApiGetByIdParams, opts: ApiOpts = {}, ): Promise | null | ErrorWithResponse | INatApiError> => { try { diff --git a/src/components/Journal/Blog.tsx b/src/components/Journal/Blog.tsx index 000c0d287..714c22ba9 100644 --- a/src/components/Journal/Blog.tsx +++ b/src/components/Journal/Blog.tsx @@ -1,6 +1,6 @@ import { useNavigation } from "@react-navigation/native"; import { POST_FOR_USER_FIELDS } from "api/fields"; -import { fetchUserPosts } from "api/posts"; +import { fetchBlogPosts } from "api/posts"; import { ScreenShell } from "components/SharedComponents/ViewWrapper"; import type { TabStackScreenProps } from "navigation/types"; import React, { @@ -18,7 +18,7 @@ const Blog = ( ) => { const navigation = useNavigation["navigation"]>( ); const { t } = useTranslation( ); - const queryKey = ["fetchUserPosts"]; + const queryKey = ["fetchBlogPosts"]; const queryParams = { fields: POST_FOR_USER_FIELDS, }; @@ -28,7 +28,7 @@ const Blog = ( ) => { fetchNextPage, isFetchingNextPage, totalResults: totalPosts, - } = useInfiniteScroll( queryKey, fetchUserPosts, queryParams, { + } = useInfiniteScroll( queryKey, fetchBlogPosts, queryParams, { enabled: true, } ); diff --git a/src/components/Journal/Journal.tsx b/src/components/Journal/Journal.tsx index 471b680e3..c223036c3 100644 --- a/src/components/Journal/Journal.tsx +++ b/src/components/Journal/Journal.tsx @@ -4,11 +4,12 @@ import React from "react"; import Blog from "./Blog"; import ProjectPosts from "./ProjectPosts"; +import UserPosts from "./UserPosts"; const Journal = ( ) => { const { params } = useRoute["route"]>( ); const { - journalPostsCount, projectIcon, projectId, projectTitle, userLogin, + projectIcon, projectId, projectTitle, userIcon, userId, userLogin, } = params || {}; if ( projectId ) { @@ -21,10 +22,14 @@ const Journal = ( ) => { ); } - // TODO: posts for one user - if ( userLogin ) { - console.log( journalPostsCount ); - return null; + if ( userId ) { + return ( + + ); } return ( diff --git a/src/components/Journal/ProjectPosts.tsx b/src/components/Journal/ProjectPosts.tsx index d5709a487..f45c1dd04 100644 --- a/src/components/Journal/ProjectPosts.tsx +++ b/src/components/Journal/ProjectPosts.tsx @@ -16,11 +16,11 @@ import PostList from "./PostList"; interface Props { projectIcon?: string; - projectId?: number; + projectId: number; projectTitle?: string; } -const PostsForProjects = ( { +const ProjectPosts = ( { projectIcon, projectId, projectTitle, @@ -81,4 +81,4 @@ const PostsForProjects = ( { ); }; -export default PostsForProjects; +export default ProjectPosts; diff --git a/src/components/Journal/UserPosts.tsx b/src/components/Journal/UserPosts.tsx new file mode 100644 index 000000000..c7e672b54 --- /dev/null +++ b/src/components/Journal/UserPosts.tsx @@ -0,0 +1,84 @@ +import { useNavigation } from "@react-navigation/native"; +import { POST_FOR_PROJECT_FIELDS } from "api/fields"; +import { fetchUserPosts } from "api/posts"; +import { ScreenShell } from "components/SharedComponents/ViewWrapper"; +import type { TabStackScreenProps } from "navigation/types"; +import React, { + useEffect, + useMemo, +} from "react"; +import { + useInfiniteScroll, + useTranslation, +} from "sharedHooks"; + +import PostList from "./PostList"; + +interface Props { + userIcon?: string; + userId: number; + userLogin?: string; +} + +const UserPosts = ( { + userIcon, + userId, + userLogin, +}: Props ) => { + const navigation + = useNavigation["navigation"]>(); + const { t } = useTranslation(); + + const queryKey = ["fetchUserPosts", userId]; + const queryParams = { + id: userId, + fields: POST_FOR_PROJECT_FIELDS, + }; + + const { + data: userPosts, + fetchNextPage, + isFetchingNextPage, + totalResults: totalPosts, + } = useInfiniteScroll( queryKey, fetchUserPosts, queryParams, { + enabled: !!userId, + } ); + + const headerOptions = useMemo( + () => ( { + headerTitle: userLogin, + headerSubtitle: t( "X-JOURNAL_POSTS", { + count: totalPosts || 0, + } ), + } ), + [totalPosts, t, userLogin], + ); + + useEffect( () => { + navigation.setOptions( headerOptions ); + }, [headerOptions, navigation] ); + + const enrichedPosts = useMemo( () => { + if ( !userPosts ) return null; + + return userPosts?.map( p => ( { + ...p, + parent: { + id: userId, + icon_url: userIcon, + }, + } ) ); + }, [userIcon, userId, userPosts] ); + + return ( + + + + ); +}; + +export default UserPosts; diff --git a/src/components/UserProfile/UserProfile.tsx b/src/components/UserProfile/UserProfile.tsx index bfb4e4c7e..c8d7d8e90 100644 --- a/src/components/UserProfile/UserProfile.tsx +++ b/src/components/UserProfile/UserProfile.tsx @@ -146,8 +146,9 @@ const UserProfile = ( ) => { const onJournalPostsPressed = ( ) => { navigation.navigate( "Journal", { + userIcon: User.uri( user ), + userId: user?.id, userLogin: user?.login, - journalPostsCount: user?.journal_posts_count, } ); }; diff --git a/src/navigation/types.ts b/src/navigation/types.ts index ca5b5f111..2efec5f07 100644 --- a/src/navigation/types.ts +++ b/src/navigation/types.ts @@ -327,8 +327,9 @@ export type BaseTabStackParamList = { }; // From UserProfile // { + // userId: user?.id, + // userIcon: user?.icon_url, // userLogin: user?.login, - // journalPostsCount: user?.journal_posts_count, // } // From ProjectDetails // { @@ -337,11 +338,12 @@ export type BaseTabStackParamList = { // projectTitle: project?.title, // } Journal: { - userLogin?: string; projectIcon?: string; projectId?: number; projectTitle?: string; - journalPostsCount?: number; + userIcon?: string; + userId?: number; + userLogin?: string; } | undefined; Debug: undefined; UILibrary: undefined;