diff --git a/src/apis/http.api.ts b/src/apis/http.api.ts index 56772c4..30383c7 100644 --- a/src/apis/http.api.ts +++ b/src/apis/http.api.ts @@ -1,4 +1,4 @@ -import axios, { AxiosInstance, AxiosRequestConfig, AxiosHeaders } from 'axios'; +import axios, { AxiosInstance, AxiosRequestConfig } from 'axios'; import { getToken, removeToken } from '@/utils/token'; // 토큰 유틸리티 함수 import import { logout } from '@/store/slices/authSlice'; import { store } from '@/store/store'; diff --git a/src/apis/mainpost.api.ts b/src/apis/mainpost.api.ts new file mode 100644 index 0000000..f0b2766 --- /dev/null +++ b/src/apis/mainpost.api.ts @@ -0,0 +1,13 @@ +import httpClient from '@/apis/http.api'; + +export const fetchPosts = async () => { + try { + console.log('Requesting posts...'); + const response = await httpClient.get('/api/main'); + console.log('Response data:', response.data); + return response.data; + } catch (error) { + console.error('Error fetching posts:', error); + throw error; + } +}; diff --git a/src/components/QuesitonTag.tsx b/src/components/QuesitonTag.tsx new file mode 100644 index 0000000..de55c2a --- /dev/null +++ b/src/components/QuesitonTag.tsx @@ -0,0 +1,34 @@ +import styled from 'styled-components'; + +// Styled Components +const TagsContainer = styled.div` + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-top: 12px; +`; + +const TagItem = styled.div` + background-color: #deffe2; + color: #858585; + font-family: 'Pretendard-ExtraLight', Helvetica; + font-size: 8px; + padding: 4px 8px; + border-radius: 12px; +`; + +interface TagsProps { + tags: string[]; +} + +function QuestionTag({ tags }: TagsProps) { + return ( + + {tags.map((tag, index) => ( + {tag} + ))} + + ); +} + +export default QuestionTag; diff --git a/src/components/QuestionBody.tsx b/src/components/QuestionBody.tsx new file mode 100644 index 0000000..9829628 --- /dev/null +++ b/src/components/QuestionBody.tsx @@ -0,0 +1,19 @@ +import styled from 'styled-components'; + +const BodyContainer = styled.div` + font-family: 'Pretendard-ExtraLight', Helvetica; + font-size: 10px; + line-height: 1.5; + color: #333; + margin-top: 8px; +`; + +interface QuestionBodyProps { + content: string; +} + +function QuestionBody({ content }: QuestionBodyProps) { + return {content}; +} + +export default QuestionBody; diff --git a/src/components/QuestionBottom.tsx b/src/components/QuestionBottom.tsx new file mode 100644 index 0000000..da776ad --- /dev/null +++ b/src/components/QuestionBottom.tsx @@ -0,0 +1,35 @@ +import styled from 'styled-components'; +import QuestionUser from './QuestionUser'; +import QuestionUtil from './QuestionUtil'; + +const BottomContainer = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 12px; +`; + +interface QuestionBottomProps { + nickname: string; + time: string; + likes: number; + comments: number; + views: number; +} + +function QuestionBottom({ + nickname, + time, + likes, + comments, + views, +}: QuestionBottomProps) { + return ( + + + + + ); +} + +export default QuestionBottom; diff --git a/src/components/QuestionBox.tsx b/src/components/QuestionBox.tsx new file mode 100644 index 0000000..69d60d8 --- /dev/null +++ b/src/components/QuestionBox.tsx @@ -0,0 +1,63 @@ +import { useEffect, useState } from 'react'; +import styled from 'styled-components'; +import QuestionBody from './QuestionBody'; +import QuestionHeader from './QuestionHeader'; +import QuestionTag from './QuesitonTag'; +import QuestionBottom from './QuestionBottom'; +import { fetchPosts } from '@/apis/mainpost.api'; +import { PostData } from '@/types/postdata'; + +const QuestionBoxContainer = styled.div` + display: flex; + flex-direction: column; + gap: 20px; +`; + +const QuestionItem = styled.div` + margin: 10px; +`; + +function QuestionBox() { + const [posts, setPosts] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const loadPosts = async () => { + try { + const data = await fetchPosts(); + setPosts(data); + } catch (err) { + setError('데이터를 불러오는 중 오류가 발생했습니다.'); + } finally { + setLoading(false); + } + }; + + loadPosts(); + }, []); + + if (loading) return
데이터를 불러오는 중...
; + if (error) return
{error}
; + + return ( + + {posts.map((post) => ( + + + + {post.tags && } + + + ))} + + ); +} + +export default QuestionBox; diff --git a/src/components/QuestionButton.tsx b/src/components/QuestionButton.tsx new file mode 100644 index 0000000..d59f66a --- /dev/null +++ b/src/components/QuestionButton.tsx @@ -0,0 +1,99 @@ +import styled from 'styled-components'; + +const ProblemBox = styled.div` + height: 20px; + width: 77px; + + .group { + height: 20px; + left: 0; + position: relative; /* fixed → relative로 수정 */ + top: 0; + } + + .overlap-group { + background-color: #d9d9d9; + border-radius: 30px; + height: 20px; + position: relative; + width: 77px; + } + + .text-wrapper { + color: #ffffff; + font-family: 'Pretendard-ExtraBold', Helvetica; + font-size: 10px; + height: 13px; + left: 13px; + line-height: 12px; + position: absolute; + text-align: center; + top: 3px; + white-space: nowrap; + width: 50px; + } +`; + +const SolveBox = styled.div` + height: 20px; + width: 77px; + + .group { + height: 20px; + left: 0; + position: relative; /* fixed → relative로 수정 */ + top: 0; + } + + .overlap-group { + background-color: #c9ffce; + border-radius: 30px; + height: 20px; + position: relative; + width: 77px; + } + + .text-wrapper { + color: #007c0c; + font-family: 'Pretendard-ExtraBold', Helvetica; + font-size: 10px; + height: 13px; + left: 13px; + line-height: 12px; + position: absolute; + text-align: center; + top: 4px; + white-space: nowrap; + width: 50px; + } +`; + +export const ProblemButton = () => { + return ( + +
+
+
problem
+
+
+
+ ); +}; + +export const SolveButton = () => { + return ( + +
+
+
solve
+
+
+
+ ); +}; + +const QuestionButton = ({ solved }: { solved: number }) => { + return solved === 1 ? : ; +}; + +export default QuestionButton; diff --git a/src/components/QuestionHeader.tsx b/src/components/QuestionHeader.tsx new file mode 100644 index 0000000..42675db --- /dev/null +++ b/src/components/QuestionHeader.tsx @@ -0,0 +1,25 @@ +import styled from 'styled-components'; +import QuestionButton from './QuestionButton'; +import QuestionTitle from './QuestionTitle'; + +const HeaderContainer = styled.div` + display: flex; + align-items: center; + gap: 8px; +`; + +interface QuestionHeaderProps { + solved: number; + title: string; +} + +function QuestionHeader({ solved, title }: QuestionHeaderProps) { + return ( + + + + + ); +} + +export default QuestionHeader; diff --git a/src/components/QuestionTitle.tsx b/src/components/QuestionTitle.tsx new file mode 100644 index 0000000..da892d2 --- /dev/null +++ b/src/components/QuestionTitle.tsx @@ -0,0 +1,18 @@ +import styled from 'styled-components'; + +const TitleContainer = styled.div` + font-family: 'Pretendard-SemiBold', Helvetica; + font-size: 10px; + color: #000; + margin-left: 8px; +`; + +interface TitleProps { + text: string; +} + +function QuestionTitle({ text }: TitleProps) { + return {text}; +} + +export default QuestionTitle; diff --git a/src/components/QuestionUser.tsx b/src/components/QuestionUser.tsx new file mode 100644 index 0000000..7140476 --- /dev/null +++ b/src/components/QuestionUser.tsx @@ -0,0 +1,25 @@ +import styled from 'styled-components'; + +const UserContainer = styled.div` + font-family: 'Pretendard-ExtraLight', Helvetica; + font-size: 10px; + color: #666; + display: flex; + gap: 8px; +`; + +interface QuestionUserProps { + nickname: string; + time: string; +} + +function QuestionUser({ nickname, time }: QuestionUserProps) { + return ( + + {nickname} + {time} + + ); +} + +export default QuestionUser; diff --git a/src/components/QuestionUtil.tsx b/src/components/QuestionUtil.tsx new file mode 100644 index 0000000..3687cfe --- /dev/null +++ b/src/components/QuestionUtil.tsx @@ -0,0 +1,69 @@ +import { useState } from 'react'; +import styled from 'styled-components'; +import { + ChatBubbleOvalLeftEllipsisIcon, + EyeIcon, +} from '@heroicons/react/24/outline'; +import { HeartIcon as SolidHeartIcon } from '@heroicons/react/24/solid'; +import { HeartIcon as OutlineHeartIcon } from '@heroicons/react/24/outline'; + +// Styled Components +const UtilContainer = styled.div` + display: flex; + gap: 12px; + font-family: 'Pretendard-ExtraLight', Helvetica; + font-size: 10px; + color: #666; +`; + +const IconWrapper = styled.span` + display: flex; + align-items: center; + gap: 4px; + cursor: pointer; + user-select: none; + + svg { + width: 16px; + height: 16px; + } +`; + +interface QuestionUtilProps { + likes: number; + comments: number; + views: number; +} + +function QuestionUtil({ likes, comments, views }: QuestionUtilProps) { + const [isLiked, setIsLiked] = useState(false); + const [likeCount, setLikeCount] = useState(likes); + + const toggleLike = () => { + setIsLiked(!isLiked); + setLikeCount((prev) => (isLiked ? prev - 1 : prev + 1)); + }; + + return ( + + + {isLiked ? ( + + ) : ( + + )} + {likeCount} + + + + {comments} + + + + {views} + + + ); +} + +export default QuestionUtil; diff --git a/src/types/postdata.ts b/src/types/postdata.ts new file mode 100644 index 0000000..e357de7 --- /dev/null +++ b/src/types/postdata.ts @@ -0,0 +1,12 @@ +export interface PostData { + id: number; + title: string; + content: string; + solved: number; + nickname: string; + created_at: string; + comment_count: number; + like_count: number; + view: number; + tags: string | null; +}