Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 공통 컴포넌트 개발 및 톡픽 수정, 삭제, 신고, 요약, 댓글 api 연결 #180

Merged
merged 28 commits into from
Sep 10, 2024
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
382c845
design: 밸런스게임 단계 확인 바 컴포넌트 UI 구현 및 스토리북 추가
areumH Sep 8, 2024
eb155db
design: 선택지 작성 버튼 컴포넌트 UI 구현 및 스토리북 추가
areumH Sep 8, 2024
14a216a
design: 밸런스게임 추천 썸네일 컴포넌트 UI 구현 및 스토리북 추가
areumH Sep 8, 2024
d0a7640
feat: 톡픽 게시글 수정 기능 api 연결
areumH Sep 8, 2024
9d51eca
feat: 톡픽 게시글 삭제 기능 api 연결
areumH Sep 8, 2024
b631e8a
feat: 톡픽 링크 복사 공유 기능 구현
areumH Sep 8, 2024
1b48f80
feat: 톡픽 신고 선택 사유 모달 제시 로직 구현
areumH Sep 8, 2024
5725d55
feat: 톡픽 요약 기능 api 연결
areumH Sep 8, 2024
298f2c5
refactor: 톡앤픽 플레이스 페이지 페이지네이션 수정
areumH Sep 8, 2024
82cfb92
feat: 톡앤픽 플레이스 작성 버튼 로직 연결
areumH Sep 8, 2024
d708245
feat: 톡픽 댓글 확인 api 연결
areumH Sep 8, 2024
d481d20
feat: 본인이 작성한 댓글일 경우 배경색 추가 및 스토리북 수정
areumH Sep 8, 2024
8353b58
refactor: 톡픽 관련 organism 컴포넌트 스토리북 오류 수정
areumH Sep 8, 2024
acd49fc
feat: 댓글 작성 기능 api 연결
areumH Sep 8, 2024
67f6b49
feat: ISO 형식 날짜 데이터 처리 함수 작성 및 적용
areumH Sep 8, 2024
a98a9d0
refactor: 댓글 컴포넌트 스토리북 수정
areumH Sep 8, 2024
24b1553
feat: 댓글 삭제 기능 api 연결
areumH Sep 8, 2024
474772c
feat: 댓글 좋아요 및 좋아요 취소 기능 api 연결
areumH Sep 8, 2024
799b99b
refactor: 본인이 작성한 댓글일 경우 스타일 수정
areumH Sep 8, 2024
026ad64
feat: 톡픽에 투표를 한 경우에만 댓글이 보여지도록 구현
areumH Sep 8, 2024
dfe87b9
feat: 댓글 신고 기능 api 연결
areumH Sep 8, 2024
ab6b8ab
refactor: 댓글 좋아요 취소 hook 수정
areumH Sep 8, 2024
4dfa9ea
feat: 댓글 삭제 모달 제시 후 삭제되도록 로직 구현
areumH Sep 8, 2024
2be4363
refactor: 오늘의 톡픽 배너 id 원상태로 수정 및 불필요한 주석 제거
areumH Sep 8, 2024
6b6bbbd
feat: ImageUploadButton 컴포넌트 스토리북 추가
areumH Sep 9, 2024
00e1637
refactor: CommentProfile 컴포넌트 스토리북 코드 수정
areumH Sep 9, 2024
bb98d9f
feat: ImageUploader 컴포넌트 스토리북 추가
areumH Sep 9, 2024
c102e7e
refactor: 톡픽 수정 시 게시글 작성 페이지로 넘겨주는 state 속성 명 수정
areumH Sep 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/api/comments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const putComment = async (
) => {
const { data } = await axiosInstance.put<ServerResponse>(
END_POINT.EDIT_COMMENT(talkPickId, commentId),
{ ...comment },
{ content: comment },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

스프레드 대신 명확하게 표현해준 부분 좋네요!

);
return data;
};
Expand Down
8 changes: 4 additions & 4 deletions src/api/like.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@ import { END_POINT } from '@/constants/api';
import { Id, ServerResponse } from '@/types/api';
import { axiosInstance } from './interceptor';

export const postLikeComment = async (commentId: Id) => {
export const postLikeComment = async (talkPickId: Id, commentId: Id) => {
const { data } = await axiosInstance.post<ServerResponse>(
END_POINT.LIKE_COMMENT(commentId),
END_POINT.LIKE_COMMENT(talkPickId, commentId),
);
return data;
};

export const deleteLikeComment = async (commentId: Id) => {
export const deleteLikeComment = async (talkPickId: Id, commentId: Id) => {
const { data } = await axiosInstance.delete<ServerResponse>(
END_POINT.DELETE_LIKE_COMMENT(commentId),
END_POINT.DELETE_LIKE_COMMENT(talkPickId, commentId),
);
return data;
};
18 changes: 18 additions & 0 deletions src/api/report.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { END_POINT } from '@/constants/api';
import { Id } from '@/types/api';
import { axiosInstance } from './interceptor';

export const postCommentReport = async (
talkPickId: Id,
commentId: Id,
reportReason: string,
) => {
const response = await axiosInstance.post(
END_POINT.REPORT_COMMENT(talkPickId, commentId),
{
reportType: 'COMMENT',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

swagger에서도 request body 에는 reportType: 'COMMENT' 로 고정되는 구조인데 reportType 는 항상 COMMENT인지 궁금합니다 :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저도 스웨거에서 'COMMENT'로 고정된 것을 보고 저렇게 작성해두었어요!!
추후에 답글 신고나 톡픽 게시글 신고의 경우엔 다른 값을 사용하지 않을까 싶습니다 😶

reason: reportReason,
},
);
return response;
};
13 changes: 11 additions & 2 deletions src/api/talk-pick.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { END_POINT } from '@/constants/api';
import { Id } from '@/types/api';
import {
TalkPickDetail,
TalkPick,
NewTalkPick,
TempTalkPick,
TalkPickListItem,
Expand All @@ -19,7 +18,10 @@ export const getTalkPickDetail = async (talkPickId: Id) => {
return data;
};

export const putTalkPick = async (talkPickId: Id, talkPickData: TalkPick) => {
export const putTalkPick = async (
talkPickId: Id,
talkPickData: NewTalkPick,
) => {
const response = await axiosInstance.put(
END_POINT.TALKPICK(talkPickId),
talkPickData,
Expand Down Expand Up @@ -78,3 +80,10 @@ export const getTalkPickList = async (pageable: Pageable) => {
);
return data;
};

export const postTalkPickSummary = async (talkPickId: Id) => {
const response = await axiosInstance.post(
END_POINT.TALKPICK_SUMMARY(talkPickId),
);
return response;
};
Binary file added src/assets/images/recommend-first.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/images/recommend-second.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/assets/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ export { default as EelProfile } from './images/eel-profile.png';
export { default as TurtleProfile } from './images/turtle-profile.png';
export { default as OctopusProfile } from './images/octopus-profile.png';
export { default as JellyfishProfile } from './images/jellyfish-profile.png';
export { default as ChoicePlus } from './svg/choice-plus.svg';
export { default as ChoiceMinus } from './svg/choice-minus.svg';
export { default as RecommendFirst } from './images/recommend-first.png';
export { default as RecommendSecond } from './images/recommend-second.png';

// TODO: 이전 SVG
export { default as Email } from './svg/email.svg';
Expand Down
4 changes: 4 additions & 0 deletions src/assets/svg/choice-minus.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/assets/svg/choice-plus.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
48 changes: 48 additions & 0 deletions src/components/atoms/ChoiceInputButton/ChoiceInputButton.style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { css } from '@emotion/react';
import color from '@/styles/color';
import typo from '@/styles/typo';

export const choiceInputContainer = css({
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
width: '578px',
backgroundColor: color.GY[3],
borderRadius: '20px',
border: `1px solid ${color.GY[2]}`,
});

export const choiceInputWithText = (withText: boolean, option: 'A' | 'B') => {
if (!withText) {
return css({});
}
Comment on lines +16 to +18
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

withText로 1차 분기 처리를 하고, true 일 경우 option을 통해 스타일을 설정을 하는 방식이 정말 깔끔한거 같습니다!


const style = {
A: css({
border: `1px solid ${color.RED}`,
backgroundColor: color.WT,
}),
B: css({
border: `1px solid ${color.BLUE}`,
backgroundColor: color.WT,
}),
};

return style[option];
};

export const choiceInputWrapper = css({
display: 'flex',
justifyContents: 'space-between',
alignItems: 'center',
width: '100%',
padding: '20px',
gap: '17px',
});

export const choiceInputStyling = css(typo.Main.Medium, {
width: '100%',
height: '34px',
paddingLeft: '10px',
color: color.BK,
});
82 changes: 82 additions & 0 deletions src/components/atoms/ChoiceInputButton/ChoiceInputButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/* eslint-disable jsx-a11y/control-has-associated-label */
import React, { ComponentPropsWithoutRef, useEffect, useState } from 'react';
import { ChoiceMinus, ChoicePlus } from '@/assets';
import Divider from '../Divider/Divider';
import * as S from './ChoiceInputButton.style';

export interface ChoiceInputBoxProps {
option: 'A' | 'B';
choiceInputProps?: ComponentPropsWithoutRef<'input'>;
infoInputProps?: ComponentPropsWithoutRef<'input'>;
}

const ChoiceInputButton = ({
option = 'A',
choiceInputProps,
infoInputProps,
}: ChoiceInputBoxProps) => {
const [infoInputClicked, setInfoButtonClicked] = useState<boolean>(false);
const [withText, setWithText] = useState<boolean>(false);

const [choiceInputValue, setChoiceInputValue] = useState<string>('');
const [infoInputValue, setInfoInputValue] = useState<string>('');

useEffect(() => {
if (choiceInputValue.trim() || infoInputValue.trim()) {
setWithText(true);
} else {
setWithText(false);
}
}, [choiceInputValue, infoInputValue]);

return (
<div
css={[
S.choiceInputContainer,
withText && S.choiceInputWithText(withText, option),
]}
>
<div css={S.choiceInputWrapper}>
<input
type="text"
placeholder={`${option} 선택지를 입력하세요.`}
maxLength={30}
css={S.choiceInputStyling}
onChange={(e) => setChoiceInputValue(e.target.value)}
{...choiceInputProps}
/>
{!infoInputClicked && (
<button type="button" onClick={() => setInfoButtonClicked(true)}>
<ChoicePlus />
</button>
)}
</div>
{infoInputClicked && (
<>
<Divider orientation="width" length={534} />
<div css={S.choiceInputWrapper}>
<input
type="text"
placeholder="해당 선택지에 대해 추가로 설명을 입력할 수 있어요!"
maxLength={50}
css={S.choiceInputStyling}
onChange={(e) => setInfoInputValue(e.target.value)}
{...infoInputProps}
/>
<button
type="button"
onClick={() => {
setInfoButtonClicked(false);
setInfoInputValue('');
}}
>
<ChoiceMinus />
</button>
</div>
</>
)}
</div>
);
};

export default ChoiceInputButton;
7 changes: 4 additions & 3 deletions src/components/atoms/CitationBox/CitationBox.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import React, { forwardRef } from 'react';
import type { ForwardedRef } from 'react';
import type { ComponentPropsWithRef, ForwardedRef } from 'react';
import * as S from './CitationBox.style';

interface CitationBoxProps {
interface CitationBoxProps extends ComponentPropsWithRef<'input'> {
setSourceUrl: (url: string) => void;
}

const CitationBox = (
{ setSourceUrl }: CitationBoxProps,
{ setSourceUrl, ...props }: CitationBoxProps,
ref: ForwardedRef<HTMLInputElement>,
) => {
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
Expand All @@ -22,6 +22,7 @@ const CitationBox = (
type="text"
ref={ref}
onChange={handleInputChange}
{...props}
/>
</div>
);
Expand Down
4 changes: 2 additions & 2 deletions src/components/atoms/CommentProfile/CommentProfile.style.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { css } from '@emotion/react';
import color from '@/styles/color';

export const containerStyle = (stance: 'pros' | 'cons') => css`
export const containerStyle = (stance: 'A' | 'B') => css`
display: inline-flex;
padding: 5px;
align-items: center;
gap: 10px;
border-radius: 50px 50px 0 50px;
background: ${stance === 'pros' ? color.RED : color.BLUE};
background: ${stance === 'A' ? color.RED : color.BLUE};
`;

export const profileWrapper = css({
Expand Down
6 changes: 3 additions & 3 deletions src/components/atoms/CommentProfile/CommentProfile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ import {
} from './CommentProfile.style';

export interface CommentProfileProps {
stance: 'pros' | 'cons';
option: 'A' | 'B';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분은 제가 반영했어야 했는데 바꿔주셨군요ㅜㅜ 감사합니다아

imgUrl?: string;
}

const CommentProfile = ({ stance, imgUrl }: CommentProfileProps) => (
<div css={[containerStyle(stance)]}>
const CommentProfile = ({ option, imgUrl }: CommentProfileProps) => (
<div css={[containerStyle(option)]}>
<div css={profileWrapper}>
<img css={profileImage} src={imgUrl} alt="profile" />
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { css } from '@emotion/react';
import color from '@/styles/color';
import typo from '@/styles/typo';

export const gameRecommendStyling = css({
display: 'flex',
flexDirection: 'column',
width: '230px',
gap: '10px',
cursor: 'pointer',
});

export const gameImgContainer = css({
display: 'flex',
width: '100%',
height: '116px',
borderRadius: '5px',
overflow: 'hidden',
});

export const gameImgWrapper = css({
flex: '1',
});

export const gameImgStyling = css({
width: '100%',
height: '100%',
objectFit: 'cover',
});

export const gameTitleStyling = css(typo.Comment.Regular, {
width: '100%',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
color: color.BK,
});
28 changes: 28 additions & 0 deletions src/components/atoms/GameRecommendButton/GameRecommendButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from 'react';
import * as S from './GameRecommendButton.style';

export type GameRecommend = {
title: string;
optionAImg: string;
optionBImg: string;
};

export interface GameRecommendButtonProps {
game: GameRecommend;
}

const GameRecommendButton = ({ game }: GameRecommendButtonProps) => (
<div css={S.gameRecommendStyling}>
<div css={S.gameImgContainer}>
<div css={S.gameImgWrapper}>
<img src={game.optionAImg} alt="A Img" css={S.gameImgStyling} />
</div>
<div css={S.gameImgWrapper}>
<img src={game.optionBImg} alt="B Img" css={S.gameImgStyling} />
</div>
</div>
<div css={S.gameTitleStyling}>{game.title}</div>
</div>
);

export default GameRecommendButton;
24 changes: 24 additions & 0 deletions src/components/atoms/GameStageBar/GameStageBar.style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { css } from '@emotion/react';
import color from '@/styles/color';

export const stageBarContainer = css({
display: 'flex',
gap: '8px',
});

export const stageBarStyling = css({
width: '34px',
height: '7px',
borderRadius: '20px',
});

export const getStageBarColor = (currentStage: boolean) => {
if (currentStage) {
return css({
backgroundColor: color.MAIN,
});
}
return css({
backgroundColor: color.GY[3],
});
};
Loading
Loading