Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
719 changes: 344 additions & 375 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

46 changes: 46 additions & 0 deletions src/apis/content.api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { IContent } from '@/types/content';
import { httpClient } from './http.api';

export interface FetchContentParams {
content_id: string | undefined;
}
Comment on lines +4 to +6
Copy link

Choose a reason for hiding this comment

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

Suggested change
export interface FetchContentParams {
content_id: string | undefined;
}
export interface FetchContentParams {
content_id?: string;
}

undefined가 올 수 있는 경우에는 ?를 이용하여 표시하기도 한답니다.
근데, 단 하나의 인자만 온다면 굳이 interface로 선언할 필요가 있을까요? 🤔
type으로 선언해도 충분할 것 같기도 합니다.
그렇다면 ?는 못쓰겠지만요 😅


export const fetchContent = async ({ content_id }: FetchContentParams) => {
try {
const response = await httpClient.get<IContent>(`/api/posts/${content_id}`);
return response.data;
} catch (e) {
console.log('에러났어', e);
Copy link

Choose a reason for hiding this comment

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

앗, 에러 메시지가 귀엽네요 😄

}
};

export const fetchLikedPost = async ({ content_id }: FetchContentParams) => {
try {
const response = await httpClient.post(`/api/posts/${content_id}/like`);
return response.data;
} catch (e) {
console.log('에러났어', e);
}
};

export const fetchSolved = async ({ content_id }: FetchContentParams) => {
try {
const response = await httpClient.post<void>(
`api/posts/${content_id}/solved`
);
return response.data;
} catch (e) {
console.log('에러났어', e);
}
};

export const fetchContentDelete = async ({
content_id,
}: FetchContentParams) => {
try {
const response = await httpClient.delete<void>(`api/posts/${content_id}`);
return response.data;
} catch (e) {
console.log('에러났어', e);
}
};
47 changes: 47 additions & 0 deletions src/components/content-detail/QuestionComment.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { useRef } from 'react';
import styled from 'styled-components';

export default function QuestionComment() {
const textRef = useRef<HTMLTextAreaElement>(null);

const handleInput = () => {
if (textRef.current) {
textRef.current.style.height = 'auto';
textRef.current.style.height = `${textRef.current.scrollHeight}px`;
}
};

return (
<QuestionCommentStyle>
<h1 className='commentTitle'>답변</h1>
<div className='commentInput'>
<textarea ref={textRef} onInput={handleInput}></textarea>
</div>
</QuestionCommentStyle>
);
}

const QuestionCommentStyle = styled.div`
Copy link

Choose a reason for hiding this comment

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

스타일 컴포넌트와 classname을 혼합해서 쓰시는 이유가 있을까요? 👀
CommentContainer
CommentTitle
CommentInput
같은 형식으로 styled-component만 써도 충분히 표현할 수 있을 것 같아요 👀

물론 class name을 혼합하여 쓰는 것이 잘못된 방식은 아닙니다.
다만 여러 방식을 혼재해서 쓸 경우 나중에는 헷갈리게 되거나 유지보수가 힘들어질 수도 있어서요.
특별한 경우가 아니라면 하나의 도구를 사용하는 편이 좋다고 생각해요 😄

.commentTitle {
font-size: 1rem;
font-weight: bold;
}

.commentInput {
textarea {
resize: none;
width: 100%;
max-height: 220px;
font-size: 1rem;
margin-top: 1rem;
padding: 1rem 1.5rem;
border: 1px solid #727272;
border-radius: 20px;
outline: none;
}

textarea::-webkit-scrollbar {
display: none;
}
}
`;
13 changes: 13 additions & 0 deletions src/components/content-detail/QuestionContent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import styled from 'styled-components';

interface Props {
content: string | undefined;
}

export default function QuestionContent({ content }: Props) {
return <QuestionContentStyle>{content}</QuestionContentStyle>;
}

const QuestionContentStyle = styled.div`
font-size: 15px;
`;
Comment on lines +7 to +13
Copy link

Choose a reason for hiding this comment

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

이정도 컴포넌트라면 단순 스타일 컴포넌트만으로 부모에서 만들어 써도 되지 않았을까 싶기는 하네요 🤔
나중의 확장성을 위해 미리 분리해두신 걸까요? 👀

182 changes: 182 additions & 0 deletions src/components/content-detail/QuestionFooter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
import { useLiked } from '@/hooks/useLiked';
import { HeartIcon } from '@heroicons/react/24/outline';
import styled from 'styled-components';

interface Props {
solve: boolean | undefined;
nicknameCheck: string | undefined;
handleSolvedClick(): void;
}

export default function QuestionFooter({
solve,
nicknameCheck,
handleSolvedClick,
}: Props) {
const { like, post, handleHeartClick } = useLiked();

return (
<QuestionFooterStyle>
<div className='tags'>
{post?.tags &&
post?.tags.split(',').map((tag) => (
<div className='tag' key={tag}>
{tag}
</div>
))}
</div>
<div className='footerWrap'>
<div className='likes' onClick={handleHeartClick}>
<span className='heartIcon'>
<HeartIcon
className={`likeHeart ${like === true ? 'likeFill' : ''}`}
/>
</span>
<span className='likeCount'>{post?.like_count}</span>
</div>
</div>
{post?.nickname === nicknameCheck && (
<div className={`solvedWrap ${solve ? 'solve' : 'problem'}`}>
<div className='solvedStatus'>
<div className='left'>
{solve ? (
<div className='solvedTitle'>해결된 질문이에요!</div>
) : (
<>
<div className='solvedTitle'>질문이 해결 되었나요?</div>
<div>해결되었다면 상태를 변경해주세요.</div>
</>
)}
</div>
<div className='right'>
<button
className={`solveButton ${solve ? 'solve' : 'problem'}`}
onClick={handleSolvedClick}
>
{solve ? 'problem' : 'solve'}
</button>
</div>
</div>
</div>
)}
</QuestionFooterStyle>
);
}

const QuestionFooterStyle = styled.div`
display: flex;
flex-direction: column;
gap: 12px;
font-size: 15px;

.tags {
display: flex;
flex-wrap: wrap;
gap: 10px;
.tag {
background-color: #deffe2;
color: #858585;
font-family: 'Pretendard-Light', Helvetica;
font-size: 12px;
padding: 8px 16px;
border-radius: 12px;
}
}

.footerWrap {
display: flex;
line-height: 24px;
gap: 20px;
.likes {
display: flex;
cursor: pointer;

.likeHeart {
width: 24px;
}
.likeFill {
fill: #ff9bd2;
stroke: #ff9bd2;
}
}

.scrapped {
display: flex;
cursor: pointer;

.scrapIcon {
svg {
width: 24px;
}

.fill {
fill: #575757;
stroke: #575757;
}
}
}

.scrapped:active,
.likes:active {
transform: translateY(5px);
}
}

.solvedWrap {
width: 100%;
border-radius: 20px;
.solvedStatus {
padding: 2.5rem 3.5rem;
display: flex;
justify-content: space-between;
font-size: 1.3rem;

.left {
display: flex;
flex-direction: column;
gap: 20px;

.solvedTitle {
font-size: 2rem;
}
}
.right {
display: flex;
align-items: center;
.solveButton {
cursor: pointer;
border: 1px solid #e6e6e6;
font-size: 1.5rem;
height: fit-content;
padding: 1rem 1.5rem;
border-radius: 50px;
}

.solveButton:hover {
scale: 1.1;
transition: all 50ms ease-in-out;
}
}
}
}

.solve {
background-color: #c9ffcf;

.right {
.solveButton {
background-color: #f7f7f7;
}
}
}

.problem {
background-color: #f7f7f7;

.right {
.solveButton {
background-color: #c9ffcf;
}
}
}
`;
Loading