Skip to content

Commit

Permalink
SKRR-24 feat: 클럽 게시물 작성하기 api 연결 (#246)
Browse files Browse the repository at this point in the history
* feat: 클럽 게시물 작성 api 연결

* feat: 클럽 게시글 조회 api 연결

* feat: 클럽 게시물 작성 시 앞뒤 공백 예외처리

* refactor: 클럽 게시물 매직넘버 상수화

* feat: 클럽 게시글 작성 완료 시 해당 게시글 상세페이지로 이동

* remove: 안 쓰는 util함수 제거

* fix: 클럽 게시물 api endpoint 수정
  • Loading branch information
colorkite10 authored Jan 10, 2024
1 parent 61ae73e commit aaf836c
Show file tree
Hide file tree
Showing 11 changed files with 146 additions and 40 deletions.
34 changes: 34 additions & 0 deletions src/apis/club/postCreateClubPost.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { END_POINTS } from '@/constants/api';
import { PostCreateClubPostRequest } from '@/types/api/postCreateClubPost';

import { axiosClientWithAuth } from '../axiosClient';

const postCreateClubPost = async ({ clubId, title, content, image }: PostCreateClubPostRequest) => {
const dataTransform = {
title,
content,
};
const formData = new FormData();
if (title || content) {
const blobRequest = new Blob([JSON.stringify(dataTransform)], { type: 'application/json' });
formData.append('postRequest', blobRequest);
}

if (image) {
formData.append('image', image);
}

const { headers } = await axiosClientWithAuth.post(
END_POINTS.CREATE_CLUB_POST(clubId),
formData,
{
headers: {
'Content-Type': 'multipart/form-data',
},
},
);

return headers.location;
};

export default postCreateClubPost;
16 changes: 10 additions & 6 deletions src/components/ClubPost/ClubPost.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { PATH } from '@/constants/path';

import { useNavigate } from 'react-router-dom';

import Avatar from '../common/Avatar/Avatar';
import {
AuthorWrapper,
BoardContainer,
Expand All @@ -18,8 +19,9 @@ interface ClubPostProps {
title: string;
content: string;
author: string;
imageUrl?: string;
postDate: string;
authorImageUrl?: string;
postImageUrl?: string;
createdDate: string;
}

const ClubPost = ({
Expand All @@ -28,11 +30,12 @@ const ClubPost = ({
title,
content,
author,
imageUrl,
postDate,
authorImageUrl,
postImageUrl,
createdDate,
}: ClubPostProps) => {
const navigate = useNavigate();
const hasImage = Boolean(imageUrl);
const hasImage = Boolean(postImageUrl);

return (
<BoardContainer>
Expand All @@ -42,7 +45,8 @@ const ClubPost = ({
<ContentStyled>{content}</ContentStyled>
</BoardContentWrapper>
<BoardInfoWrapper>
<PostDateWrapper>{postDate}</PostDateWrapper>
<PostDateWrapper>{createdDate}</PostDateWrapper>
<Avatar avatarSize="small" profileImageSrc={authorImageUrl} />
<AuthorWrapper>{author}</AuthorWrapper>
</BoardInfoWrapper>
</BoardContainer>
Expand Down
27 changes: 15 additions & 12 deletions src/components/ClubPosts/ClubPosts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,21 @@ const ClubPosts = ({ clubId }: ClubPostsProps) => {
return (
<>
<PostsContainer>
{posts?.map(({ postId, title, content, author, imageUrl, postDate }) => (
<ClubPost
key={postId}
clubId={clubId}
postId={postId}
title={title}
content={content}
author={author}
imageUrl={imageUrl}
postDate={postDate}
/>
))}
{posts?.map(
({ postId, title, content, author, authorImageUrl, createdDate, postImageUrl }) => (
<ClubPost
key={postId}
clubId={clubId}
postId={postId}
title={title}
content={content}
author={author}
authorImageUrl={authorImageUrl}
postImageUrl={postImageUrl}
createdDate={createdDate}
/>
),
)}
{posts?.length === 0 && <EmptyClubEvent>게시글이 없습니다.</EmptyClubEvent>}
</PostsContainer>
<Pagination
Expand Down
4 changes: 3 additions & 1 deletion src/constants/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,10 @@ const END_POINTS = {
CLUB_EVENTS: ({ clubId, pageNumber }: { clubId: string; pageNumber: number }) =>
`/clubs/${clubId}/events?page=${pageNumber}&size=12&sort=id,asc`,
CLUB_SCHEDULES: ({ clubId }: { clubId: string }) => `/clubs/${clubId}/events?page=0&sort=id,desc`,

CREATE_CLUB_POST: (clubId: string) => `/boards/posts/${clubId}`,
CLUB_POSTS: (clubId: string, pageNumber: number) =>
`/boards/${clubId}?page=${pageNumber}&size=20&sort=id,desc`,
`/boards/posts/${clubId}?page=${pageNumber}&size=20&sort=id,desc`,
CLUB_POST: (clubId: string, postId: string) => `/boards/posts/${clubId}/${postId}`,

PATCH_MEMBER_ROLE: ({ clubId, memberId }: { clubId: string; memberId: string }) =>
Expand Down
9 changes: 9 additions & 0 deletions src/constants/club.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ const MAX_CLUB_NAME_LENGTH = 12;
const MIN_CLUB_INFO_LENGTH = 2;
const MAX_CLUB_INFO_LENGTH = 25;

const MIN_CLUB_POST_TITLE_LENGTH = 2;
const MAX_CLUB_POST_TITLE_LENGTH = 30;
const MIN_CLUB_POST_CONTENT_LENGTH = 2;
const MAX_CLUB_POST_CONTENT_LENGTH = 1000;

export {
CREATE_CLUB,
INVITE_LINK,
Expand All @@ -36,4 +41,8 @@ export {
MAX_CLUB_NAME_LENGTH,
MIN_CLUB_INFO_LENGTH,
MAX_CLUB_INFO_LENGTH,
MIN_CLUB_POST_TITLE_LENGTH,
MAX_CLUB_POST_TITLE_LENGTH,
MIN_CLUB_POST_CONTENT_LENGTH,
MAX_CLUB_POST_CONTENT_LENGTH,
};
4 changes: 2 additions & 2 deletions src/hooks/query/club/useGetClubPostsQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ const useGetClubPostsQuery = ({ clubId, pageNumber }: GetClubPostsRequest) => {
staleTime: CLUB_POSTS_STALE_TIME,
});

const { posts, pageData } = clubPosts ?? {};
const { data, pageData } = clubPosts ?? {};

return { posts, pageData };
return { posts: data, pageData };
};

export default useGetClubPostsQuery;
31 changes: 31 additions & 0 deletions src/hooks/query/club/usePostCreateClubPost.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import postCreateClubPost from '@/apis/club/postCreateClubPost';
import { PATH } from '@/constants/path';
import useToast from '@/hooks/useToast';

import { useNavigate } from 'react-router-dom';

import { useMutation, useQueryClient } from '@tanstack/react-query';

import { QUERY_KEY } from './useGetClubPostsQuery';

const usePostCreateClubPost = () => {
const navigate = useNavigate();
const queryClient = useQueryClient();
const { createToast } = useToast();

const { mutate: createPost } = useMutation(postCreateClubPost, {
onSuccess: (data: string) => {
queryClient.invalidateQueries([QUERY_KEY.CLUB_POSTS]);
createToast({ message: '작성 완료되었습니다.', toastType: 'success' });
const [, , , , , clubId, postId] = data.split('/');
navigate(PATH.CLUB.POST(clubId, postId));
},
onError: () => {
createToast({ message: '글 작성 실패', toastType: 'error' });
},
});

return { createPost };
};

export default usePostCreateClubPost;
38 changes: 28 additions & 10 deletions src/pages/club/ClubPostWritePage/ClubPostWritePage.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import ClubHeader from '@/components/ClubHeader/ClubHeader';
import Button from '@/components/common/Button/Button';
import ClubBanner from '@/components/common/ClubBanner/ClubBanner';
import {
MAX_CLUB_POST_CONTENT_LENGTH,
MAX_CLUB_POST_TITLE_LENGTH,
MIN_CLUB_POST_CONTENT_LENGTH,
MIN_CLUB_POST_TITLE_LENGTH,
} from '@/constants/club';
import usePostCreateClubPost from '@/hooks/query/club/usePostCreateClubPost';

import { SubmitHandler, useForm } from 'react-hook-form';
import { useParams } from 'react-router-dom';
Expand Down Expand Up @@ -36,8 +43,17 @@ const ClubPostWritePage = () => {
},
});
if (!clubId) throw new Error('클럽 ID를 찾을 수 없습니다');
const { createPost } = usePostCreateClubPost();

const onSubmit: SubmitHandler<ClubPostWriteValue> = (data) => {
createPost({ clubId, ...data, title: data.title?.trim(), content: data.content?.trim() });
};

const onSubmit: SubmitHandler<ClubPostWriteValue> = () => {};
const handleInputValueValidate = (value: string) => {
if (value.trim() === '') {
return '공백 문자만 입력할 수 없습니다.';
}
};

return (
<>
Expand All @@ -55,13 +71,14 @@ const ClubPostWritePage = () => {
{...register('title', {
required: '제목 입력은 필수입니다.',
minLength: {
value: 2,
message: '2글자 이상 입력해주세요.',
value: MIN_CLUB_POST_TITLE_LENGTH,
message: `${MIN_CLUB_POST_TITLE_LENGTH}글자 이상 입력해주세요.`,
},
maxLength: {
value: 30,
message: '30자 이상 입력할 수 없습니다.',
value: MAX_CLUB_POST_TITLE_LENGTH,
message: `${MAX_CLUB_POST_TITLE_LENGTH} 이상 입력할 수 없습니다.`,
},
validate: (value) => handleInputValueValidate(value ?? ''),
})}
placeholder="제목을 입력해주세요."
/>
Expand All @@ -70,19 +87,20 @@ const ClubPostWritePage = () => {
{...register('content', {
required: '내용 입력은 필수입니다.',
minLength: {
value: 2,
message: '2글자 이상 입력해주세요.',
value: MIN_CLUB_POST_CONTENT_LENGTH,
message: `${MIN_CLUB_POST_CONTENT_LENGTH}글자 이상 입력해주세요.`,
},
maxLength: {
value: 1000,
message: '1000자 이상 입력할 수 없습니다.',
value: MAX_CLUB_POST_CONTENT_LENGTH,
message: `${MAX_CLUB_POST_CONTENT_LENGTH} 이상 입력할 수 없습니다.`,
},
validate: (value) => handleInputValueValidate(value ?? ''),
})}
placeholder="내용을 입력해주세요."
/>
<ErrorMessageStyled>{errors?.content ? errors.content.message : ''}</ErrorMessageStyled>
<FileInputWrapper>
<input type="file" accept=".jpg, .jpeg, .png, .heic" />
<input {...register('image')} type="file" accept=".jpg, .jpeg, .png, .heic" />
</FileInputWrapper>
</ContentWrapper>
</form>
Expand Down
9 changes: 6 additions & 3 deletions src/types/api/getClubPosts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@ interface GetClubPostsRequest {
}

interface GetClubPostsResponse {
posts: {
data: {
postId: string;
title: string;
content: string;
authorId: string;
author: string;
imageUrl: string;
postDate: string;
authorImageUrl?: string;
postImageUrl?: string;
createdDate: string;
lastModifiedDate?: string;
}[];
pageData: PageData;
}
Expand Down
8 changes: 8 additions & 0 deletions src/types/api/postCreateClubPost.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
interface PostCreateClubPostRequest {
clubId: string;
title: string;
content: string;
image: File;
}

export { PostCreateClubPostRequest };
6 changes: 0 additions & 6 deletions src/utils/truncateText.ts

This file was deleted.

0 comments on commit aaf836c

Please sign in to comment.