-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
27 changed files
with
465 additions
and
44 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
13 changes: 13 additions & 0 deletions
13
frontend/src/components/Recipe/CommentForm/CommentForm.stories.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import type { Meta, StoryObj } from '@storybook/react'; | ||
|
||
import CommentForm from './CommentForm'; | ||
|
||
const meta: Meta<typeof CommentForm> = { | ||
title: 'recipe/CommentForm', | ||
component: CommentForm, | ||
}; | ||
|
||
export default meta; | ||
type Story = StoryObj<typeof meta>; | ||
|
||
export const Default: Story = {}; |
101 changes: 101 additions & 0 deletions
101
frontend/src/components/Recipe/CommentForm/CommentForm.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
import { Button, Spacing, Text, Textarea, useTheme } from '@fun-eat/design-system'; | ||
import type { ChangeEventHandler, FormEventHandler } from 'react'; | ||
import { useState } from 'react'; | ||
import styled from 'styled-components'; | ||
|
||
import { SvgIcon } from '@/components/Common'; | ||
import { useToastActionContext } from '@/hooks/context'; | ||
import { useRecipeCommentMutation } from '@/hooks/queries/recipe'; | ||
|
||
interface CommentFormProps { | ||
recipeId: number; | ||
} | ||
|
||
const MAX_COMMENT_LENGTH = 200; | ||
|
||
const CommentForm = ({ recipeId }: CommentFormProps) => { | ||
const [commentValue, setCommentValue] = useState(''); | ||
const { mutate } = useRecipeCommentMutation(recipeId); | ||
|
||
const theme = useTheme(); | ||
const { toast } = useToastActionContext(); | ||
|
||
const handleCommentInput: ChangeEventHandler<HTMLTextAreaElement> = (e) => { | ||
setCommentValue(e.target.value); | ||
}; | ||
|
||
const handleSubmitComment: FormEventHandler<HTMLFormElement> = (e) => { | ||
e.preventDefault(); | ||
|
||
mutate( | ||
{ comment: commentValue }, | ||
{ | ||
onSuccess: () => { | ||
setCommentValue(''); | ||
toast.success('댓글이 등록되었습니다.'); | ||
}, | ||
onError: (error) => { | ||
if (error instanceof Error) { | ||
toast.error(error.message); | ||
return; | ||
} | ||
|
||
toast.error('댓글을 등록하는데 오류가 발생했습니다.'); | ||
}, | ||
} | ||
); | ||
}; | ||
|
||
return ( | ||
<CommentFormContainer> | ||
<Form onSubmit={handleSubmitComment}> | ||
<CommentTextarea | ||
placeholder="댓글을 입력하세요. (200자)" | ||
value={commentValue} | ||
onChange={handleCommentInput} | ||
maxLength={MAX_COMMENT_LENGTH} | ||
/> | ||
<SubmitButton variant="transparent" disabled={commentValue.length === 0}> | ||
<SvgIcon | ||
variant="plane" | ||
width={30} | ||
height={30} | ||
color={commentValue.length === 0 ? theme.colors.gray2 : theme.colors.gray4} | ||
/> | ||
</SubmitButton> | ||
</Form> | ||
<Spacing size={8} /> | ||
<Text size="xs" color={theme.textColors.info} align="right"> | ||
{commentValue.length}자 / {MAX_COMMENT_LENGTH}자 | ||
</Text> | ||
</CommentFormContainer> | ||
); | ||
}; | ||
|
||
export default CommentForm; | ||
|
||
const CommentFormContainer = styled.div` | ||
position: fixed; | ||
bottom: 0; | ||
width: calc(100% - 40px); | ||
max-width: 540px; | ||
padding: 16px 0; | ||
background: ${({ theme }) => theme.backgroundColors.default}; | ||
`; | ||
|
||
const Form = styled.form` | ||
display: flex; | ||
gap: 4px; | ||
justify-content: space-around; | ||
align-items: center; | ||
`; | ||
|
||
const CommentTextarea = styled(Textarea)` | ||
height: 50px; | ||
padding: 8px; | ||
font-size: 1.4rem; | ||
`; | ||
|
||
const SubmitButton = styled(Button)` | ||
cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')}; | ||
`; |
18 changes: 18 additions & 0 deletions
18
frontend/src/components/Recipe/CommentItem/CommentItem.stories.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import type { Meta, StoryObj } from '@storybook/react'; | ||
|
||
import CommentItem from './CommentItem'; | ||
|
||
import comments from '@/mocks/data/comments.json'; | ||
|
||
const meta: Meta<typeof CommentItem> = { | ||
title: 'recipe/CommentItem', | ||
component: CommentItem, | ||
args: { | ||
recipeComment: comments.comments[0], | ||
}, | ||
}; | ||
|
||
export default meta; | ||
type Story = StoryObj<typeof meta>; | ||
|
||
export const Default: Story = {}; |
50 changes: 50 additions & 0 deletions
50
frontend/src/components/Recipe/CommentItem/CommentItem.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import { Divider, Spacing, Text, useTheme } from '@fun-eat/design-system'; | ||
import styled from 'styled-components'; | ||
|
||
import type { Comment } from '@/types/recipe'; | ||
import { getFormattedDate } from '@/utils/date'; | ||
|
||
interface CommentItemProps { | ||
recipeComment: Comment; | ||
} | ||
|
||
const CommentItem = ({ recipeComment }: CommentItemProps) => { | ||
const theme = useTheme(); | ||
const { author, comment, createdAt } = recipeComment; | ||
|
||
return ( | ||
<> | ||
<AuthorWrapper> | ||
<AuthorProfileImage src={author.profileImage} alt={`${author.nickname}님의 프로필`} width={32} height={32} /> | ||
<div> | ||
<Text size="xs" color={theme.textColors.info}> | ||
{author.nickname} 님 | ||
</Text> | ||
<Text size="xs" color={theme.textColors.info}> | ||
{getFormattedDate(createdAt)} | ||
</Text> | ||
</div> | ||
</AuthorWrapper> | ||
<CommentContent size="sm">{comment}</CommentContent> | ||
<Divider variant="disabled" /> | ||
<Spacing size={16} /> | ||
</> | ||
); | ||
}; | ||
|
||
export default CommentItem; | ||
|
||
const AuthorWrapper = styled.div` | ||
display: flex; | ||
gap: 12px; | ||
align-items: center; | ||
`; | ||
|
||
const AuthorProfileImage = styled.img` | ||
border: 1px solid ${({ theme }) => theme.colors.primary}; | ||
border-radius: 50%; | ||
`; | ||
|
||
const CommentContent = styled(Text)` | ||
margin: 16px 0; | ||
`; |
13 changes: 13 additions & 0 deletions
13
frontend/src/components/Recipe/CommentList/CommentList.stories.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import type { Meta, StoryObj } from '@storybook/react'; | ||
|
||
import CommentList from './CommentList'; | ||
|
||
const meta: Meta<typeof CommentList> = { | ||
title: 'recipe/CommentList', | ||
component: CommentList, | ||
}; | ||
|
||
export default meta; | ||
type Story = StoryObj<typeof meta>; | ||
|
||
export const Default: Story = {}; |
35 changes: 35 additions & 0 deletions
35
frontend/src/components/Recipe/CommentList/CommentList.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import { Heading, Spacing } from '@fun-eat/design-system'; | ||
import { useRef } from 'react'; | ||
|
||
import CommentItem from '../CommentItem/CommentItem'; | ||
|
||
import { useIntersectionObserver } from '@/hooks/common'; | ||
import { useInfiniteRecipeCommentQuery } from '@/hooks/queries/recipe'; | ||
|
||
interface CommentListProps { | ||
recipeId: number; | ||
} | ||
|
||
const CommentList = ({ recipeId }: CommentListProps) => { | ||
const scrollRef = useRef<HTMLDivElement>(null); | ||
|
||
const { fetchNextPage, hasNextPage, data } = useInfiniteRecipeCommentQuery(Number(recipeId)); | ||
useIntersectionObserver<HTMLDivElement>(fetchNextPage, scrollRef, hasNextPage); | ||
|
||
const comments = data.pages.flatMap((page) => page.comments); | ||
|
||
return ( | ||
<section> | ||
<Heading as="h3" size="lg"> | ||
댓글 ({comments.length}개) | ||
</Heading> | ||
<Spacing size={12} /> | ||
{comments.map((comment) => ( | ||
<CommentItem key={comment.id} recipeComment={comment} /> | ||
))} | ||
<div ref={scrollRef} style={{ height: '1px' }} aria-hidden /> | ||
</section> | ||
); | ||
}; | ||
|
||
export default CommentList; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
36 changes: 36 additions & 0 deletions
36
frontend/src/hooks/queries/recipe/useInfiniteRecipeCommentQuery.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import { useSuspendedInfiniteQuery } from '../useSuspendedInfiniteQuery'; | ||
|
||
import { recipeApi } from '@/apis'; | ||
import type { CommentResponse } from '@/types/response'; | ||
|
||
interface PageParam { | ||
lastId: number; | ||
totalElements: number | null; | ||
} | ||
|
||
const fetchRecipeComments = async (pageParam: PageParam, recipeId: number) => { | ||
const { lastId, totalElements } = pageParam; | ||
const response = await recipeApi.get({ | ||
params: `/${recipeId}/comments`, | ||
queries: `?lastId=${lastId}&totalElements=${totalElements}`, | ||
}); | ||
const data: CommentResponse = await response.json(); | ||
return data; | ||
}; | ||
|
||
const useInfiniteRecipeCommentQuery = (recipeId: number) => { | ||
return useSuspendedInfiniteQuery( | ||
['recipeComment', recipeId], | ||
({ pageParam = { lastId: 0, totalElements: null } }) => fetchRecipeComments(pageParam, recipeId), | ||
{ | ||
getNextPageParam: (prevResponse: CommentResponse) => { | ||
const lastId = prevResponse.comments[prevResponse.comments.length - 1].id; | ||
const totalElements = prevResponse.totalElements; | ||
const lastCursor = { lastId: lastId, totalElements: totalElements }; | ||
return prevResponse.hasNext ? lastCursor : undefined; | ||
}, | ||
} | ||
); | ||
}; | ||
|
||
export default useInfiniteRecipeCommentQuery; |
Oops, something went wrong.