From 627f1c8edc3bbe167f639fb119dca7b8eca7ad3b Mon Sep 17 00:00:00 2001 From: Seungmin Cha <75214259+Virtuso1225@users.noreply.github.com> Date: Sun, 23 Mar 2025 22:30:16 +0900 Subject: [PATCH 1/5] =?UTF-8?q?feat:=20=EC=8B=A0=EA=B3=A0=ED=95=98?= =?UTF-8?q?=EA=B8=B0=20api=20=EC=97=B0=EA=B2=B0=EC=9D=84=20=ED=95=B4?= =?UTF-8?q?=EC=9A=94.=20(#215)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 신고하기 api 연결을 해요. * feat: pr 리뷰 반영 --- src/api/hooks/community.ts | 20 ----------------- src/components/community/post/Comment.tsx | 6 ++++- .../community/post/CommentHeader.tsx | 22 +++++++++---------- .../community/post/CommentReply.tsx | 7 ++++-- src/components/community/post/CommentSet.tsx | 7 +++--- src/components/community/post/Post.tsx | 13 +++++------ src/domain/Report/hooks/usePostReport.ts | 15 +++++++++++++ 7 files changed, 45 insertions(+), 45 deletions(-) create mode 100644 src/domain/Report/hooks/usePostReport.ts diff --git a/src/api/hooks/community.ts b/src/api/hooks/community.ts index c09480a4..e2d377d3 100644 --- a/src/api/hooks/community.ts +++ b/src/api/hooks/community.ts @@ -9,7 +9,6 @@ import { useSearchParams } from 'react-router-dom' import { useErrorHandledMutation } from '@/api/hooks/useErrorHandledMutation' import { - CommentReportRequest, GetMyCommentsResponse, PostByBoardResponse, PostCommentLikeRequest, @@ -20,7 +19,6 @@ import { PostPreviewResponse, PostReactionRequest, PostReactionResponse, - PostReportRequest, PostScrapResponse, } from '@/api/types/community' import { CommentProps, PostPreviewByBoardMeta, PostPreviewProps, PostViewProps, ReactionType } from '@/types/community' @@ -318,24 +316,6 @@ export const useDeletePost = () => { return useErrorHandledMutation({ mutationFn: deletePost }) } -const reportPost = async ({ postId, reason }: PostReportRequest) => { - const response = await apiInterface.post(`/post/${postId}/report`, { reason }) - return response.data -} - -export const useReportPost = () => { - return useErrorHandledMutation({ mutationFn: reportPost }) -} - -const reportComment = async ({ commentId, reason }: CommentReportRequest) => { - const response = await apiInterface.post(`/comment/${commentId}/report`, { reason }) - return response.data -} - -export const useReportComment = () => { - return useErrorHandledMutation({ mutationFn: reportComment }) -} - const getMyPost = async (take: number, cursor?: string) => { const response = await apiInterface.get('post/my', { params: { take, cursor: cursor?.length === 14 ? cursor : undefined }, diff --git a/src/components/community/post/Comment.tsx b/src/components/community/post/Comment.tsx index 2e5e15c3..44d595e8 100644 --- a/src/components/community/post/Comment.tsx +++ b/src/components/community/post/Comment.tsx @@ -17,8 +17,9 @@ interface CommentProps { isOpen: boolean currentIndex: number handleClick: () => void + boardName: string } -const Comment = memo(({ isOpen, currentIndex, handleClick }: CommentProps) => { +const Comment = memo(({ isOpen, currentIndex, handleClick, boardName }: CommentProps) => { const post = useAtomValue(postAtom) const isPostAuthorAnonymous = post.user.isAnonymous const comment = post.comments[currentIndex] @@ -34,6 +35,7 @@ const Comment = memo(({ isOpen, currentIndex, handleClick }: CommentProps) => { () => getCommentUsername({ comment, isPostAuthorAnonymous }), [comment, isPostAuthorAnonymous], ) + return (
{ date={comment.createdAt} isMyComment={comment.isMyComment} commentId={comment.id} + postId={post.id} + boardName={boardName} isAuthorMatchingPostAnonymity={isAuthorMatchingPostAnonymity({ isAuthor: comment.isAuthor, isPostAuthorAnonymous, diff --git a/src/components/community/post/CommentHeader.tsx b/src/components/community/post/CommentHeader.tsx index a93e6d5e..8d63a9bf 100644 --- a/src/components/community/post/CommentHeader.tsx +++ b/src/components/community/post/CommentHeader.tsx @@ -2,11 +2,11 @@ import { css } from '@styled-system/css' import { boardTag } from '@styled-system/recipes' import { User, UserPen } from 'lucide-react' import { useCallback, useState } from 'react' -import { useLocation } from 'react-router-dom' -import { useDeleteComment, useReportComment } from '@/api/hooks/community' +import { useDeleteComment } from '@/api/hooks/community' import UtilButton from '@/components/community/post/UtilButton' import AlertModal from '@/components/ui/modal/AlertModal' +import { usePostReport } from '@/domain/Report/hooks/usePostReport' import { getFormattedTimeString } from '@/util/getFormattedTimeString' import { useModal } from '@/util/hooks/useModal' @@ -17,6 +17,8 @@ interface CommentHeaderProps { commentId: number isAuthorMatchingPostAnonymity?: boolean isDeleted: boolean + postId: number + boardName: string } const CommentHeader = ({ isMyComment, @@ -25,12 +27,13 @@ const CommentHeader = ({ commentId, isAuthorMatchingPostAnonymity, isDeleted, + postId, + boardName, }: CommentHeaderProps) => { const [target, setTarget] = useState<'report' | 'delete'>() const { modalRef, isOpen, handleOpen, handleLayoutClose, handleButtonClose } = useModal() - const { mutate: mutateReportComment } = useReportComment() + const { mutate: mutateReportComment } = usePostReport({}) const { mutate: mutateDeleteComment } = useDeleteComment() - const boardName = useLocation().pathname.split('/')[2] const handleTargetAndOpen = useCallback( (target: 'report' | 'delete') => { setTarget(target) @@ -38,14 +41,11 @@ const CommentHeader = ({ }, [handleOpen], ) + const handleReportConfirm = useCallback(() => { - mutateReportComment( - { commentId, reason: 'Inappropriate' }, - { - onSettled: () => handleButtonClose(), - }, - ) - }, [commentId, handleButtonClose, mutateReportComment]) + mutateReportComment({ postId, commentId, reason: 'Inappropriate' }, { onSettled: handleButtonClose }) + }, [commentId, handleButtonClose, mutateReportComment, postId]) + const handleDelete = useCallback( () => mutateDeleteComment(commentId, { onSuccess: handleButtonClose }), [commentId, handleButtonClose, mutateDeleteComment], diff --git a/src/components/community/post/CommentReply.tsx b/src/components/community/post/CommentReply.tsx index 8187d1ad..2339476f 100644 --- a/src/components/community/post/CommentReply.tsx +++ b/src/components/community/post/CommentReply.tsx @@ -11,14 +11,15 @@ import { POST_MESSAGES } from '@/lib/messages/community' import { postAtom } from '@/lib/store/post' import { CommentProps } from '@/types/community' import { getCommentUsername } from '@/util/getCommentUsername' -import { isAuthorMatchingPostAnonymity } from '@/util/isAuthorMatchingPostAnonymity' import { useModal } from '@/util/hooks/useModal' +import { isAuthorMatchingPostAnonymity } from '@/util/isAuthorMatchingPostAnonymity' interface CommentReplyProps { reply: Omit parentId: number + boardName: string } -const CommentReply = ({ reply, parentId }: CommentReplyProps) => { +const CommentReply = ({ reply, parentId, boardName }: CommentReplyProps) => { const post = useAtomValue(postAtom) const isPostAuthorAnonymous = post.user.isAnonymous const { mutate: mutateLike } = usePostCommentLike() @@ -50,6 +51,8 @@ const CommentReply = ({ reply, parentId }: CommentReplyProps) => { date={reply.createdAt} isMyComment={reply.isMyComment} commentId={reply.id} + postId={post.id} + boardName={boardName} isAuthorMatchingPostAnonymity={isAuthorMatchingPostAnonymity({ isAuthor: reply.isAuthor, isPostAuthorAnonymous, diff --git a/src/components/community/post/CommentSet.tsx b/src/components/community/post/CommentSet.tsx index 1d3d9d2b..06d90b2c 100644 --- a/src/components/community/post/CommentSet.tsx +++ b/src/components/community/post/CommentSet.tsx @@ -1,12 +1,12 @@ import { css } from '@styled-system/css' import { useAtomValue } from 'jotai' import { useCallback, useState } from 'react' +import { useParams } from 'react-router-dom' import Comment from '@/components/community/post/Comment' import CommentInput from '@/components/community/post/CommentInput' import CommentReply from '@/components/community/post/CommentReply' import { postAtom } from '@/lib/store/post' - interface CommentSetProps { index: number } @@ -14,6 +14,7 @@ const CommentSet = ({ index }: CommentSetProps) => { const [open, setOpen] = useState(false) const handleClick = useCallback(() => setOpen(prev => !prev), []) const comment = useAtomValue(postAtom).comments[index] + const { boardName = '' } = useParams() return (
{ alignSelf: 'stretch', })} > - + {comment.reply.map(reply => ( - + ))}
diff --git a/src/components/community/post/Post.tsx b/src/components/community/post/Post.tsx index bb09b0e0..9c708335 100644 --- a/src/components/community/post/Post.tsx +++ b/src/components/community/post/Post.tsx @@ -6,12 +6,13 @@ import { Eye } from 'lucide-react' import { memo, useCallback, useMemo } from 'react' import { useNavigate, useParams } from 'react-router-dom' -import { useDeletePost, useReportPost } from '@/api/hooks/community' +import { useDeletePost } from '@/api/hooks/community' import BoardTag from '@/components/community/Boards/BoardTag' import PostImgCarousel from '@/components/community/post/PostImgCarousel' import ReactionSection from '@/components/community/post/ReactionSection' import UtilButton from '@/components/community/post/UtilButton' import AlertModal from '@/components/ui/modal/AlertModal' +import { usePostReport } from '@/domain/Report/hooks/usePostReport' import { persistedPostData, postAtom } from '@/lib/store/post' import { BoardType } from '@/types/community' import { useModal } from '@/util/hooks/useModal' @@ -27,8 +28,9 @@ const Post = memo(() => { navigate(`/community/action/edit/post/${boardName}`) postEditData(postAtomData) }, [navigate, boardName, postEditData, postAtomData]) + const { mutate: mutateDeletePost } = useDeletePost() - const { mutate: mutateReportPost } = useReportPost() + const { mutate: mutateReportPost } = usePostReport({}) const { modalRef, isOpen, handleOpen, handleLayoutClose, handleButtonClose } = useModal() const handleConfirm = useCallback(() => { @@ -41,12 +43,7 @@ const Post = memo(() => { }, [handleButtonClose, mutateDeletePost, navigate, postAtomData.id]) const handleReportConfirm = useCallback(() => { - mutateReportPost( - { postId: postAtomData.id, reason: 'Inappropriate' }, - { - onSettled: () => handleButtonClose(), - }, - ) + mutateReportPost({ postId: postAtomData.id, reason: 'Inappropriate' }, { onSettled: () => handleButtonClose() }) }, [handleButtonClose, mutateReportPost, postAtomData.id]) const isPostEditable = useMemo(() => { diff --git a/src/domain/Report/hooks/usePostReport.ts b/src/domain/Report/hooks/usePostReport.ts new file mode 100644 index 00000000..3e001220 --- /dev/null +++ b/src/domain/Report/hooks/usePostReport.ts @@ -0,0 +1,15 @@ +import { useErrorHandledMutation, UseErrorHandledMutationOption } from '@/api/hooks/useErrorHandledMutation' +import { useAsyncRead } from '@/common/hooks/useAsyncRead' +import { kuKeyClient } from '@/packages/api' +import { ReportPostRequestParams } from '@/packages/api/ku-key/api/report-api' + +type Props = UseErrorHandledMutationOption + +export const usePostReport = (props: Props) => { + const fetch = useAsyncRead(kuKeyClient.api.ReportApi.reportPost) + + const mutation = (params: ReportPostRequestParams['createReportRequestDto']) => + fetch({ createReportRequestDto: params }) + + return useErrorHandledMutation({ ...props, mutationFn: mutation }) +} From 9bc6d2894dc8c86880ef9be4ea8a335601d94f95 Mon Sep 17 00:00:00 2001 From: Seungmin Cha <75214259+Virtuso1225@users.noreply.github.com> Date: Tue, 25 Mar 2025 17:43:46 +0900 Subject: [PATCH 2/5] =?UTF-8?q?feat:=20=EA=B2=80=EC=83=89=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=EC=9D=84=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20api=20?= =?UTF-8?q?=EC=99=80=20=EC=97=B0=EA=B2=B0=ED=95=B4=EC=9A=94.=20-=204=20(#1?= =?UTF-8?q?98)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 검색과 api fetching을 연결해요. * feat(group): pr 리뷰 피드백 --- .../Community/CommunityOutlet/index.tsx | 19 ++------- .../Community/CommunityOutlet/style.css.ts | 6 --- src/features/Community/PostAll/index.tsx | 18 +++++++- src/features/Community/PostByBoard/index.tsx | 3 ++ src/features/Community/PostSearch/index.tsx | 42 +++++++++++++++++++ .../Community/PostSearch/style.css.ts | 9 ++++ src/pages/Community/All/index.tsx | 4 +- src/pages/Community/All/style.css.ts | 4 +- 8 files changed, 81 insertions(+), 24 deletions(-) create mode 100644 src/features/Community/PostSearch/index.tsx create mode 100644 src/features/Community/PostSearch/style.css.ts diff --git a/src/features/Community/CommunityOutlet/index.tsx b/src/features/Community/CommunityOutlet/index.tsx index b648b1a5..cd1c6d0c 100644 --- a/src/features/Community/CommunityOutlet/index.tsx +++ b/src/features/Community/CommunityOutlet/index.tsx @@ -1,28 +1,17 @@ import * as s from './style.css' -import SearchBox from '@/components/timetable/SearchBox' +import HotBoardPreview from '@/components/community/HotBoard/HotBoardPreview' import CommunityPostDetail from '@/features/Community/CommunityPostDetail' -import { Button } from '@/ui/Button' -import { Typography } from '@/ui/Typography' +import CommunityPostSearch from '@/features/Community/PostSearch' const CommunityOutlet = () => { return (
-
-
-
- View Recent Posts - - Check out our recent posts - -
- -
- {}} /> -
+
+
) } diff --git a/src/features/Community/CommunityOutlet/style.css.ts b/src/features/Community/CommunityOutlet/style.css.ts index 198fb433..478e4608 100644 --- a/src/features/Community/CommunityOutlet/style.css.ts +++ b/src/features/Community/CommunityOutlet/style.css.ts @@ -5,9 +5,3 @@ import { f } from '@/style' export const Wrapper = style([f.flex, f.directionRow, f.alignStart, { gap: '3.75rem' }]) export const LeftWrapper = style([f.flex, f.directionColumn, f.wFull, { gap: '5.375rem', maxWidth: '44.5625rem' }]) - -export const SearchWrapper = style([f.flex, f.directionColumn, f.wFull, { gap: '1.875rem' }]) - -export const Header = style([f.flex, f.alignCenter, f.justifyBetween, f.wFull]) - -export const Title = style([f.flex, f.directionColumn, { gap: '0.375rem' }]) diff --git a/src/features/Community/PostAll/index.tsx b/src/features/Community/PostAll/index.tsx index 04435028..8fcabbbf 100644 --- a/src/features/Community/PostAll/index.tsx +++ b/src/features/Community/PostAll/index.tsx @@ -3,9 +3,25 @@ import * as s from './style.css' import PostPreview from '@/components/community/PostPreview' import { useReadCommunityPostsAll } from '@/domain/Post/useReadCommunityPostsAll' import Pagination from '@/ui/Pagination' +import { useQueryParams } from '@/util/hooks/useQueryParams' + +type SearchParams = { + keyword?: string +} const CommunityPostAll = () => { - const { data: posts, hasNextPage, isFetchingNextPage, fetchNextPage } = useReadCommunityPostsAll({}) + const [queryParams] = useQueryParams() + const { + data: posts, + hasNextPage, + isFetchingNextPage, + fetchNextPage, + } = useReadCommunityPostsAll({ + keyword: queryParams.keyword, + }) + + if (!posts.length) return
No Search Result
+ return ( { boardId: queryParam.boardId, keyword: queryParam.keyword, }) + + if (!posts.length) return
No Search Result
+ return ( { + const [queryParams, setQueryParams] = useQueryParams() + const { isOpen, handleOpen } = useModal(true) + const onSubmit = (searchParam: string) => { + if (searchParam.length === 1) return handleOpen() + setQueryParams({ keyword: searchParam.length ? searchParam : undefined }) + } + + const keyword = queryParams.keyword ?? '' + + return ( +
+
+
+ View Recent Posts + + Check out our recent posts + +
+ +
+ + +
+ ) +} + +export default CommunityPostSearch diff --git a/src/features/Community/PostSearch/style.css.ts b/src/features/Community/PostSearch/style.css.ts new file mode 100644 index 00000000..b0689767 --- /dev/null +++ b/src/features/Community/PostSearch/style.css.ts @@ -0,0 +1,9 @@ +import { style } from '@vanilla-extract/css' + +import { f } from '@/style' + +export const SearchWrapper = style([f.flex, f.directionColumn, f.wFull, { gap: '1.875rem' }]) + +export const Header = style([f.flex, f.alignCenter, f.justifyBetween, f.wFull]) + +export const Title = style([f.flex, f.directionColumn, { gap: '0.375rem' }]) diff --git a/src/pages/Community/All/index.tsx b/src/pages/Community/All/index.tsx index 500f68c1..de1e2f51 100644 --- a/src/pages/Community/All/index.tsx +++ b/src/pages/Community/All/index.tsx @@ -10,7 +10,9 @@ const CommunityAllPage = () => { return ( }>
- +
+ +
}> diff --git a/src/pages/Community/All/style.css.ts b/src/pages/Community/All/style.css.ts index 72da723a..38030c92 100644 --- a/src/pages/Community/All/style.css.ts +++ b/src/pages/Community/All/style.css.ts @@ -2,4 +2,6 @@ import { style } from '@vanilla-extract/css' import { f } from '@/style' -export const Wrapper = style([f.flex, f.directionColumn, { marginTop: '2.5rem', gap: '2.5rem' }]) +export const Wrapper = style([f.flex, f.directionColumn, f.alignCenter, { marginTop: '2.5rem', gap: '2.5rem' }]) + +export const SelectTabWrapper = style([f.flex, f.alignStart, { width: '66.6875rem' }]) From 2b7d7fc1bd30d1387d07d4929ffd66a705202f71 Mon Sep 17 00:00:00 2001 From: Seungmin Cha <75214259+Virtuso1225@users.noreply.github.com> Date: Tue, 25 Mar 2025 23:43:04 +0900 Subject: [PATCH 3/5] =?UTF-8?q?feat:=20=EB=B3=80=EA=B2=BD=EB=90=9C=20?= =?UTF-8?q?=ED=95=AB=20=EA=B2=8C=EC=8B=9C=ED=8C=90=20=ED=94=84=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0=EB=A5=BC=20=EA=B5=AC=ED=98=84=ED=96=88=EC=96=B4?= =?UTF-8?q?=EC=9A=94.=20-=205=20(#199)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 핫 게시판 프리뷰를 구현했어요. * feat: lazyloading route 로 변경했어요. * feat: pr 리뷰 * feat: PR 리뷰 반영 --- .../components/PostCategoryBadge/index.tsx | 33 +++++++++++++++++ .../components/PostCategoryBadge/style.css.ts | 11 ++++++ .../Post/components/PostPreviewCard/index.tsx | 36 +++++++++++++++++++ .../components/PostPreviewCard/style.css.ts | 20 +++++++++++ .../Post/components/PostPreviewItem/index.tsx | 18 ++++++++++ .../components/PostPreviewItem/style.css.ts | 5 +++ .../Post/hooks/useReadCommunityHotPosts.ts | 17 +++++++++ .../{ => hooks}/useReadCommunityPostsAll.ts | 0 .../useReadCommunityPostsByBoard.ts | 0 src/domain/Post/queries.ts | 7 +++- .../Community/CommunityOutlet/index.tsx | 4 +-- .../Community/CommunityOutlet/style.css.ts | 2 +- .../Community/HotPostPreview/index.tsx | 28 +++++++++++++++ .../Community/HotPostPreview/style.css.ts | 11 ++++++ src/features/Community/PostAll/index.tsx | 2 +- src/features/Community/PostByBoard/index.tsx | 2 +- src/lib/router/lazy-route.tsx | 1 + src/lib/router/router.tsx | 2 +- src/ui/Pagination/index.tsx | 2 +- src/ui/Typography/index.tsx | 36 +++++++++++++------ 20 files changed, 219 insertions(+), 18 deletions(-) create mode 100644 src/domain/Post/components/PostCategoryBadge/index.tsx create mode 100644 src/domain/Post/components/PostCategoryBadge/style.css.ts create mode 100644 src/domain/Post/components/PostPreviewCard/index.tsx create mode 100644 src/domain/Post/components/PostPreviewCard/style.css.ts create mode 100644 src/domain/Post/components/PostPreviewItem/index.tsx create mode 100644 src/domain/Post/components/PostPreviewItem/style.css.ts create mode 100644 src/domain/Post/hooks/useReadCommunityHotPosts.ts rename src/domain/Post/{ => hooks}/useReadCommunityPostsAll.ts (100%) rename src/domain/Post/{ => hooks}/useReadCommunityPostsByBoard.ts (100%) create mode 100644 src/features/Community/HotPostPreview/index.tsx create mode 100644 src/features/Community/HotPostPreview/style.css.ts diff --git a/src/domain/Post/components/PostCategoryBadge/index.tsx b/src/domain/Post/components/PostCategoryBadge/index.tsx new file mode 100644 index 00000000..a308ec24 --- /dev/null +++ b/src/domain/Post/components/PostCategoryBadge/index.tsx @@ -0,0 +1,33 @@ +import { useMemo } from 'react' +import { LuBookText, LuMessageCircleQuestion, LuUsers } from 'react-icons/lu' +import { match } from 'ts-pattern' + +import * as s from './style.css' + +import { Typography } from '@/ui/Typography' + +type Props = { + boarName: string +} + +const PostCategoryBadge = ({ boarName }: Props) => { + const svg = useMemo( + () => + match(boarName) + .with('Community Board', () => ) + .with('Question Board', () => ) + .with('Information Board', () => ) + .otherwise(() => ), + [boarName], + ) + return ( +
+ {svg} + + {boarName} + +
+ ) +} + +export default PostCategoryBadge diff --git a/src/domain/Post/components/PostCategoryBadge/style.css.ts b/src/domain/Post/components/PostCategoryBadge/style.css.ts new file mode 100644 index 00000000..70623a40 --- /dev/null +++ b/src/domain/Post/components/PostCategoryBadge/style.css.ts @@ -0,0 +1,11 @@ +import { style } from '@vanilla-extract/css' + +import { f } from '@/style' + +export const Wrapper = style([ + f.flex, + f.alignCenter, + f.background.lightGray2, + f.color.static.darkGray1, + { padding: '0.25rem 0.625rem', gap: '0.375rem', borderRadius: '6px' }, +]) diff --git a/src/domain/Post/components/PostPreviewCard/index.tsx b/src/domain/Post/components/PostPreviewCard/index.tsx new file mode 100644 index 00000000..9287f38e --- /dev/null +++ b/src/domain/Post/components/PostPreviewCard/index.tsx @@ -0,0 +1,36 @@ +import { formatDistanceToNow } from 'date-fns' + +import * as s from './style.css' + +import Profile from '@/components/ui/profile' +import { PostPreviewWithBoardName } from '@/packages/api/ku-key/models' +import { Typography } from '@/ui/Typography' + +type Props = Pick +const CommunityPostPreviewCard = ({ user, createdAt, title }: Props) => { + const timeDistance = formatDistanceToNow(createdAt) + + return ( +
+ +
+
+ + {user.isAnonymous ? 'Anonymous' : user.username} + + + {timeDistance} ago + +
+ + {title} + +
+
+ ) +} + +export default CommunityPostPreviewCard diff --git a/src/domain/Post/components/PostPreviewCard/style.css.ts b/src/domain/Post/components/PostPreviewCard/style.css.ts new file mode 100644 index 00000000..820f09a1 --- /dev/null +++ b/src/domain/Post/components/PostPreviewCard/style.css.ts @@ -0,0 +1,20 @@ +import { style } from '@vanilla-extract/css' + +import { f } from '@/style' + +export const Wrapper = style([ + f.flex, + f.alignCenter, + f.wFull, + f.background.white, + { + padding: '0.375rem 1.25rem 0.375rem 0.625rem', + gap: '0.625rem', + borderRadius: '10px', + boxShadow: '0px 0px 4px 0px rgba(0, 0, 0, 0.25)', + }, +]) + +export const Body = style([f.flex, f.directionColumn, f.alignStart, { gap: '0.25rem', flex: '1 0 0' }]) + +export const Header = style([f.flex, f.alignCenter, f.justifyBetween, f.wFull]) diff --git a/src/domain/Post/components/PostPreviewItem/index.tsx b/src/domain/Post/components/PostPreviewItem/index.tsx new file mode 100644 index 00000000..31e32723 --- /dev/null +++ b/src/domain/Post/components/PostPreviewItem/index.tsx @@ -0,0 +1,18 @@ +import * as s from './style.css' + +import PostCategoryBadge from '@/domain/Post/components/PostCategoryBadge' +import CommunityPostPreviewCard from '@/domain/Post/components/PostPreviewCard' +import { PostPreviewWithBoardName } from '@/packages/api/ku-key/models' + +type Props = PostPreviewWithBoardName + +const PostPreviewItem = ({ user, createdAt, title, boardName }: Props) => { + return ( +
+ + +
+ ) +} + +export default PostPreviewItem diff --git a/src/domain/Post/components/PostPreviewItem/style.css.ts b/src/domain/Post/components/PostPreviewItem/style.css.ts new file mode 100644 index 00000000..0755678c --- /dev/null +++ b/src/domain/Post/components/PostPreviewItem/style.css.ts @@ -0,0 +1,5 @@ +import { style } from '@vanilla-extract/css' + +import { f } from '@/style' + +export const Wrapper = style([f.flex, f.directionColumn, f.alignStart, f.wFull, { gap: '0.625rem' }]) diff --git a/src/domain/Post/hooks/useReadCommunityHotPosts.ts b/src/domain/Post/hooks/useReadCommunityHotPosts.ts new file mode 100644 index 00000000..b855bb2c --- /dev/null +++ b/src/domain/Post/hooks/useReadCommunityHotPosts.ts @@ -0,0 +1,17 @@ +import { useSuspenseInfiniteQuery } from '@tanstack/react-query' + +import { useAsyncRead } from '@/common/hooks/useAsyncRead' +import { COMMUNITY_POSTS_QUERY_KEY } from '@/domain/Post/queries' +import { kuKeyClient } from '@/packages/api' +import { PostHotGetRequestParams } from '@/packages/api/ku-key/api/post-api' + +export const useReadCommunityHotPosts = ({ take = 10, cursor = undefined }: PostHotGetRequestParams) => { + const read = useAsyncRead(kuKeyClient.api.PostApi.postHotGet) + return useSuspenseInfiniteQuery({ + queryKey: COMMUNITY_POSTS_QUERY_KEY.hot({ take, cursor }), + queryFn: ({ pageParam: cursor }) => read({ take, cursor: cursor?.toString() }), + getNextPageParam: lastPage => (lastPage.meta.hasNextData ? lastPage.meta.nextCursor : undefined), + initialPageParam: cursor, + select: data => (data.pages ?? []).flatMap(page => page.data), + }) +} diff --git a/src/domain/Post/useReadCommunityPostsAll.ts b/src/domain/Post/hooks/useReadCommunityPostsAll.ts similarity index 100% rename from src/domain/Post/useReadCommunityPostsAll.ts rename to src/domain/Post/hooks/useReadCommunityPostsAll.ts diff --git a/src/domain/Post/useReadCommunityPostsByBoard.ts b/src/domain/Post/hooks/useReadCommunityPostsByBoard.ts similarity index 100% rename from src/domain/Post/useReadCommunityPostsByBoard.ts rename to src/domain/Post/hooks/useReadCommunityPostsByBoard.ts diff --git a/src/domain/Post/queries.ts b/src/domain/Post/queries.ts index 15e9abee..c0911f25 100644 --- a/src/domain/Post/queries.ts +++ b/src/domain/Post/queries.ts @@ -1,7 +1,12 @@ -import { PostAllGetRequestParams, PostGetRequestParams } from '@/packages/api/ku-key/api/post-api' +import { + PostAllGetRequestParams, + PostGetRequestParams, + PostHotGetRequestParams, +} from '@/packages/api/ku-key/api/post-api' export const COMMUNITY_POSTS_QUERY_KEY = { base: () => ['communityPosts'] as const, all: (params: PostAllGetRequestParams) => [...COMMUNITY_POSTS_QUERY_KEY.base(), 'all', params] as const, byBoard: (params: PostGetRequestParams) => [...COMMUNITY_POSTS_QUERY_KEY.base(), 'byBoard', params] as const, + hot: (params: PostHotGetRequestParams) => [...COMMUNITY_POSTS_QUERY_KEY.base(), 'hot', params] as const, } diff --git a/src/features/Community/CommunityOutlet/index.tsx b/src/features/Community/CommunityOutlet/index.tsx index cd1c6d0c..fa45728f 100644 --- a/src/features/Community/CommunityOutlet/index.tsx +++ b/src/features/Community/CommunityOutlet/index.tsx @@ -1,7 +1,7 @@ import * as s from './style.css' -import HotBoardPreview from '@/components/community/HotBoard/HotBoardPreview' import CommunityPostDetail from '@/features/Community/CommunityPostDetail' +import CommunityHotPostPreview from '@/features/Community/HotPostPreview' import CommunityPostSearch from '@/features/Community/PostSearch' const CommunityOutlet = () => { @@ -11,7 +11,7 @@ const CommunityOutlet = () => {
- + ) } diff --git a/src/features/Community/CommunityOutlet/style.css.ts b/src/features/Community/CommunityOutlet/style.css.ts index 478e4608..6f735af1 100644 --- a/src/features/Community/CommunityOutlet/style.css.ts +++ b/src/features/Community/CommunityOutlet/style.css.ts @@ -2,6 +2,6 @@ import { style } from '@vanilla-extract/css' import { f } from '@/style' -export const Wrapper = style([f.flex, f.directionRow, f.alignStart, { gap: '3.75rem' }]) +export const Wrapper = style([f.flex, f.directionRow, f.alignStart, f.wFull, { gap: '3.75rem', maxWidth: '70.75rem' }]) export const LeftWrapper = style([f.flex, f.directionColumn, f.wFull, { gap: '5.375rem', maxWidth: '44.5625rem' }]) diff --git a/src/features/Community/HotPostPreview/index.tsx b/src/features/Community/HotPostPreview/index.tsx new file mode 100644 index 00000000..b47c211d --- /dev/null +++ b/src/features/Community/HotPostPreview/index.tsx @@ -0,0 +1,28 @@ +import * as s from './style.css' + +import PostPreviewItem from '@/domain/Post/components/PostPreviewItem' +import { useReadCommunityHotPosts } from '@/domain/Post/hooks/useReadCommunityHotPosts' +import { Typography } from '@/ui/Typography' + +const CommunityHotPostPreview = () => { + const { data: hotPosts } = useReadCommunityHotPosts({ take: 5 }) + return ( +
+
+
+ Hot Posts + + Check out most popular posts + +
+
+
+ {hotPosts.map(post => ( + + ))} +
+
+ ) +} + +export default CommunityHotPostPreview diff --git a/src/features/Community/HotPostPreview/style.css.ts b/src/features/Community/HotPostPreview/style.css.ts new file mode 100644 index 00000000..707844e4 --- /dev/null +++ b/src/features/Community/HotPostPreview/style.css.ts @@ -0,0 +1,11 @@ +import { style } from '@vanilla-extract/css' + +import { f } from '@/style' + +export const Wrapper = style([f.flex, f.directionColumn, f.alignStart, { width: '22.4375rem', gap: '3rem' }]) + +export const TitleWrapper = style([f.flex, f.justifyBetween, f.wFull, f.alignEnd]) + +export const Title = style([f.flex, f.directionColumn, f.alignStart, { gap: '0.5rem' }]) + +export const ItemWrapper = style([f.flex, f.directionColumn, f.alignStart, f.wFull, { gap: '0.625rem' }]) diff --git a/src/features/Community/PostAll/index.tsx b/src/features/Community/PostAll/index.tsx index 8fcabbbf..6d9dc2ad 100644 --- a/src/features/Community/PostAll/index.tsx +++ b/src/features/Community/PostAll/index.tsx @@ -1,7 +1,7 @@ import * as s from './style.css' import PostPreview from '@/components/community/PostPreview' -import { useReadCommunityPostsAll } from '@/domain/Post/useReadCommunityPostsAll' +import { useReadCommunityPostsAll } from '@/domain/Post/hooks/useReadCommunityPostsAll' import Pagination from '@/ui/Pagination' import { useQueryParams } from '@/util/hooks/useQueryParams' diff --git a/src/features/Community/PostByBoard/index.tsx b/src/features/Community/PostByBoard/index.tsx index ceab9cbf..9e9b0a1a 100644 --- a/src/features/Community/PostByBoard/index.tsx +++ b/src/features/Community/PostByBoard/index.tsx @@ -1,7 +1,7 @@ import * as s from './style.css' import BoardPostPreview from '@/components/community/Boards/BoardPostPreview' -import { useReadCommunityPosts } from '@/domain/Post/useReadCommunityPostsByBoard' +import { useReadCommunityPosts } from '@/domain/Post/hooks/useReadCommunityPostsByBoard' import { BoardQueryParam } from '@/features/Community/CommunitySelectTab' import Pagination from '@/ui/Pagination' import { useQueryParams } from '@/util/hooks/useQueryParams' diff --git a/src/lib/router/lazy-route.tsx b/src/lib/router/lazy-route.tsx index 1eca941e..93c0f868 100644 --- a/src/lib/router/lazy-route.tsx +++ b/src/lib/router/lazy-route.tsx @@ -12,6 +12,7 @@ const withSuspense = (LazyComponent: ComponentType) => { export const ClubPage = withSuspense(lazy(() => import('@/pages/ClubPage'))) export const MainCommunityPage = withSuspense(lazy(() => import('@/pages/CommunityPage'))) +export const CommunityAllPage = withSuspense(lazy(() => import('@/pages/Community/All'))) export const BoardPage = withSuspense(lazy(() => import('@/pages/CommunityPage/BoardPage'))) export const HotBoardPage = withSuspense(lazy(() => import('@/pages/CommunityPage/HotBoardPage'))) export const PostViewPage = withSuspense(lazy(() => import('@/pages/CommunityPage/PostViewPage'))) diff --git a/src/lib/router/router.tsx b/src/lib/router/router.tsx index 74a66d8b..303f8291 100644 --- a/src/lib/router/router.tsx +++ b/src/lib/router/router.tsx @@ -5,6 +5,7 @@ import { BoardPage, ClubDetailPage, ClubPage, + CommunityAllPage, CourseInfoPage, CourseReviewPage, FriendPage, @@ -22,7 +23,6 @@ import { WriteReviewPage, } from '@/lib/router/lazy-route' import ProtectedRoutes from '@/lib/router/ProtectedRoutes' -import CommunityAllPage from '@/pages/Community/All' import HomePage from '@/pages/Home' import LandingPage from '@/pages/LandingPage' import Login from '@/pages/LoginPage' diff --git a/src/ui/Pagination/index.tsx b/src/ui/Pagination/index.tsx index 14bb56f7..fa795822 100644 --- a/src/ui/Pagination/index.tsx +++ b/src/ui/Pagination/index.tsx @@ -35,7 +35,7 @@ const Pagination = ({ items, render, hasNextPage, isFetchingNextPage, fetchN return ( <> -
    {items.map((item, index) => render(item, index))}
+
    {items.map(render)}
) diff --git a/src/ui/Typography/index.tsx b/src/ui/Typography/index.tsx index 109945bd..eb64ae59 100644 --- a/src/ui/Typography/index.tsx +++ b/src/ui/Typography/index.tsx @@ -1,4 +1,5 @@ import { forwardRef } from 'react' +import { match, P } from 'ts-pattern' import { vars } from '@/theme/theme.css' import { useMediaQueryByName } from '@/util/hooks/useMediaQueryByName' @@ -15,19 +16,34 @@ type Props = { export type ColorValue = keyof typeof vars.color export const Typography = forwardRef( - ({ children, color = 'black', typography = 'display1B', style, mobileTypography, ...rest }, ref) => { + ({ children, color = 'black', typography, style, mobileTypography, ...rest }, ref) => { const isMobile = useMediaQueryByName('smDown') const getColor = (color: ColorValue) => { return vars.color[color] } - const getTypography = (typography: keyof typeof vars.typography.desktop | keyof typeof vars.typography.mobile) => { - if (isMobile) { - if (mobileTypography) { - return vars.typography.mobile[mobileTypography] - } - return vars.typography.mobile[typography as keyof typeof vars.typography.mobile] - } - return vars.typography.desktop[typography as keyof typeof vars.typography.desktop] + + const baseTypography = isMobile ? vars.typography.mobile.bodyM : vars.typography.desktop.body1M + const getTypography = () => { + return match({ isMobile, typography, mobileTypography }) + .with( + { isMobile: true, mobileTypography: P.not(undefined) }, + ({ mobileTypography }) => vars.typography.mobile[mobileTypography], + ) + .with( + { isMobile: true, typography: P.not(undefined) }, + ({ typography }) => vars.typography.mobile[typography as keyof typeof vars.typography.mobile], + ) + .with({ isMobile: true }, () => baseTypography) + .with( + { typography: undefined, mobileTypography: P.not(undefined) }, + ({ mobileTypography }) => vars.typography.mobile[mobileTypography], + ) + .with( + { typography: P.not(undefined) }, + ({ typography }) => vars.typography.desktop[typography as keyof typeof vars.typography.desktop], + ) + .with({ typography: undefined }, () => baseTypography) + .exhaustive() } return ( @@ -36,7 +52,7 @@ export const Typography = forwardRef( style={{ color: getColor(color), ...style, - ...getTypography(typography), + ...getTypography(), display: 'inline-block', }} {...rest} From 006a8a0e7d01bb5391539599989ed884db74d147 Mon Sep 17 00:00:00 2001 From: halion Date: Mon, 31 Mar 2025 13:44:04 +0900 Subject: [PATCH 4/5] =?UTF-8?q?[#220]=20=EC=88=98=EC=A0=95=EB=90=9C=20Time?= =?UTF-8?q?table=20DTO=EC=97=90=20=EB=8C=80=EC=9D=91=ED=95=B4=EC=9A=94=20(?= =?UTF-8?q?#221)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix!: responded to changed timetable api DTO * fix: post 요청시 queryData 패칭에 DTO 올바르게 적용 --- src/api/hooks/friends.ts | 4 +- src/api/hooks/schedule.ts | 56 ++++++++++--------- src/api/hooks/timetable.ts | 21 ++++--- src/api/types/timetable.ts | 10 +--- .../timetable/Friend/FriendTimetable.tsx | 11 +++- src/components/timetable/Grid/LectureGrid.tsx | 4 +- .../timetable/Grid/NullTable/index.tsx | 15 +++++ .../timetable/Grid/NullTable/style.css.ts | 17 ++++++ .../timetable/Grid/TimetableLayout.tsx | 22 ++++---- src/components/timetable/index.tsx | 1 - src/types/timetable.ts | 7 +++ 11 files changed, 106 insertions(+), 62 deletions(-) create mode 100644 src/components/timetable/Grid/NullTable/index.tsx create mode 100644 src/components/timetable/Grid/NullTable/style.css.ts diff --git a/src/api/hooks/friends.ts b/src/api/hooks/friends.ts index ff0e1a36..728ce848 100644 --- a/src/api/hooks/friends.ts +++ b/src/api/hooks/friends.ts @@ -10,7 +10,7 @@ import { PatchFriendshipRequestRequest, PostFriendshipRequest, } from '@/api/types/friends' -import { GetFriendTimetableResponse } from '@/api/types/timetable' +import { GetTimetableByTimetableIdResponse } from '@/api/types/timetable' import { apiInterface } from '@/util/axios/custom-axios' const getFriendList = async ({ keyword }: GetFriendListRequest) => { @@ -141,7 +141,7 @@ export const useDeleteFriendship = () => { } const getFriendTimetable = async (props: GetFriendTimetableRequest) => { - const response = await apiInterface.get(`/friendship/friend-timetable`, { + const response = await apiInterface.get(`/friendship/friend-timetable`, { params: props, }) return response.data diff --git a/src/api/hooks/schedule.ts b/src/api/hooks/schedule.ts index eec9f4b8..c54dc412 100644 --- a/src/api/hooks/schedule.ts +++ b/src/api/hooks/schedule.ts @@ -30,19 +30,21 @@ export const usePostSchedule = () => { queryClient.setQueryData( ['timetable', String(response.timetableId)], prevData => { - if (prevData !== undefined) { + if (prevData !== undefined && prevData.timetable !== null) { return { - ...prevData, - schedules: prevData.schedules.concat([ - { - location: response.location, - scheduleDay: response.day, - scheduleEndTime: response.endTime, - scheduleId: response.id, - scheduleStartTime: response.startTime, - scheduleTitle: response.title, - }, - ]), + timetable: { + ...prevData.timetable, + schedules: prevData.timetable.schedules.concat([ + { + location: response.location, + scheduleDay: response.day, + scheduleEndTime: response.endTime, + scheduleId: response.id, + scheduleStartTime: response.startTime, + scheduleTitle: response.title, + }, + ]), + }, } } }, @@ -100,22 +102,24 @@ export const usePatchSchedule = () => { queryClient.setQueryData( ['timetable', String(response.timetableId)], prevData => { - if (prevData !== undefined) { + if (prevData !== undefined && prevData.timetable !== null) { return { - ...prevData, - schedules: prevData.schedules.map(schedule => { - if (schedule.scheduleId === response.id) { - return { - location: response.location, - scheduleDay: response.day, - scheduleEndTime: response.endTime, - scheduleId: response.id, - scheduleStartTime: response.startTime, - scheduleTitle: response.title, + timetable: { + ...prevData.timetable, + schedules: prevData.timetable.schedules.map(schedule => { + if (schedule.scheduleId === response.id) { + return { + location: response.location, + scheduleDay: response.day, + scheduleEndTime: response.endTime, + scheduleId: response.id, + scheduleStartTime: response.startTime, + scheduleTitle: response.title, + } } - } - return schedule - }), + return schedule + }), + }, } } }, diff --git a/src/api/hooks/timetable.ts b/src/api/hooks/timetable.ts index e8cd5bdb..85cff0f7 100644 --- a/src/api/hooks/timetable.ts +++ b/src/api/hooks/timetable.ts @@ -31,9 +31,12 @@ export const useGetUserTimetableList = () => { } const INITIAL_TIMETABLE: GetTimetableByTimetableIdResponse = { - courses: [], - schedules: [], - color: 'Red', + timetable: { + timetableName: '', + courses: [], + schedules: [], + color: 'Red', + }, } const getTimetableByID = async ({ timetableId }: GetTimetableByTimetableIdRequest) => { @@ -184,12 +187,14 @@ export const useDeleteCourse = () => { onSuccess: (response, request) => { if (response.deleted) { queryClient.setQueryData(['timetable', request.timetableId], prevData => { - if (prevData !== undefined) { + if (prevData !== undefined && prevData.timetable !== null) { return { - ...prevData, - courses: prevData.courses.filter(course => { - return course.courseId !== request.courseId - }), + timetable: { + ...prevData.timetable, + courses: prevData.timetable.courses.filter(course => { + return course.courseId !== request.courseId + }), + }, } } }) diff --git a/src/api/types/timetable.ts b/src/api/types/timetable.ts index de4a49a9..027cfd3f 100644 --- a/src/api/types/timetable.ts +++ b/src/api/types/timetable.ts @@ -1,17 +1,11 @@ -import { ColorType, CourseType, ScheduleType, SemesterType, TimetableInfo } from '@/types/timetable' +import { ColorType, SemesterType, TimetableInfo, TimetableInterface } from '@/types/timetable' export interface GetTimetableByTimetableIdRequest { timetableId: number } export interface GetTimetableByTimetableIdResponse { - courses: CourseType[] - schedules: ScheduleType[] - color: ColorType -} - -export interface GetFriendTimetableResponse extends GetTimetableByTimetableIdResponse { - timetableName: string + timetable: TimetableInterface | null } export type GetTimetableByUserIdResponse = TimetableInfo[] diff --git a/src/components/timetable/Friend/FriendTimetable.tsx b/src/components/timetable/Friend/FriendTimetable.tsx index 71f4e355..3967e5ba 100644 --- a/src/components/timetable/Friend/FriendTimetable.tsx +++ b/src/components/timetable/Friend/FriendTimetable.tsx @@ -3,6 +3,7 @@ import { forwardRef } from 'react' import { useGetFriendTimetable } from '@/api/hooks/friends' import LectureGrid from '@/components/timetable/Grid/LectureGrid' +import NullTable from '@/components/timetable/Grid/NullTable' import { SemesterType } from '@/types/timetable' import { numberToSemester } from '@/util/timetableUtil' @@ -13,7 +14,11 @@ interface TimetableProps { } const FriendTimetable = forwardRef(({ user, year, semester }, ref) => { - const { data } = useGetFriendTimetable({ username: user, year, semester }) + const { + data: { timetable: timetableData }, + } = useGetFriendTimetable({ username: user, year, semester }) + + if (timetableData === null) return return (
@@ -48,11 +53,11 @@ const FriendTimetable = forwardRef(({ user, year textOverflow: 'ellipsis', })} > - {data.timetableName} + {timetableData.timetableName}
- + ) }) diff --git a/src/components/timetable/Grid/LectureGrid.tsx b/src/components/timetable/Grid/LectureGrid.tsx index 175b32cf..cff8eddc 100644 --- a/src/components/timetable/Grid/LectureGrid.tsx +++ b/src/components/timetable/Grid/LectureGrid.tsx @@ -1,14 +1,14 @@ import { css } from '@styled-system/css' import { useMemo } from 'react' -import { GetTimetableByTimetableIdResponse } from '@/api/types/timetable' import NoScheduledArea from '@/components/timetable/Grid/NoScheduledArea' import ScheduledArea from '@/components/timetable/Grid/ScheduledArea' +import { TimetableInterface } from '@/types/timetable' import { getWeeknTimeList, lectureDataPreprocess } from '@/util/timetableUtil' interface LectureGridProps { timetableId?: number // 친구 시간표의 경우 undefined - timetableData: GetTimetableByTimetableIdResponse + timetableData: TimetableInterface isMine?: boolean } diff --git a/src/components/timetable/Grid/NullTable/index.tsx b/src/components/timetable/Grid/NullTable/index.tsx new file mode 100644 index 00000000..08cfabc4 --- /dev/null +++ b/src/components/timetable/Grid/NullTable/index.tsx @@ -0,0 +1,15 @@ +import * as s from './style.css' + +import { Typography } from '@/ui/Typography' + +const NullTable = () => { + return ( +
+ + Timetable hasn't been created yet! + +
+ ) +} + +export default NullTable diff --git a/src/components/timetable/Grid/NullTable/style.css.ts b/src/components/timetable/Grid/NullTable/style.css.ts new file mode 100644 index 00000000..a2962ced --- /dev/null +++ b/src/components/timetable/Grid/NullTable/style.css.ts @@ -0,0 +1,17 @@ +import { style } from '@vanilla-extract/css' + +import { f } from '@/style' +import { vars } from '@/theme/theme.css' + +export const Wrapper = style([ + f.wFull, + f.hFull, + f.flexCenter, + { + height: '30rem', + backgroundColor: vars.color.bgGray, + borderRadius: '10px', + border: '1px solid', + borderColor: vars.color.lightGray1, + }, +]) diff --git a/src/components/timetable/Grid/TimetableLayout.tsx b/src/components/timetable/Grid/TimetableLayout.tsx index 3648de0f..375feb40 100644 --- a/src/components/timetable/Grid/TimetableLayout.tsx +++ b/src/components/timetable/Grid/TimetableLayout.tsx @@ -2,6 +2,7 @@ import { memo } from 'react' import { useGetTimetable } from '@/api/hooks/timetable' import LectureGrid from '@/components/timetable/Grid/LectureGrid' +import NullTable from '@/components/timetable/Grid/NullTable' import TimetableModal from '@/components/timetable/Modal/TimetableModal' import { GlobalModalStateType } from '@/types/timetable' @@ -10,7 +11,6 @@ interface TimetableLayoutProps { globalModalState: GlobalModalStateType closeTimetableModal: () => void deleteTimetableHandler: (timetableId: number) => void - timetableName: string } /** @@ -18,25 +18,23 @@ interface TimetableLayoutProps { * 시간표 제목 헤더를 제외한 실제 그리드를 구성합니다 */ const TimetableLayout = memo( - ({ - timetableId, - globalModalState, - closeTimetableModal, - deleteTimetableHandler, - timetableName, - }: TimetableLayoutProps) => { - const { data } = useGetTimetable({ timetableId }) + ({ timetableId, globalModalState, closeTimetableModal, deleteTimetableHandler }: TimetableLayoutProps) => { + const { + data: { timetable: timetableData }, + } = useGetTimetable({ timetableId }) + + if (timetableData === null) return return ( <> - + ) diff --git a/src/components/timetable/index.tsx b/src/components/timetable/index.tsx index 93bf6cde..555daa0a 100644 --- a/src/components/timetable/index.tsx +++ b/src/components/timetable/index.tsx @@ -152,7 +152,6 @@ const Timetable = forwardRef( globalModalState={globalModalState} closeTimetableModal={closeTimetableModal} deleteTimetableHandler={deleteTimetableHandler} - timetableName={timetableName} /> ) diff --git a/src/types/timetable.ts b/src/types/timetable.ts index 3de4dea4..b61384a4 100644 --- a/src/types/timetable.ts +++ b/src/types/timetable.ts @@ -60,3 +60,10 @@ export interface GridType extends TimetableContentsType { endTime: string day: DayType } + +export interface TimetableInterface { + timetableName: string + courses: CourseType[] + schedules: ScheduleType[] + color: ColorType +} From c8588a46fd1d04020a6c0121860c12567e9e4b9e Mon Sep 17 00:00:00 2001 From: Seungmin Cha <75214259+Virtuso1225@users.noreply.github.com> Date: Tue, 1 Apr 2025 20:32:39 +0900 Subject: [PATCH 5/5] =?UTF-8?q?feat:=20=EB=B3=80=EA=B2=BD=EB=90=9C=20?= =?UTF-8?q?=EA=B2=8C=EC=8B=9C=EA=B8=80=20=EC=95=84=EC=9D=B4=ED=85=9C=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=EB=A5=BC=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=ED=95=98=EA=B3=A0=20=EB=B0=98=EC=98=81=ED=95=B4?= =?UTF-8?q?=EC=9A=94.=20-=206=20(#201)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 게시글 아이템에 카테고리 배지를 보여줘요. * feat: minor 스타일 조정 * feat: 변수명 확실하게 변경하기 * feat: PR 리뷰 반영 --- src/domain/Post/components/FeedItem/index.tsx | 151 ++++++++++++++++++ .../Post/components/FeedItem/style.css.ts | 39 +++++ .../components/PostCategoryBadge/index.tsx | 10 +- .../Post/components/PostPreviewItem/index.tsx | 2 +- src/domain/Post/util/parseBoardName.ts | 16 ++ src/features/Community/PostAll/index.tsx | 14 +- src/features/Community/PostByBoard/index.tsx | 28 ++-- src/pages/Community/All/style.css.ts | 2 +- src/types/community.ts | 1 - src/ui/Typography/index.tsx | 2 +- 10 files changed, 226 insertions(+), 39 deletions(-) create mode 100644 src/domain/Post/components/FeedItem/index.tsx create mode 100644 src/domain/Post/components/FeedItem/style.css.ts create mode 100644 src/domain/Post/util/parseBoardName.ts diff --git a/src/domain/Post/components/FeedItem/index.tsx b/src/domain/Post/components/FeedItem/index.tsx new file mode 100644 index 00000000..02db046f --- /dev/null +++ b/src/domain/Post/components/FeedItem/index.tsx @@ -0,0 +1,151 @@ +import { formatDistanceToNow } from 'date-fns' +import { useCallback } from 'react' +import { HiOutlineEye } from 'react-icons/hi' +import { LuBookmark, LuCookie, LuMessageCircle } from 'react-icons/lu' +import { useNavigate } from 'react-router-dom' + +import * as s from './style.css' + +import Profile from '@/components/ui/profile' +import PostCategoryBadge from '@/domain/Post/components/PostCategoryBadge' +import { parseBoardName } from '@/domain/Post/util/parseBoardName' +import { PostPreviewWithBoardName } from '@/packages/api/ku-key/models' +import { vars } from '@/theme/theme.css' +import { Typography } from '@/ui/Typography' + +type Props = Pick< + PostPreviewWithBoardName, + | 'id' + | 'title' + | 'content' + | 'createdAt' + | 'user' + | 'views' + | 'reactionCount' + | 'commentCount' + | 'scrapCount' + | 'commentCount' + | 'scrapCount' + | 'myScrap' + | 'thumbnailDir' + | 'boardName' +> & { + showCommunityBadge?: boolean +} + +const FeedItem = ({ + id, + title, + content, + createdAt, + user, + views, + reactionCount, + commentCount, + scrapCount, + myScrap, + thumbnailDir, + boardName, + showCommunityBadge, +}: Props) => { + const timeDistance = formatDistanceToNow(createdAt) + const navigate = useNavigate() + const parsedBoardName = parseBoardName(boardName) + + const handleNavigate = useCallback( + () => navigate(`/community/${parsedBoardName}/post/${id}`), + [navigate, parsedBoardName, id], + ) + + return ( + + ) +} + +export default FeedItem diff --git a/src/domain/Post/components/FeedItem/style.css.ts b/src/domain/Post/components/FeedItem/style.css.ts new file mode 100644 index 00000000..b94a800b --- /dev/null +++ b/src/domain/Post/components/FeedItem/style.css.ts @@ -0,0 +1,39 @@ +import { style } from '@vanilla-extract/css' + +import { f } from '@/style' + +export const Wrapper = style([ + f.flex, + f.directionColumn, + f.alignStart, + f.wFull, + f.cursorPointer, + { + gap: '2.5rem', + paddingTop: '2.5rem', + borderRadius: '10px', + boxShadow: '0px 0px 4px 0px rgba(0, 0, 0, 0.25)', + }, +]) + +export const ContentsWrapper = style([ + f.flex, + f.directionColumn, + f.alignStart, + f.wFull, + { gap: '1.25rem', padding: '0 1.25rem' }, +]) + +export const Header = style([f.flex, f.justifyBetween, f.alignCenter, f.wFull]) + +export const LeftWrapper = style([f.flex, f.alignCenter, { gap: '1.25rem' }]) + +export const ViewCount = style([f.flex, f.alignCenter, f.color.static.darkGray2, { gap: '0.25rem' }]) + +export const FeedInfo = style([f.flex, f.justifyBetween, f.alignCenter, f.wFull, { padding: '2.5rem 1.25rem' }]) + +export const User = style([f.flex, f.alignCenter, { gap: '0.625rem' }]) + +export const FeedBack = style([f.flex, f.alignCenter, f.color.static.darkGray2, { gap: '1rem' }]) + +export const Icon = style([f.flex, f.alignCenter, { gap: '0.25rem' }]) diff --git a/src/domain/Post/components/PostCategoryBadge/index.tsx b/src/domain/Post/components/PostCategoryBadge/index.tsx index a308ec24..da7155a7 100644 --- a/src/domain/Post/components/PostCategoryBadge/index.tsx +++ b/src/domain/Post/components/PostCategoryBadge/index.tsx @@ -7,24 +7,24 @@ import * as s from './style.css' import { Typography } from '@/ui/Typography' type Props = { - boarName: string + boardName: string } -const PostCategoryBadge = ({ boarName }: Props) => { +const PostCategoryBadge = ({ boardName }: Props) => { const svg = useMemo( () => - match(boarName) + match(boardName) .with('Community Board', () => ) .with('Question Board', () => ) .with('Information Board', () => ) .otherwise(() => ), - [boarName], + [boardName], ) return (
{svg} - {boarName} + {boardName}
) diff --git a/src/domain/Post/components/PostPreviewItem/index.tsx b/src/domain/Post/components/PostPreviewItem/index.tsx index 31e32723..7c2a81a0 100644 --- a/src/domain/Post/components/PostPreviewItem/index.tsx +++ b/src/domain/Post/components/PostPreviewItem/index.tsx @@ -9,7 +9,7 @@ type Props = PostPreviewWithBoardName const PostPreviewItem = ({ user, createdAt, title, boardName }: Props) => { return (
- +
) diff --git a/src/domain/Post/util/parseBoardName.ts b/src/domain/Post/util/parseBoardName.ts new file mode 100644 index 00000000..9299f611 --- /dev/null +++ b/src/domain/Post/util/parseBoardName.ts @@ -0,0 +1,16 @@ +/** + * 게시판 이름을 파싱하여 소문자로 반환합니다. + * @param boardName - 게시판 이름 + * @returns 소문자로 변환된 게시판 이름 + */ +export const parseBoardName = (boardName: string) => { + const parsedBoardName = boardName.split(' ')[0].toLowerCase() + if (board.includes(parsedBoardName as BoardType)) { + return parsedBoardName as BoardType + } + return 'community' as const +} + +const board = ['community', 'information', 'question'] as const + +type BoardType = (typeof board)[number] diff --git a/src/features/Community/PostAll/index.tsx b/src/features/Community/PostAll/index.tsx index 6d9dc2ad..a030464a 100644 --- a/src/features/Community/PostAll/index.tsx +++ b/src/features/Community/PostAll/index.tsx @@ -1,6 +1,6 @@ import * as s from './style.css' -import PostPreview from '@/components/community/PostPreview' +import FeedItem from '@/domain/Post/components/FeedItem' import { useReadCommunityPostsAll } from '@/domain/Post/hooks/useReadCommunityPostsAll' import Pagination from '@/ui/Pagination' import { useQueryParams } from '@/util/hooks/useQueryParams' @@ -26,17 +26,7 @@ const CommunityPostAll = () => { ( - - )} + render={post => } hasNextPage={hasNextPage} isFetchingNextPage={isFetchingNextPage} fetchNextPage={fetchNextPage} diff --git a/src/features/Community/PostByBoard/index.tsx b/src/features/Community/PostByBoard/index.tsx index 9e9b0a1a..7771d65c 100644 --- a/src/features/Community/PostByBoard/index.tsx +++ b/src/features/Community/PostByBoard/index.tsx @@ -1,6 +1,8 @@ +import { match } from 'ts-pattern' + import * as s from './style.css' -import BoardPostPreview from '@/components/community/Boards/BoardPostPreview' +import FeedItem from '@/domain/Post/components/FeedItem' import { useReadCommunityPosts } from '@/domain/Post/hooks/useReadCommunityPostsByBoard' import { BoardQueryParam } from '@/features/Community/CommunitySelectTab' import Pagination from '@/ui/Pagination' @@ -22,29 +24,19 @@ const CommunityPostByBoard = () => { keyword: queryParam.keyword, }) + const boardName = match(queryParam.board) + .with('CommunityBoard', () => 'community') + .with('QuestionBoard', () => 'question') + .with('InformationBoard', () => 'information') + .otherwise(() => 'community') + if (!posts.length) return
No Search Result
return ( ( - - )} + render={post => } hasNextPage={hasNextPage} isFetchingNextPage={isFetchingNextPage} fetchNextPage={fetchNextPage} diff --git a/src/pages/Community/All/style.css.ts b/src/pages/Community/All/style.css.ts index 38030c92..1dc496d0 100644 --- a/src/pages/Community/All/style.css.ts +++ b/src/pages/Community/All/style.css.ts @@ -4,4 +4,4 @@ import { f } from '@/style' export const Wrapper = style([f.flex, f.directionColumn, f.alignCenter, { marginTop: '2.5rem', gap: '2.5rem' }]) -export const SelectTabWrapper = style([f.flex, f.alignStart, { width: '66.6875rem' }]) +export const SelectTabWrapper = style([f.flex, f.alignStart, { width: '70.75rem' }]) diff --git a/src/types/community.ts b/src/types/community.ts index 8d20246c..88e97815 100644 --- a/src/types/community.ts +++ b/src/types/community.ts @@ -35,7 +35,6 @@ export interface PostPreviewByBoardMeta { } export type BoardPostPreviewProps = Omit export type BoardType = 'Community Board' | 'Information Board' | 'Question Board' - export interface CommentProps { id: number isDeleted: boolean diff --git a/src/ui/Typography/index.tsx b/src/ui/Typography/index.tsx index eb64ae59..71dfbbcc 100644 --- a/src/ui/Typography/index.tsx +++ b/src/ui/Typography/index.tsx @@ -50,10 +50,10 @@ export const Typography = forwardRef(