Skip to content

Commit

Permalink
Feat/#188 조회 페이지 댓글 기능 (#199)
Browse files Browse the repository at this point in the history
* test: 댓글 조회 및 등록 msw 적용

* feat: Comment 컴포넌트 구현

* feat: CommentList 컴포넌트 구현

* feat: SongPage에 killingPart 별로 CommentList 적용

* config: ol, ul에 list-style 제거

* fix: 익명 프로필 asset 수정

* design: 킬링파트 투표 많은순으로 워딩 변경

* feat: SongPage(듣기)와 SongDetailPage(투표) 페이지 전환 추가

* fix: 킬링파트 순위 변경시 댓글이 변경되지 않던 버그 수정
  • Loading branch information
cruelladevil authored Aug 3, 2023
1 parent 570a6af commit 143c839
Show file tree
Hide file tree
Showing 14 changed files with 359 additions and 33 deletions.
9 changes: 9 additions & 0 deletions frontend/src/assets/icon/shookshook.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
81 changes: 81 additions & 0 deletions frontend/src/components/CommentList/Comment.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { styled } from 'styled-components';
import shookshook from '@/assets/icon/shookshook.svg';
import { Spacing } from '../@common';

interface CommentProps {
content: string;
createdAt: string;
}

// FIXME: 분리 및 포맷 정리, ~일 전 말고도 세분화 필요
const rtf = new Intl.RelativeTimeFormat('ko', {
numeric: 'always',
});

const Comment = ({ content, createdAt }: CommentProps) => {
const time = Math.ceil(
(new Date(createdAt).getTime() - new Date().getTime()) / (1000 * 60 * 60 * 24)
);

return (
<Wrapper>
<Flex>
<Profile>
<img src={shookshook} alt="익명 프로필" />
</Profile>
<Spacing direction="horizontal" size={14} />
<Box tabIndex={0} role="comment">
<Username>익명</Username>
<RelativeTime>{rtf.format(time, 'day')}</RelativeTime>
<Content>{content}</Content>
</Box>
</Flex>
</Wrapper>
);
};

export default Comment;

const Wrapper = styled.li`
width: 100%;
margin-bottom: 16px;
`;

const Flex = styled.div`
display: flex;
width: 100%;
align-items: flex-start;
`;

const Profile = styled.div`
width: 40px;
height: 40px;

border-radius: 100%;
background-color: white;

overflow: hidden;
`;

const Box = styled.div`
flex: 1;
`;

const Username = styled.span`
font-size: 14px;
`;

const RelativeTime = styled.span`
margin-left: 5px;
font-size: 12px;
color: #aaaaaa;
`;

const Content = styled.div`
font-size: 14px;

display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
`;
168 changes: 168 additions & 0 deletions frontend/src/components/CommentList/CommentList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import { useEffect, useState } from 'react';
import { css, styled } from 'styled-components';
import fetcher from '@/apis';
import shookshook from '@/assets/icon/shookshook.svg';
import useFetch from '@/hooks/@common/useFetch';
import { useMutation } from '@/hooks/@common/useMutation';
import { Spacing } from '../@common';
import useToastContext from '../@common/Toast/hooks/useToastContext';
import Comment from './Comment';

interface Comment {
id: number;
content: string;
createdAt: string;
}

interface CommentListProps {
songId: string;
partId: number;
}

const CommentList = ({ songId, partId }: CommentListProps) => {
const [newComment, setNewComment] = useState('');

const { data: comments, fetchData: getComment } = useFetch<Comment[]>(() =>
fetcher(`/songs/${songId}/parts/${partId}/comments`, 'GET')
);

const { mutateData } = useMutation(() =>
fetcher(`/songs/${songId}/parts/${partId}/comments`, 'POST', { content: newComment })
);
const { showToast } = useToastContext();

const resetNewComment = () => setNewComment('');

const changeNewComment: React.ChangeEventHandler<HTMLInputElement> = ({
currentTarget: { value },
}) => setNewComment(value);

const submitNewComment: React.FormEventHandler<HTMLFormElement> = async (event) => {
event.preventDefault();

await mutateData();

showToast('댓글이 등록되었습니다.');
resetNewComment();
getComment();
};

useEffect(() => {
getComment();
}, [partId]);

if (!comments) {
return null;
}

return (
<div>
<Spacing direction="vertical" size={24} />
<h3>댓글 {comments.length}</h3>
<Spacing direction="vertical" size={24} />
<form onSubmit={submitNewComment}>
<Flex>
<Profile>
<img src={shookshook} alt="익명 프로필" />
</Profile>
<Input
type="text"
value={newComment}
onChange={changeNewComment}
placeholder="댓글 추가..."
maxLength={200}
/>
</Flex>
<FlexEnd>
<Cancel type="button" onClick={resetNewComment} aria-label="댓글 작성 취소">
취소
</Cancel>
<Submit aria-label="댓글 작성 완료" disabled={newComment === ''}>
댓글
</Submit>
</FlexEnd>
</form>
<Spacing direction="vertical" size={20} />
<Comments>
{comments.map(({ id, content, createdAt }) => (
<Comment key={id} content={content} createdAt={createdAt} />
))}
</Comments>
</div>
);
};

export default CommentList;

const Flex = styled.div`
display: flex;
align-items: flex-start;
gap: 14px;
`;

const Profile = styled.div`
width: 40px;
height: 40px;
border-radius: 100%;
background-color: white;
overflow: hidden;
`;

const Input = styled.input`
flex: 1;
margin: 0 8px;
border: none;
-webkit-box-shadow: none;
box-shadow: none;
margin: 0;
padding: 0;
background-color: transparent;
border-bottom: 1px solid white;
outline: none;
font-size: 14px;
`;

const FlexEnd = styled.div`
display: flex;
justify-content: flex-end;
gap: 10px;
`;

const buttonBase = css`
width: 50px;
height: 36px;
font-size: 14px;
border-radius: 10px;
`;

const Cancel = styled.button`
${buttonBase}
&:hover,
&:focus {
background-color: ${({ theme }) => theme.color.secondary};
}
`;

const Submit = styled.button`
${buttonBase}
background-color: ${({ theme }) => theme.color.primary};
&:hover,
&:focus {
background-color: #de5484;
}
&:disabled {
background-color: ${({ theme }) => theme.color.secondary};
}
`;

const Comments = styled.ol`
gap: 10px;
`;
1 change: 1 addition & 0 deletions frontend/src/components/CommentList/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './CommentList';
13 changes: 12 additions & 1 deletion frontend/src/components/VoteInterface/VoteInterface.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { useState } from 'react';
import { Link } from 'react-router-dom';
import useVoteInterfaceContext from '@/components/VoteInterface/hooks/useVoteInterfaceContext';
import { useVideoPlayerContext } from '@/components/Youtube';
import { usePostKillingPart } from '@/hooks/killingPart';
import { ButtonContainer } from '@/pages/SongDetailPage.style';
import { UnderLine } from '@/pages/SongPage/SongPage';
import { PrimarySpan, SubTitle } from '@/pages/SongPage/SongPage.style';
import { getPlayingTimeText, minSecToSeconds } from '@/utils/convertTime';
import useToastContext from '../@common/Toast/hooks/useToastContext';
import { IntervalInput } from '../IntervalInput';
Expand Down Expand Up @@ -76,7 +79,15 @@ const VoteInterface = ({ videoLength, songId }: VoteInterfaceProps) => {

return (
<Container>
<RegisterTitle>당신의 킬링파트에 투표하세요🎧</RegisterTitle>
<SubTitle>
<Link to={`/song/${songId}`}>
<PrimarySpan>킬링파트</PrimarySpan> 듣기
</Link>
<UnderLine>
<PrimarySpan>킬링파트</PrimarySpan> 투표
</UnderLine>
</SubTitle>
<RegisterTitle>당신의 킬링파트에 투표하세요 🔖</RegisterTitle>
<KillingPartToggleGroup />
<IntervalInput
videoLength={videoLength}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/hooks/@common/useFetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const useFetch = <T>(fetcher: () => Promise<T>) => {
} finally {
setIsLoading(false);
}
}, []);
}, [fetcher]);

useEffect(() => {
fetchData();
Expand Down
29 changes: 29 additions & 0 deletions frontend/src/mocks/handlers/songsHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,35 @@ export const songsHandlers = [
return res(ctx.json(popularSongs));
}),

rest.get(`${BASE_URL}/songs/:songId/parts/:partId/comments`, (req, res, ctx) => {
const comments = [
{ id: 1, content: '1번 댓글입니다.', createdAt: '2023-08-01T16:02:13.422Z' },
{
id: 2,
content: '2번 댓글입니다. 200자 입니다. '.repeat(10),
createdAt: '2023-08-02T16:02:13.422Z',
},
{ id: 3, content: '3번 댓글입니다.', createdAt: '2023-08-02T16:02:13.422Z' },
{ id: 4, content: '4번 댓글입니다.', createdAt: '2023-08-02T16:02:13.422Z' },
{ id: 5, content: '5번 댓글입니다.', createdAt: '2023-08-02T16:02:13.422Z' },
{ id: 6, content: '6번 댓글입니다.', createdAt: '2023-08-02T16:02:13.422Z' },
{ id: 7, content: '7번 댓글입니다.', createdAt: '2023-08-02T16:02:13.422Z' },
{ id: 8, content: '8번 댓글입니다.', createdAt: '2023-08-02T16:02:13.422Z' },
{ id: 9, content: '9번 댓글입니다.', createdAt: '2023-08-02T16:02:13.422Z' },
];

return res(ctx.json(comments));
}),

rest.post(`${BASE_URL}/songs/:songId/parts/:partId/comments`, async (req, res, ctx) => {
const { songId, partId } = req.params;
const content = await req.json();

console.log(songId, partId, JSON.parse(content));

return res(ctx.status(201));
}),

rest.get(`${BASE_URL}/songs/:songId`, (req, res, ctx) => {
const { songId } = req.params;

Expand Down
2 changes: 1 addition & 1 deletion frontend/src/pages/SongDetailPage.style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export const Container = styled.section`
display: flex;
width: 100%;
flex-direction: column;
gap: 16px;
gap: 20px;
`;

export const SongInfoContainer = styled.div`
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/pages/SongDetailPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { VoteInterface, VoteInterfaceProvider } from '@/components/VoteInterface
import { VideoPlayerProvider, Youtube } from '@/components/Youtube';
import { useGetSongDetail } from '@/hooks/song';
import { Container, Singer, SongTitle, SongInfoContainer, Info } from './SongDetailPage.style';
import { BigTitle } from './SongPage/SongPage';

const SongDetailPage = () => {
const { id: songIdParam } = useParams();
Expand All @@ -17,6 +18,7 @@ const SongDetailPage = () => {

return (
<Container>
<BigTitle>킬링파트 투표 🔖</BigTitle>
<SongInfoContainer>
<Thumbnail src={albumCoverUrl} alt={`${title} 앨범 자켓`} />
<Info>
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/pages/SongPage/SongPage.style.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ export const Singer = styled.p`
`;

export const SubTitle = styled.h2`
display: flex;
justify-content: space-between;
align-items: center;
font-size: 18px;
font-weight: 700;
color: ${({ theme: { color } }) => color.white};
Expand Down
Loading

0 comments on commit 143c839

Please sign in to comment.