Skip to content

Commit

Permalink
문제 상세 내용 관련 API 연동
Browse files Browse the repository at this point in the history
* feat: Loading 컴포넌트 구현

Loading 컴포넌트 구현

* feat: 질문 상세 페이지 관련 API 구현

- getQuestionDetailContentData : 질문 상세 내용를 가져오는 API
- getQuestionAnswerListData : 질문별 답변 리스트를 가져오는 API
- postAnswer : 질문을 생성하는 API

* feat: 질문 상세 페이지 API 도입

- 질문 상세 내용을 서버에서 가져오는 로직 추가
- 질문 상세 내용에 대한 답변 리스트를 서버에서 가져오는 로직 추가
- 질문 답변을 서버에 업로드하는 로직 추가
- 로딩 상태 관리 로직 추가

* feat: 질문에 대한 답변 API 적용

- 질문 답변 데이터 type 변경
- 질문 답변 API 적용

* refactor: styled-component props 변수명 변경

styled-component에서 사용되고 있던 props의 변수명을 camelCase에서 lowercase로 변경해주었습니다.

* refactor: 질문 답변 등록 API body 수정

질문 답변을 등록하는 API의 body 객체에 questionId를 추가했습니다.
  • Loading branch information
MayOwall authored Nov 30, 2023
1 parent ce65456 commit d8adcd8
Show file tree
Hide file tree
Showing 14 changed files with 197 additions and 104 deletions.
Binary file added FE/public/images/walking-penguin.webp
Binary file not shown.
49 changes: 49 additions & 0 deletions FE/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,52 @@ export const getQuestionList = async (options: Options) => {
console.error(e);
}
};

export const getQuestionDetailContentData = async (questionId: number) => {
try {
const url = `/api/questions/${questionId}`;
const { status, data } = await instance.get(url);
if (status !== 200) {
throw new Error();
}
return data;
} catch (e) {
console.error(e);
}
};

export const getQuestionAnswerListData = async (questionId: number) => {
try {
const url = `/api/answers/${questionId}`;
const { data } = await instance.get(url);
const { answers } = data;
return answers;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) {
if (error.response) {
if (error.response.status === 404) {
return null;
}
} else {
console.error('Error from get answer list data:', error.message);
}
throw error;
}
};

export const postAnswer = async (content: string, questionId: number) => {
try {
const url = '/api/answers';
const { status, data } = await instance.post(url, {
questionId,
content,
videoLink: 'https://localhost.com',
});
if (status !== 201) {
throw new Error('답변 작성 실패');
}
return data;
} catch (e) {
console.error(e);
}
};
14 changes: 14 additions & 0 deletions FE/src/components/Loading/Loading.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import styled from 'styled-components';

export const LoadingContainer = styled.div`
position: absolute;
top: calc(50% - 2rem);
left: calc(50% - 3rem);
width: 6rem;
height: 6rem;
img {
width: 100%;
height: 100%;
}
`;
15 changes: 15 additions & 0 deletions FE/src/components/Loading/Loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import WalkingPenguin from '/images/walking-penguin.webp';
import { LoadingContainer } from './Loading.styles';

const Loading = () => {
return (
<LoadingContainer>
<picture>
<source srcSet={WalkingPenguin} type="image/webp" />
<img src="" />
</picture>
</LoadingContainer>
);
};

export default Loading;
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export const Inner = styled.div`
`;

interface HeaderProps {
isadopted: boolean;
isadopted: string;
}

export const Header = styled.div<HeaderProps>`
Expand All @@ -30,7 +30,8 @@ export const Header = styled.div<HeaderProps>`
height: 100%;
align-items: center;
color: ${({ theme }) => theme.color.mainColor.blueMain};
border-bottom: ${({ isadopted }) => (isadopted ? '0.25rem solid' : 'none')};
border-bottom: ${({ isadopted }) =>
isadopted === 'false' ? '0.25rem solid' : 'none'};
> div {
font-weight: 900;
Expand Down
16 changes: 8 additions & 8 deletions FE/src/components/QuestionAnswerCard/QuestionAnswerCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ import {

const QuestionAnswerCard = ({ cardData }: QuestionAnswerCardProps) => {
const {
content,
nickname,
createdAt,
isAdopted,
isLiked: isLikedInitial,
Content: content,
CreatedAt: createdAt,
IsAdopted: isAdopted,
User,
} = cardData;
const [isLiked, setIsLiked] = useState(isLikedInitial);
const { Nickname: nickname } = User;
const [isLiked, setIsLiked] = useState(false);

useEffect(() => {
// useDidMoundEffect로 변경 & API 로직 구현
Expand All @@ -30,7 +30,7 @@ const QuestionAnswerCard = ({ cardData }: QuestionAnswerCardProps) => {
return (
<Container>
<Inner>
<Header isadopted={isAdopted}>
<Header isadopted={String(isAdopted)}>
<div>A</div>
{isAdopted && (
<AdoptedBadge>
Expand All @@ -40,7 +40,7 @@ const QuestionAnswerCard = ({ cardData }: QuestionAnswerCardProps) => {
)}
<KebabIcon />
</Header>
<Content>{content}</Content>
<Content dangerouslySetInnerHTML={{ __html: content }}></Content>
<QuestionInfo>
<CreateInfo>
{nickname} {getFormalizedDate(createdAt)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ const QuestionDetailContent = ({
<div>Q</div>
{title}
</Title>
<Content>{content}</Content>
<Content dangerouslySetInnerHTML={{ __html: content }}></Content>
<TagInfo>
<TagButton content={programmingLanguage} isInteractive={false} />
<TagButton content={tag} isInteractive={false} />
Expand Down
2 changes: 1 addition & 1 deletion FE/src/components/QuestionList/QuestionList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export function Item({ itemData }: { itemData: ItemData }) {
} = itemData;

const handleItemClick = () => {
navigate(`/question/${id}`);
navigate(`/question/${id}`, { state: { questionId: id } });
};

return (
Expand Down
25 changes: 16 additions & 9 deletions FE/src/components/TagButton/TagButton.styles.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
import styled from 'styled-components';

interface ContainerProps {
isSelected: boolean;
isInteractive: boolean;
isselected: string;
isinteractive: string;
}

export const Container = styled.button<ContainerProps>`
height: 2rem;
padding: 0.3rem 1rem;
border-radius: 1rem;
border: none;
box-shadow: ${({ isSelected, theme }) =>
isSelected ? `0 0 0 1px ${theme.color.grayscale.black} inset` : 'none'};
background-color: ${({ isSelected, theme }) =>
isSelected ? theme.color.grayscale.white : theme.color.grayscale[50]};
color: ${({ isSelected, theme }) =>
isSelected ? theme.color.grayscale.black : theme.color.grayscale[200]};
box-shadow: ${({ isselected, theme }) =>
isselected === 'true'
? `0 0 0 1px ${theme.color.grayscale.black} inset`
: 'none'};
background-color: ${({ isselected, theme }) =>
isselected === 'true'
? theme.color.grayscale.white
: theme.color.grayscale[50]};
color: ${({ isselected, theme }) =>
isselected === 'true'
? theme.color.grayscale.black
: theme.color.grayscale[200]};
${({ theme }) => theme.font.medium14}
cursor: ${({ isInteractive }) => (isInteractive ? 'pointer' : 'default')};
cursor: ${({ isinteractive }) =>
isinteractive === 'true' ? 'pointer' : 'default'};
`;
4 changes: 2 additions & 2 deletions FE/src/components/TagButton/TagButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ const TagButton = ({

return (
<Container
isSelected={isSelected}
isInteractive={isInteractive}
isselected={String(isSelected)}
isinteractive={String(isInteractive)}
onClick={handleButtonState}
>
{content}
Expand Down
2 changes: 2 additions & 0 deletions FE/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import SquareButton from './SquareButton/SquareButton';
import QuestionAnswerCard from './QuestionAnswerCard/QuestionAnswerCard';
import QuestionAnswerFormCard from './QuestionAnswerFormCard/QuestionAnswerFormCard';
import Scroller from './Scroller/Scroller';
import Loading from './Loading/Loading';

export {
MainHeader,
Expand All @@ -32,4 +33,5 @@ export {
QuestionAnswerCard,
QuestionAnswerFormCard,
Scroller,
Loading,
};
7 changes: 7 additions & 0 deletions FE/src/pages/QuestionDetailPage/QuestionDetailPage.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,15 @@ export const Container = styled.div`
width: 100%;
height: fit-content;
background-color: ${({ theme }) => theme.color.grayscale[50]};
margin-bottom: 6rem;
> * {
background-color: ${({ theme }) => theme.color.grayscale.white};
}
`;

export const NoAnswer = styled.div`
text-align: center;
padding: 3rem;
color: ${({ theme }) => theme.color.grayscale[200]};
`;
Loading

0 comments on commit d8adcd8

Please sign in to comment.