mirror of
https://github.com/inaturalist/iNaturalistReactNative.git
synced 2026-08-01 18:07:45 -04:00
Merge pull request #3843 from inaturalist/user-posts
View a user's journal posts from their profile
This commit is contained in:
@@ -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<string, unknown> = {},
|
||||
opts: Record<string, unknown> = {},
|
||||
): Promise<Record<string, unknown> | null | ErrorWithResponse | INatApiError> => {
|
||||
const fetchBlogPosts = async <T = ApiDefaultResult>(
|
||||
params: ApiParams = {},
|
||||
opts: ApiOpts = {},
|
||||
): Promise<ApiResponse<T> | 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<string, unknown> = {},
|
||||
opts: Record<string, unknown> = {},
|
||||
): Promise<Record<string, unknown> | null | ErrorWithResponse | INatApiError> => {
|
||||
const fetchProjectPosts = async <T = ApiDefaultResult>(
|
||||
params: ApiGetByIdParams,
|
||||
opts: ApiOpts = {},
|
||||
): Promise<ApiResponse<T> | 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 <T = ApiDefaultResult>(
|
||||
params: ApiGetByIdParams,
|
||||
opts: ApiOpts = {},
|
||||
): Promise<ApiResponse<T> | 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,
|
||||
};
|
||||
|
||||
8
src/api/types.d.ts
vendored
8
src/api/types.d.ts
vendored
@@ -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;
|
||||
|
||||
@@ -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 <T = ApiDefaultResult>(
|
||||
params: UsersProjectsParams,
|
||||
params: ApiGetByIdParams,
|
||||
opts: ApiOpts = {},
|
||||
): Promise<ApiResponse<T> | null | ErrorWithResponse | INatApiError> => {
|
||||
try {
|
||||
|
||||
@@ -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<TabStackScreenProps<"Journal">["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,
|
||||
} );
|
||||
|
||||
|
||||
@@ -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<TabStackScreenProps<"Journal">["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 (
|
||||
<UserPosts
|
||||
userIcon={userIcon}
|
||||
userId={userId}
|
||||
userLogin={userLogin}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -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;
|
||||
|
||||
84
src/components/Journal/UserPosts.tsx
Normal file
84
src/components/Journal/UserPosts.tsx
Normal file
@@ -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<TabStackScreenProps<"Journal">["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 (
|
||||
<ScreenShell>
|
||||
<PostList
|
||||
posts={enrichedPosts}
|
||||
fetchNextPage={fetchNextPage}
|
||||
isFetchingNextPage={isFetchingNextPage}
|
||||
/>
|
||||
</ScreenShell>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserPosts;
|
||||
@@ -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,
|
||||
} );
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user