From 352f3799ea248af0d0a36f310f648502372e9a2b Mon Sep 17 00:00:00 2001 From: leechan Date: Tue, 17 Dec 2024 21:13:37 +0900 Subject: [PATCH 01/13] =?UTF-8?q?[Refactor]=20auth.api.ts=20=ED=95=84?= =?UTF-8?q?=EC=9A=94=EC=97=86=EB=8A=94=20=EB=B6=80=EB=B6=84=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/auth.api.ts | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/apis/auth.api.ts b/src/apis/auth.api.ts index 6cabed1..3edd653 100644 --- a/src/apis/auth.api.ts +++ b/src/apis/auth.api.ts @@ -1,6 +1,5 @@ -import { LoginProps } from '@/pages/LoginPage'; import { JoinProps } from '@/pages/JoinPage'; -import httpClient from './http.api'; +import { httpClient } from './http.api'; export const join = async (data: JoinProps) => { const response = await httpClient.post('/api/users/join', data); @@ -12,11 +11,3 @@ export interface LoginResponse { success: boolean; token?: string; } - -export const login = async (data: LoginProps) => { - const response = await httpClient.post( - '/api/users/login', - data - ); - return response.data; -}; From 7f37d56f1ecd96f5d69703977d81beb3ebf50f0b Mon Sep 17 00:00:00 2001 From: leechan Date: Tue, 17 Dec 2024 21:14:06 +0900 Subject: [PATCH 02/13] =?UTF-8?q?[Refacotr]=20=ED=8F=B4=EB=8D=94=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/mainpost.api.ts | 13 --- src/components/AuthButton.tsx | 95 ------------------ src/components/QuesitonTag.tsx | 34 ------- src/components/QuestionBody.tsx | 19 ---- src/components/QuestionBottom.tsx | 35 ------- src/components/QuestionBox.tsx | 63 ------------ src/components/QuestionButton.tsx | 99 ------------------- src/components/QuestionHeader.tsx | 25 ----- src/components/QuestionTitle.tsx | 18 ---- src/components/QuestionUser.tsx | 25 ----- src/components/QuestionUtil.tsx | 69 ------------- .../ui/atoms/PasswordGuideLines.tsx | 87 ---------------- src/model/main.model.ts | 25 ----- 13 files changed, 607 deletions(-) delete mode 100644 src/apis/mainpost.api.ts delete mode 100644 src/components/AuthButton.tsx delete mode 100644 src/components/QuesitonTag.tsx delete mode 100644 src/components/QuestionBody.tsx delete mode 100644 src/components/QuestionBottom.tsx delete mode 100644 src/components/QuestionBox.tsx delete mode 100644 src/components/QuestionButton.tsx delete mode 100644 src/components/QuestionHeader.tsx delete mode 100644 src/components/QuestionTitle.tsx delete mode 100644 src/components/QuestionUser.tsx delete mode 100644 src/components/QuestionUtil.tsx delete mode 100644 src/components/ui/atoms/PasswordGuideLines.tsx delete mode 100644 src/model/main.model.ts diff --git a/src/apis/mainpost.api.ts b/src/apis/mainpost.api.ts deleted file mode 100644 index f0b2766..0000000 --- a/src/apis/mainpost.api.ts +++ /dev/null @@ -1,13 +0,0 @@ -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/AuthButton.tsx b/src/components/AuthButton.tsx deleted file mode 100644 index 305a041..0000000 --- a/src/components/AuthButton.tsx +++ /dev/null @@ -1,95 +0,0 @@ -// src/components/Header.tsx -import { useNavigate } from 'react-router'; -import { useSelector, useDispatch } from 'react-redux'; -import { RootState } from '@/store/rootReducer'; -import { logout } from '@/hooks/userSlice'; -import { removeToken } from '@/apis/http.api'; -import styled from 'styled-components'; - -function AuthButton() { - const navigate = useNavigate(); - const dispatch = useDispatch(); - const isLoggedIn = useSelector((state: RootState) => state.user.isLoggedIn); - - const handleLogin = () => { - // 로그인 페이지로 이동 - navigate('/login'); - }; - - const handleLogout = () => { - // Redux 상태 초기화 - dispatch(logout()); - // 토큰 제거 - removeToken(); - // 로그아웃 후 메인 페이지 이동(필요하다면) - navigate('/'); - }; - - return isLoggedIn ? ( - -
-
-
로그아웃
-
-
-
- ) : ( - -
-
-
로그인
-
-
-
- ); -} - -export default AuthButton; - -// Styled Components 동일 -const ButtonBox = styled.div` - height: 52px; - width: 114px; - - .group { - height: 100%; - position: relative; - } - - .overlap-group { - border-radius: 30px; - display: flex; - align-items: center; - justify-content: center; - height: 100%; - width: 100%; - cursor: pointer; - transition: background-color 0.2s ease-in-out; - } - - .text-wrapper { - color: #ffffff; - font-family: 'Pretendard-ExtraBold', Helvetica; - font-size: 15px; - line-height: 12px; - white-space: nowrap; - } -`; - -const LoginBox = styled(ButtonBox)` - .overlap-group { - background-color: #32c040; - } - .overlap-group:hover { - background-color: #28a034; - } -`; - -const LogoutBox = styled(ButtonBox)` - .overlap-group { - background-color: #9a9a9a; - } - .overlap-group:hover { - background-color: #7f7f7f; - } -`; diff --git a/src/components/QuesitonTag.tsx b/src/components/QuesitonTag.tsx deleted file mode 100644 index de55c2a..0000000 --- a/src/components/QuesitonTag.tsx +++ /dev/null @@ -1,34 +0,0 @@ -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 deleted file mode 100644 index 9829628..0000000 --- a/src/components/QuestionBody.tsx +++ /dev/null @@ -1,19 +0,0 @@ -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 deleted file mode 100644 index da776ad..0000000 --- a/src/components/QuestionBottom.tsx +++ /dev/null @@ -1,35 +0,0 @@ -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 deleted file mode 100644 index 69d60d8..0000000 --- a/src/components/QuestionBox.tsx +++ /dev/null @@ -1,63 +0,0 @@ -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 deleted file mode 100644 index d59f66a..0000000 --- a/src/components/QuestionButton.tsx +++ /dev/null @@ -1,99 +0,0 @@ -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 deleted file mode 100644 index 42675db..0000000 --- a/src/components/QuestionHeader.tsx +++ /dev/null @@ -1,25 +0,0 @@ -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 deleted file mode 100644 index da892d2..0000000 --- a/src/components/QuestionTitle.tsx +++ /dev/null @@ -1,18 +0,0 @@ -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 deleted file mode 100644 index 7140476..0000000 --- a/src/components/QuestionUser.tsx +++ /dev/null @@ -1,25 +0,0 @@ -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 deleted file mode 100644 index 3687cfe..0000000 --- a/src/components/QuestionUtil.tsx +++ /dev/null @@ -1,69 +0,0 @@ -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/components/ui/atoms/PasswordGuideLines.tsx b/src/components/ui/atoms/PasswordGuideLines.tsx deleted file mode 100644 index f5f03be..0000000 --- a/src/components/ui/atoms/PasswordGuideLines.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import { useEffect, useState } from 'react'; -import { CheckCircleIcon } from '@heroicons/react/20/solid'; -import styled from 'styled-components'; - -interface Guideline { - label: string; - isValid: boolean; - check: (password: string) => boolean; -} - -const Container = styled.div` - display: flex; - flex-direction: column; - > * + * { - margin-top: 0.5rem; - } -`; - -const GuidelineItem = styled.div` - display: flex; - align-items: center; -`; - -const StyledCheckCircleIcon = styled(CheckCircleIcon)` - width: 1.25rem; - height: 1.25rem; - color: #32c040; -`; - -const EmptyCircle = styled.span` - margin-left: 0.15rem; - width: 0.85rem; - height: 0.85rem; - border: 1px solid #374151; - border-radius: 9999px; - display: inline-block; -`; - -const GuidelineLabel = styled.span<{ isValid: boolean }>` - margin-left: 0.5rem; - font-size: 0.875rem; - color: ${(props) => (props.isValid ? '#32C040' : '#6b7280')}; -`; - -const PasswordGuideLines = ({ password }: { password: string }) => { - const [guidelines, setGuidelines] = useState([ - { - label: '8자 이상, 15자 이하로 설정해 주세요', - isValid: false, - check: (password: string) => - password.length >= 8 && password.length <= 15, - }, - { - label: '특수 문자를 사용해 주세요', - isValid: false, - check: (password: string) => /[!@#$%^&*(),.?":{}|<>]/.test(password), - }, - { - label: '동일한 문자가 4번 반복되면 안돼요', - isValid: false, - check: (password: string) => - !/(.)\1{3}/.test(password.replace(/\s/g, '')), - }, - ]); - - useEffect(() => { - setGuidelines((prevGuidelines) => - prevGuidelines.map((guideline) => ({ - ...guideline, - isValid: guideline.check(password), - })) - ); - }, [password]); - - return ( - - {guidelines.map(({ label, isValid }, index) => ( - - {isValid ? : } - {label} - - ))} - - ); -}; - -export default PasswordGuideLines; diff --git a/src/model/main.model.ts b/src/model/main.model.ts deleted file mode 100644 index fa407cd..0000000 --- a/src/model/main.model.ts +++ /dev/null @@ -1,25 +0,0 @@ -export interface mainUsers { - users: string | null; -} -export interface mainTags { - id: number | null; - name: string; - isActive?: boolean; -} -export interface mainPosts { - id: number; - title: string; - content: string; - created_at: string; - updated_at: string; - solved: boolean; - nickname: string; - comment_count: number; - like_count: number; - tags: string; -} -export interface mainData { - users: mainUsers; - tags: mainTags[]; - posts: mainPosts[]; -} \ No newline at end of file From 9a00db2ac9d9b34ef65c5e48f6b1620d191a5d2a Mon Sep 17 00:00:00 2001 From: leechan Date: Tue, 17 Dec 2024 21:14:31 +0900 Subject: [PATCH 03/13] =?UTF-8?q?[Feat]=20=EA=B8=B0=EB=B3=B8=20=EC=9C=A0?= =?UTF-8?q?=EC=A0=80=20=EC=9D=B4=EB=AF=B8=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/DefaultAvatar.svg | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 src/assets/DefaultAvatar.svg diff --git a/src/assets/DefaultAvatar.svg b/src/assets/DefaultAvatar.svg new file mode 100644 index 0000000..e5daaa3 --- /dev/null +++ b/src/assets/DefaultAvatar.svg @@ -0,0 +1,4 @@ + + + + From 84b16891d360704c783f1ea2175d640df767994d Mon Sep 17 00:00:00 2001 From: leechan Date: Tue, 17 Dec 2024 21:15:55 +0900 Subject: [PATCH 04/13] =?UTF-8?q?[Refactor]=20=ED=8F=B4=EB=8D=94=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/main-page/MainTagList.tsx | 32 +++--- src/components/main-page/QuestionBox.tsx | 64 ++++++++++++ src/components/ui/atoms/Avator.tsx | 4 +- .../ui/atoms/join-atom/PasswordGuideLines.tsx | 87 +++++++++++++++++ .../ui/atoms/mainpage-atom/QuesitonTag.tsx | 34 +++++++ .../ui/atoms/mainpage-atom/QuestionButton.tsx | 97 +++++++++++++++++++ .../ui/atoms/mainpage-atom/QuestionTitle.tsx | 19 ++++ .../ui/atoms/mainpage-atom/QuestionUser.tsx | 26 +++++ .../ui/atoms/mainpage-atom/QuestionUtil.tsx | 56 +++++++++++ .../mainpage-molecule/QuestionBody.tsx | 21 ++++ .../mainpage-molecule/QuestionBottom.tsx | 35 +++++++ .../mainpage-molecule/QuestionHeader.tsx | 25 +++++ 12 files changed, 483 insertions(+), 17 deletions(-) create mode 100644 src/components/main-page/QuestionBox.tsx create mode 100644 src/components/ui/atoms/join-atom/PasswordGuideLines.tsx create mode 100644 src/components/ui/atoms/mainpage-atom/QuesitonTag.tsx create mode 100644 src/components/ui/atoms/mainpage-atom/QuestionButton.tsx create mode 100644 src/components/ui/atoms/mainpage-atom/QuestionTitle.tsx create mode 100644 src/components/ui/atoms/mainpage-atom/QuestionUser.tsx create mode 100644 src/components/ui/atoms/mainpage-atom/QuestionUtil.tsx create mode 100644 src/components/ui/molecules/mainpage-molecule/QuestionBody.tsx create mode 100644 src/components/ui/molecules/mainpage-molecule/QuestionBottom.tsx create mode 100644 src/components/ui/molecules/mainpage-molecule/QuestionHeader.tsx diff --git a/src/components/main-page/MainTagList.tsx b/src/components/main-page/MainTagList.tsx index c69b273..f4dabe0 100644 --- a/src/components/main-page/MainTagList.tsx +++ b/src/components/main-page/MainTagList.tsx @@ -1,45 +1,48 @@ import { useMainTags } from '@/hooks/useMainTags'; import { useSearchParams } from 'react-router'; -import { useState } from 'react'; import styled from 'styled-components'; - const MainTagList = () => { const { maintags } = useMainTags(); const [searchParams, setSearchParams] = useSearchParams(); - const handleTags = (id:number | null) => { + const handleTags = (id: number | null) => { const newSearchParams = new URLSearchParams(searchParams); - if(id === null) { - newSearchParams.delete('id',); + if (id === null) { + newSearchParams.delete('id'); } else { newSearchParams.set('id', id.toString()); } setSearchParams(newSearchParams); - } + }; return ( - { - maintags.map((tag)=>(handleTags(tag.id)}>{tag.name})) - } + {maintags.map((tag) => ( + handleTags(tag.id)} + > + {tag.name} + + ))} ); -} +}; const MainTagListContainer = styled.div` display: flex; justify-content: space-evenly; align-items: center; - `; const TagButton = styled.button` border-radius: 30px; font-size: 1.1rem; background-color: transparent; - color: #32C040; - border: 1px solid #32C040; + color: #32c040; + border: 1px solid #32c040; padding: 8px 20px; text-decoration: none; cursor: pointer; @@ -52,7 +55,6 @@ const TagButton = styled.button` &.active { background-color: rgba(49, 191, 63, 0.23); } - `; -export default MainTagList; \ No newline at end of file +export default MainTagList; diff --git a/src/components/main-page/QuestionBox.tsx b/src/components/main-page/QuestionBox.tsx new file mode 100644 index 0000000..664caf0 --- /dev/null +++ b/src/components/main-page/QuestionBox.tsx @@ -0,0 +1,64 @@ +import { useEffect, useState } from 'react'; +import styled from 'styled-components'; +import QuestionBody from '../ui/molecules/mainpage-molecule/QuestionBody'; +import QuestionHeader from '../ui/molecules/mainpage-molecule/QuestionHeader'; +import QuestionTag from '../ui/atoms/mainpage-atom/QuesitonTag'; +import QuestionBottom from '../ui/molecules/mainpage-molecule/QuestionBottom'; +import { fetchMainData } from '@/apis/maindata.api'; +import { mainPosts } from '@/types/main.model'; + +const QuestionBoxContainer = styled.div` + display: flex; + flex-direction: column; + margin-top: 30px; + gap: 30px; +`; + +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 fetchMainData(); + setPosts(data.posts); + } 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/ui/atoms/Avator.tsx b/src/components/ui/atoms/Avator.tsx index 4cd4161..2f493a4 100644 --- a/src/components/ui/atoms/Avator.tsx +++ b/src/components/ui/atoms/Avator.tsx @@ -22,8 +22,8 @@ const styles = { height: 200px; `, small: ` - width: 64px; - height: 64px; + width: 48px; + height: 48px; `, disabled: ` opacity: 0.5; diff --git a/src/components/ui/atoms/join-atom/PasswordGuideLines.tsx b/src/components/ui/atoms/join-atom/PasswordGuideLines.tsx new file mode 100644 index 0000000..f5f03be --- /dev/null +++ b/src/components/ui/atoms/join-atom/PasswordGuideLines.tsx @@ -0,0 +1,87 @@ +import { useEffect, useState } from 'react'; +import { CheckCircleIcon } from '@heroicons/react/20/solid'; +import styled from 'styled-components'; + +interface Guideline { + label: string; + isValid: boolean; + check: (password: string) => boolean; +} + +const Container = styled.div` + display: flex; + flex-direction: column; + > * + * { + margin-top: 0.5rem; + } +`; + +const GuidelineItem = styled.div` + display: flex; + align-items: center; +`; + +const StyledCheckCircleIcon = styled(CheckCircleIcon)` + width: 1.25rem; + height: 1.25rem; + color: #32c040; +`; + +const EmptyCircle = styled.span` + margin-left: 0.15rem; + width: 0.85rem; + height: 0.85rem; + border: 1px solid #374151; + border-radius: 9999px; + display: inline-block; +`; + +const GuidelineLabel = styled.span<{ isValid: boolean }>` + margin-left: 0.5rem; + font-size: 0.875rem; + color: ${(props) => (props.isValid ? '#32C040' : '#6b7280')}; +`; + +const PasswordGuideLines = ({ password }: { password: string }) => { + const [guidelines, setGuidelines] = useState([ + { + label: '8자 이상, 15자 이하로 설정해 주세요', + isValid: false, + check: (password: string) => + password.length >= 8 && password.length <= 15, + }, + { + label: '특수 문자를 사용해 주세요', + isValid: false, + check: (password: string) => /[!@#$%^&*(),.?":{}|<>]/.test(password), + }, + { + label: '동일한 문자가 4번 반복되면 안돼요', + isValid: false, + check: (password: string) => + !/(.)\1{3}/.test(password.replace(/\s/g, '')), + }, + ]); + + useEffect(() => { + setGuidelines((prevGuidelines) => + prevGuidelines.map((guideline) => ({ + ...guideline, + isValid: guideline.check(password), + })) + ); + }, [password]); + + return ( + + {guidelines.map(({ label, isValid }, index) => ( + + {isValid ? : } + {label} + + ))} + + ); +}; + +export default PasswordGuideLines; diff --git a/src/components/ui/atoms/mainpage-atom/QuesitonTag.tsx b/src/components/ui/atoms/mainpage-atom/QuesitonTag.tsx new file mode 100644 index 0000000..dcd267e --- /dev/null +++ b/src/components/ui/atoms/mainpage-atom/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-Light', Helvetica; + font-size: 12px; + padding: 8px 16px; + 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/ui/atoms/mainpage-atom/QuestionButton.tsx b/src/components/ui/atoms/mainpage-atom/QuestionButton.tsx new file mode 100644 index 0000000..b584ecd --- /dev/null +++ b/src/components/ui/atoms/mainpage-atom/QuestionButton.tsx @@ -0,0 +1,97 @@ +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: 30px; + position: relative; + width: 100px; + } + + .text-wrapper { + color: #ffffff; + font-family: 'Pretendard-ExtraBold', Helvetica; + font-size: 15px; + left: 20px; + line-height: 20px; + 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: 30px; + position: relative; + width: 100px; + } + + .text-wrapper { + color: #007c0c; + font-family: 'Pretendard-ExtraBold', Helvetica; + font-size: 15px; + left: 25px; + line-height: 20px; + 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/ui/atoms/mainpage-atom/QuestionTitle.tsx b/src/components/ui/atoms/mainpage-atom/QuestionTitle.tsx new file mode 100644 index 0000000..0075131 --- /dev/null +++ b/src/components/ui/atoms/mainpage-atom/QuestionTitle.tsx @@ -0,0 +1,19 @@ +import styled from 'styled-components'; + +const TitleContainer = styled.div` + font-family: 'Pretendard-SemiBold', Helvetica; + font-size: 18px; + color: #000; + margin-top: 5px; + margin-left: 30px; +`; + +interface TitleProps { + text: string; +} + +function QuestionTitle({ text }: TitleProps) { + return {text}; +} + +export default QuestionTitle; diff --git a/src/components/ui/atoms/mainpage-atom/QuestionUser.tsx b/src/components/ui/atoms/mainpage-atom/QuestionUser.tsx new file mode 100644 index 0000000..58cd5f0 --- /dev/null +++ b/src/components/ui/atoms/mainpage-atom/QuestionUser.tsx @@ -0,0 +1,26 @@ +import styled from 'styled-components'; + +const UserContainer = styled.div` + font-family: 'Pretendard-ExtraLight', Helvetica; + font-size: 12px; + color: #666; + display: flex; + gap: 8px; + margin-left: 5px; +`; + +interface QuestionUserProps { + nickname: string; + time: string; +} + +function QuestionUser({ nickname, time }: QuestionUserProps) { + return ( + + {nickname} + {time} + + ); +} + +export default QuestionUser; diff --git a/src/components/ui/atoms/mainpage-atom/QuestionUtil.tsx b/src/components/ui/atoms/mainpage-atom/QuestionUtil.tsx new file mode 100644 index 0000000..f514bc9 --- /dev/null +++ b/src/components/ui/atoms/mainpage-atom/QuestionUtil.tsx @@ -0,0 +1,56 @@ +import styled from 'styled-components'; +import { + ChatBubbleOvalLeftEllipsisIcon, + EyeIcon, +} from '@heroicons/react/24/outline'; +import { HeartIcon as SolidHeartIcon } from '@heroicons/react/24/solid'; + +// Styled Components +const UtilContainer = styled.div` + display: flex; + gap: 12px; + font-family: 'Pretendard-ExtraLight', Helvetica; + font-size: 13px; + color: #666; + margin-right: 5px; +`; + +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) { + return ( + + + + {likes} + + + + {comments} + + + + {views} + + + ); +} + +export default QuestionUtil; diff --git a/src/components/ui/molecules/mainpage-molecule/QuestionBody.tsx b/src/components/ui/molecules/mainpage-molecule/QuestionBody.tsx new file mode 100644 index 0000000..12f73ba --- /dev/null +++ b/src/components/ui/molecules/mainpage-molecule/QuestionBody.tsx @@ -0,0 +1,21 @@ +import styled from 'styled-components'; + +const BodyContainer = styled.div` + font-family: 'Pretendard-Light', Helvetica; + font-size: 15px; + margin-left: 5px; + line-height: 1.5; + color: #333; + margin-top: 20px; + margin-right: 5px; +`; + +interface QuestionBodyProps { + content: string; +} + +function QuestionBody({ content }: QuestionBodyProps) { + return {content}; +} + +export default QuestionBody; diff --git a/src/components/ui/molecules/mainpage-molecule/QuestionBottom.tsx b/src/components/ui/molecules/mainpage-molecule/QuestionBottom.tsx new file mode 100644 index 0000000..810b3e8 --- /dev/null +++ b/src/components/ui/molecules/mainpage-molecule/QuestionBottom.tsx @@ -0,0 +1,35 @@ +import styled from 'styled-components'; +import QuestionUser from '../../atoms/mainpage-atom/QuestionUser'; +import QuestionUtil from '../../atoms/mainpage-atom/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/ui/molecules/mainpage-molecule/QuestionHeader.tsx b/src/components/ui/molecules/mainpage-molecule/QuestionHeader.tsx new file mode 100644 index 0000000..28a4110 --- /dev/null +++ b/src/components/ui/molecules/mainpage-molecule/QuestionHeader.tsx @@ -0,0 +1,25 @@ +import styled from 'styled-components'; +import QuestionButton from '../../atoms/mainpage-atom/QuestionButton'; +import QuestionTitle from '../../atoms/mainpage-atom/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; From 10ee50e610d0a99055d38e67a7e6117749e79909 Mon Sep 17 00:00:00 2001 From: leechan Date: Tue, 17 Dec 2024 21:16:35 +0900 Subject: [PATCH 05/13] =?UTF-8?q?[Refactor]=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/http.api.ts | 84 +++++++++++++++++++++++++++----------------- 1 file changed, 51 insertions(+), 33 deletions(-) diff --git a/src/apis/http.api.ts b/src/apis/http.api.ts index a532349..fe233f9 100644 --- a/src/apis/http.api.ts +++ b/src/apis/http.api.ts @@ -1,11 +1,6 @@ -import axios, { - AxiosInstance, - AxiosRequestConfig, - AxiosHeaders, - AxiosRequestHeaders, -} from 'axios'; +import axios, { AxiosRequestConfig } from 'axios'; -const BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:3333'; +const BASE_URL = import.meta.env.VITE_API_BASE_URL; const DEFAULT_TIMEOUT = 30000; // 요청 제한 시간 // 토큰 관리 함수 @@ -21,46 +16,43 @@ function removeToken() { localStorage.removeItem('token'); } -// Axios 인스턴스 생성 함수 -export const createClient = (config?: AxiosRequestConfig): AxiosInstance => { +export const createClient = (config?: AxiosRequestConfig) => { + const token = getToken(); // 토큰 가져오기 const axiosInstance = axios.create({ baseURL: BASE_URL, timeout: DEFAULT_TIMEOUT, headers: { 'Content-Type': 'application/json', + Authorization: token ? `Bearer ${token}` : '', }, withCredentials: true, ...config, }); - // 요청 인터셉터: Authorization 헤더 동적 설정 axiosInstance.interceptors.request.use( (config) => { - const accessToken = getToken(); // 여기서 getToken()을 호출하여 실제로 사용 - if (accessToken) { - if ( - config.headers && - 'set' in config.headers && - typeof (config.headers as AxiosHeaders).set === 'function' - ) { - // AxiosHeaders 타입으로 헤더를 다루는 경우 - (config.headers as AxiosHeaders).set( - 'Authorization', - `Bearer ${accessToken}` - ); - } else { - // 일반 객체로 헤더를 다루는 경우 - config.headers = { - ...config.headers, - Authorization: `Bearer ${accessToken}`, - } as AxiosRequestHeaders; - } + const token = getToken(); + if (token) { + config.headers.Authorization = `Bearer ${token}`; } - console.log('Request Headers:', config.headers); // 디버깅용 로그 return config; }, (error) => { - console.error('Request Error:', error); + return Promise.reject(error); + } + ); + + axiosInstance.interceptors.response.use( + (response) => { + return response; + }, + (error) => { + // 로그인 만료 처리 + if (error.response.status === 401) { + removeToken(); + window.location.href = '/login'; + return; + } return Promise.reject(error); } ); @@ -68,10 +60,36 @@ export const createClient = (config?: AxiosRequestConfig): AxiosInstance => { return axiosInstance; }; -// 기본 Axios 인스턴스 생성 export const httpClient = createClient(); // 토큰 관련 함수 export export { setToken, removeToken, getToken }; -export default httpClient; +// 공통 요청 부분 + +type RequestMethod = 'get' | 'post' | 'put' | 'delete'; + +export const requestHandler = async ( + method: RequestMethod, + url: string, + payload?: T +) => { + let response; + + switch (method) { + case 'post': + response = await httpClient.post(url, payload); + break; + case 'get': + response = await httpClient.get(url); + break; + case 'put': + response = await httpClient.put(url, payload); + break; + case 'delete': + response = await httpClient.delete(url); + break; + } + + return response.data; +}; From c102646ecc470c3fc9eac99a0cae85b0f08c233a Mon Sep 17 00:00:00 2001 From: leechan Date: Tue, 17 Dec 2024 21:17:24 +0900 Subject: [PATCH 06/13] =?UTF-8?q?[Refactor]=20=ED=8F=B4=EB=8D=94=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=EC=9C=BC=EB=A1=9C=20=EC=9D=B8=ED=95=9C=20imp?= =?UTF-8?q?ort=20=EB=AC=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/maindata.api.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/apis/maindata.api.ts b/src/apis/maindata.api.ts index d7ad4c4..63b8e83 100644 --- a/src/apis/maindata.api.ts +++ b/src/apis/maindata.api.ts @@ -1,5 +1,5 @@ -import httpClient from '@/apis/http.api'; -import { mainData } from '@/model/main.model'; +import { httpClient } from './http.api'; +import { mainData } from '@/types/main.model'; export const fetchMainData = async () => { const response = await httpClient.get('/api/main'); @@ -8,4 +8,4 @@ export const fetchMainData = async () => { } catch { throw Error; } -} \ No newline at end of file +}; From 8d436d7ca3fddb573abcaa05e0777b2f139679a7 Mon Sep 17 00:00:00 2001 From: leechan Date: Tue, 17 Dec 2024 21:17:54 +0900 Subject: [PATCH 07/13] =?UTF-8?q?[Feature]=20=EB=A9=94=EB=89=B4=20?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Header.tsx | 12 +-- src/components/MenuButton.tsx | 146 ++++++++++++++++++++++++++++++++++ 2 files changed, 148 insertions(+), 10 deletions(-) create mode 100644 src/components/MenuButton.tsx diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 24ab86c..690cda9 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -1,21 +1,13 @@ import styled from 'styled-components'; import logo from '@/assets/logo.svg'; -import Avatar from './ui/atoms/Avator'; -import AuthButton from './AuthButton'; +import MenuButton from './MenuButton'; const Header = () => ( Logo -
- -
- +
); diff --git a/src/components/MenuButton.tsx b/src/components/MenuButton.tsx new file mode 100644 index 0000000..e78fe03 --- /dev/null +++ b/src/components/MenuButton.tsx @@ -0,0 +1,146 @@ +import { useState } from 'react'; +import styled from 'styled-components'; +import { XMarkIcon, Bars3Icon } from '@heroicons/react/24/outline'; +import Avatar from './ui/atoms/Avator'; +import defaultAvatar from '@/assets/DefaultAvatar.svg'; +import { useNavigate } from 'react-router'; +import { logout } from '@/hooks/userSlice'; +import { useSelector, useDispatch } from 'react-redux'; +import { RootState } from '@/store/rootReducer'; +import { removeToken } from '@/apis/http.api'; + +const MenuButton = () => { + const [isOpen, setIsOpen] = useState(false); + const navigate = useNavigate(); + const dispatch = useDispatch(); + const isLoggedIn = useSelector((state: RootState) => state.user.isLoggedIn); + + const handleLogin = () => { + // 로그인 페이지로 이동 + navigate('/login'); + }; + + const handleMyPage = () => { + navigate('/mypage'); + }; + + const handleLogout = () => { + // Redux 상태 초기화 + dispatch(logout()); + // 토큰 제거 + removeToken(); + // 로그아웃 후 메인 페이지 이동(필요하다면) + navigate('/'); + }; + + const toggleMenu = () => { + setIsOpen((prev) => !prev); + }; + + const nickname = useSelector( + (state: RootState) => state.user.userInfo?.nickname + ); + + return ( + + {/* 햄버거 버튼 */} + + + {/* 드롭다운 메뉴 */} + {isOpen && ( + +
    +
  • {} : handleLogin}> + {/* 아바타 표시 */} + + + {isLoggedIn ? nickname : '로그인'} + +
  • + {isLoggedIn ? ( + <> +
  • 마이페이지
  • +
  • 문의하기
  • +
  • 서비스 소개
  • +
  • 로그아웃
  • + + ) : ( + <> +
  • 문의하기
  • +
  • 서비스 소개
  • + + )} +
+
+ )} +
+ ); +}; + +export default MenuButton; + +const DropdownContainer = styled.div` + position: relative; +`; + +const Button = styled.button` + background: none; + border: none; + cursor: pointer; + + .icon { + width: 30px; + height: 30px; + color: #333; + } +`; + +const DropdownMenu = styled.div` + position: absolute; + top: 100%; + right: 0; + width: 250px; + background-color: white; + box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1); + border: 1px solid #ddd; + z-index: 100; + + ul { + list-style: none; + padding: 10px 0; + margin: 0; + + li { + padding: 10px 15px; + cursor: pointer; + font-weight: bold; + display: flex; + align-items: center; + + &:hover { + background-color: #f3f3f3; + } + } + } +`; + +const AvatarContainer = styled.div` + display: flex; + align-items: center; + gap: 10px; +`; From 1f102211b497e749b75aafd1dc9ee8b4e77f78bb Mon Sep 17 00:00:00 2001 From: leechan Date: Tue, 17 Dec 2024 21:18:12 +0900 Subject: [PATCH 08/13] =?UTF-8?q?[Refactor]=20=ED=8F=B4=EB=8D=94=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=EC=9C=BC=EB=A1=9C=20=EC=9D=B8=ED=95=9C=20imp?= =?UTF-8?q?ort=20=EB=AC=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/JoinPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/JoinPage.tsx b/src/pages/JoinPage.tsx index 8e540e0..c2c87f0 100644 --- a/src/pages/JoinPage.tsx +++ b/src/pages/JoinPage.tsx @@ -4,7 +4,7 @@ import styled from 'styled-components'; import { useNavigate } from 'react-router'; import qublogo from '@/assets/qublogo.svg'; import { join } from '@/apis/auth.api'; -import PasswordGuideLines from '@/components/ui/atoms/PasswordGuideLines'; +import PasswordGuideLines from '@/components/ui/atoms/join-atom/PasswordGuideLines'; import Input from '@/components/ui/atoms/Input'; export interface JoinProps { From a6268c72f98107329e651434faa5587e1d77d73a Mon Sep 17 00:00:00 2001 From: leechan Date: Tue, 17 Dec 2024 21:18:36 +0900 Subject: [PATCH 09/13] =?UTF-8?q?[Refactor]=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=20=EC=8B=9C=20=ED=86=A0=ED=81=B0=20=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/userSlice.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/hooks/userSlice.ts b/src/hooks/userSlice.ts index 3ce5fd0..4a5b8f4 100644 --- a/src/hooks/userSlice.ts +++ b/src/hooks/userSlice.ts @@ -85,6 +85,10 @@ const userSlice = createSlice({ state.userInfo = userInfo; state.loading = false; state.error = null; + + if (typeof window !== 'undefined') { + localStorage.setItem('token', action.payload); + } }) .addCase(loginAsync.rejected, (state, action) => { state.loading = false; From 937442b9d159cdbec48a2f2263f6dde8e92509c9 Mon Sep 17 00:00:00 2001 From: leechan Date: Tue, 17 Dec 2024 21:19:15 +0900 Subject: [PATCH 10/13] =?UTF-8?q?[Feat]=20=EB=A9=94=EC=9D=B8=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=ED=83=80=EC=9E=85=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/types/main.model.ts | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 src/types/main.model.ts diff --git a/src/types/main.model.ts b/src/types/main.model.ts new file mode 100644 index 0000000..c3ae25c --- /dev/null +++ b/src/types/main.model.ts @@ -0,0 +1,25 @@ +export interface mainUsers { + users: string | null; +} +export interface mainTags { + id: number | null; + name: string; + isActive?: boolean; +} +export interface mainPosts { + id: number; + title: string; + content: string; + solved: number; + nickname: string; + created_at: string; + comment_count: number; + like_count: number; + view: number; + tags: string | null; +} +export interface mainData { + users: mainUsers; + tags: mainTags[]; + posts: mainPosts[]; +} From 2859027464614f7f3deeb57f170d5819e09a97af Mon Sep 17 00:00:00 2001 From: leechan Date: Tue, 17 Dec 2024 21:19:33 +0900 Subject: [PATCH 11/13] =?UTF-8?q?[Feat]=20=EC=A7=88=EB=AC=B8=20=EC=BB=A8?= =?UTF-8?q?=ED=85=90=EC=B8=A0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/HomePage.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx index 738a3c2..86020db 100644 --- a/src/pages/HomePage.tsx +++ b/src/pages/HomePage.tsx @@ -1,11 +1,15 @@ import MainTagList from '@/components/main-page/MainTagList'; import SearchInput from '@/components/main-page/SearchInput'; +import QuestionBox from '@/components/main-page/QuestionBox'; const HomePage = () => { - return
- - -
; + return ( +
+ + + +
+ ); }; export default HomePage; From 9527e79c1ca38e1066ff988691eb333ee7050d5d Mon Sep 17 00:00:00 2001 From: leechan Date: Tue, 17 Dec 2024 21:20:40 +0900 Subject: [PATCH 12/13] =?UTF-8?q?[Refactor]=20=ED=8F=B4=EB=8D=94=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=EC=9C=BC=EB=A1=9C=20=EC=9D=B8=ED=95=9C=20imp?= =?UTF-8?q?ort=20=EB=AC=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useMainTags.ts | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/hooks/useMainTags.ts b/src/hooks/useMainTags.ts index 2c4b2fe..795a41b 100644 --- a/src/hooks/useMainTags.ts +++ b/src/hooks/useMainTags.ts @@ -1,48 +1,48 @@ import { fetchMainData } from '@/apis/maindata.api'; -import { mainTags } from '@/model/main.model'; +import { mainTags } from '@/types/main.model'; import { useEffect, useState } from 'react'; import { useLocation } from 'react-router'; export const useMainTags = () => { const location = useLocation(); const [maintags, setMaintags] = useState([]); - + const setActive = () => { const params = new URLSearchParams(location.search); - if(params.get('id')) { - setMaintags((prev)=> { + if (params.get('id')) { + setMaintags((prev) => { return prev.map((item) => { - return {...item, isActive: item.id === Number(params.get('id'))} - }) - }) + return { ...item, isActive: item.id === Number(params.get('id')) }; + }); + }); } else { setMaintags((prev) => { return prev.map((item) => { - return {...item, isActive: false} - }) - }) + return { ...item, isActive: false }; + }); + }); } - } + }; - useEffect(()=>{ + useEffect(() => { fetchMainData().then((data) => { - if(!data.tags) return; + if (!data.tags) return; const tagsAll = [ { id: null, name: '전체', }, ...data.tags, - ] + ]; setMaintags(tagsAll); setActive(); - }) + }); }, []); - useEffect(()=>{ + useEffect(() => { setActive(); }, [location.search]); return { maintags }; -} \ No newline at end of file +}; From 28200ee152717e15475ad30ec0de08fd79080575 Mon Sep 17 00:00:00 2001 From: leechan Date: Tue, 17 Dec 2024 21:21:46 +0900 Subject: [PATCH 13/13] =?UTF-8?q?[Refactor]=20=ED=83=80=EC=9E=85=20export?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/LoginPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/LoginPage.tsx b/src/pages/LoginPage.tsx index 1f3ba91..f418c29 100644 --- a/src/pages/LoginPage.tsx +++ b/src/pages/LoginPage.tsx @@ -8,7 +8,7 @@ import { RootState } from '@/store/rootReducer'; import { loginAsync } from '@/hooks/userSlice'; import { AppDispatch } from '@/store/store'; -type LoginFormData = { +export type LoginFormData = { email: string; password: string; };