diff --git a/src/apis/authAxios.ts b/src/apis/authAxios.ts index 3ea4925..caa0850 100644 --- a/src/apis/authAxios.ts +++ b/src/apis/authAxios.ts @@ -12,10 +12,8 @@ export const getAuthAxios = (accessToken?: string) => { const authAxios = axios.create({ baseURL: 'http://13.124.54.157:8080', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${accessToken}`, - }, + headers: headersOption, + withCredentials: true, }); authAxios.interceptors.response.use(async (response) => { diff --git a/src/apis/mypage.ts b/src/apis/mypage.ts index 9b9db74..b3b0739 100644 --- a/src/apis/mypage.ts +++ b/src/apis/mypage.ts @@ -1,7 +1,7 @@ import { getAuthAxios } from './authAxios'; /** MYPAGE API */ -export const getMyPage = async () => { +export const getUserInfo = async () => { const access = localStorage.getItem('access'); if (!access) { @@ -14,3 +14,48 @@ export const getMyPage = async () => { console.log(response.data); return response.data; }; + +/** MYPAGE 이미지 수정 */ +export const putImage = async () => { + const access = localStorage.getItem('access'); + + if (!access) { + console.error('Access token is missing or invalid'); + throw new Error('Access token is missing or invalid'); + } + + const authAxios = getAuthAxios(access); + const response = await authAxios.put('/api/mypage/users/image'); + console.log(response.data); + return response.data; +}; + +interface MypagePosts { + pageParam: number; + problemType: 'created' | 'liked' | 'solved'; +} + +/** MYPAGE 게시글 조회 */ +export const getMypagePosts = async ({ pageParam = 0, problemType }: MypagePosts) => { + const access = localStorage.getItem('access'); + + if (!access) { + console.error('Access token is missing or invalid'); + throw new Error('Access token is missing or invalid'); + } + + const authAxios = getAuthAxios(access); + + // problemType을 헤더에 포함하여 요청을 보냅니다. + const response = await authAxios.get('/api/mypage/posts', { + headers: { + problemType: problemType, + }, + params: { + page: pageParam, + }, + }); + + console.log(response.data); + return response.data; +}; diff --git a/src/apis/userapi.ts b/src/apis/userapi.ts index feed6de..b9f1367 100644 --- a/src/apis/userapi.ts +++ b/src/apis/userapi.ts @@ -36,7 +36,21 @@ export const postSignup = async ({ }; export const logout = () => { - // 로컬 스토리지에서 토큰 제거 localStorage.removeItem('access'); window.location.href = '/login'; }; + +/** 유저정보 수정 */ +export const putUser = async () => { + const access = localStorage.getItem('access'); + + if (!access) { + console.error('Access token is missing or invalid'); + throw new Error('Access token is missing or invalid'); + } + + const authAxios = getAuthAxios(access); + const response = await authAxios.put('/api/mypage/users'); + console.log(response.data); + return response.data; +}; diff --git a/src/components/loginForm/index.tsx b/src/components/loginForm/index.tsx index f471be5..2c855d0 100644 --- a/src/components/loginForm/index.tsx +++ b/src/components/loginForm/index.tsx @@ -83,7 +83,9 @@ export default function LoginForm() { 회원가입

- 로그인 + + 로그인 + 간편하게 로그인하세요! diff --git a/src/components/loginForm/styles.ts b/src/components/loginForm/styles.ts index f33741b..87625cf 100644 --- a/src/components/loginForm/styles.ts +++ b/src/components/loginForm/styles.ts @@ -35,19 +35,19 @@ export const Blank = styled.div` export const Signup = styled.div` display: flex; - flex-direction: row; justify-content: center; + gap: 25px; margin: 30px 0; color: ${colors.gray300}; p { - margin-left: 25px; color: ${colors.yellow}; } `; export const Social = styled.div` ${layoutMap.flexCenter} + gap: 25px; margin: 20px 0; width: 70%; color: ${colors.gray300}; @@ -60,11 +60,12 @@ export const Social = styled.div` export const KakaoButton = styled.button` ${layoutMap.styledButton} background: ${colors.kakao}; - padding: 16px; - margin: 25px 0; + border: 1px solid ${colors.kakao}; + width: 100%; `; export const GoogleButton = styled.button` ${layoutMap.styledButton} border: 1px solid ${colors.gray}; + width: 100%; `; diff --git a/src/components/mypage/tabMenu/styles.ts b/src/components/mypage/tabMenu/styles.ts index d677722..2a146b5 100644 --- a/src/components/mypage/tabMenu/styles.ts +++ b/src/components/mypage/tabMenu/styles.ts @@ -1,5 +1,5 @@ -//import { layoutMap } from '@/styles/layout'; import { colors } from '@/styles/colorPalette'; +import { typographyMap } from '@/styles/typography'; import styled from '@emotion/styled'; export const Container = styled.div` @@ -11,9 +11,11 @@ export const TabMenu = styled.ul` display: flex; justify-content: space-around; border-bottom: 1px solid ${colors.gray}; + ${typographyMap.t2} `; export const TabBox = styled.li<{ active: boolean }>` - padding: 0 90px 20px; - border-bottom: ${({ active }) => (active ? `5px solid ${colors.yellow}` : 'none')}; + padding: 0 80px 16px; + border-bottom: ${({ active }) => (active ? `8px solid ${colors.yellow}` : 'none')}; + color: ${({ active }) => (active ? `${colors.yellowActive}` : 'black')}; `; diff --git a/src/components/mypage/userInfo/index.tsx b/src/components/mypage/userInfo/index.tsx index 2b1f053..a50bf00 100644 --- a/src/components/mypage/userInfo/index.tsx +++ b/src/components/mypage/userInfo/index.tsx @@ -1,23 +1,25 @@ -import { UserCircle } from '@/components/shared/index.ts'; +import { Icon, UserCircle } from '@/components/shared/index.ts'; import * as S from './styles.ts'; import { useEffect, useState } from 'react'; -import { Link } from 'react-router-dom'; -import { getMyPage } from '@/apis/mypage.ts'; +import { useNavigate } from 'react-router-dom'; +import { getUserInfo } from '@/apis/mypage.ts'; import { User } from '@/types/auth.ts'; +import { IconValues } from '@/components/shared/icon/index.tsx'; const UserInfo = () => { + const navigate = useNavigate(); const [userData, setUserData] = useState({ - email: '', - nickname: '', - github: '', - blog: '', - introduce: '', + email: 'email.com', + nickname: 'Soo', + github: 'github', + blog: 'blog', + introduce: 'ss', profileImage: '', }); useEffect(() => { const fetchUserData = async () => { - const data = await getMyPage(); + const data = await getUserInfo(); if (data) { setUserData({ email: data.email, @@ -27,23 +29,41 @@ const UserInfo = () => { introduce: data.introduce, profileImage: data.profileImageResDto?.fileUrl, }); + } else { + navigate('/login'); } }; fetchUserData(); }, []); + const contactInfo = [ + { key: 'email', value: userData.email, icon: 'email' }, + { key: 'github', value: userData.github, icon: 'github' }, + { key: 'blog', value: userData.blog, icon: 'blog' }, + ]; + return ( - - 편집 - + navigate('profile')}> + + {userData.nickname} - {userData.email} + + {contactInfo.map( + (info) => + info.value && ( + + + {info.value} + + ), + )} + {userData.introduce} diff --git a/src/components/mypage/userInfo/styles.ts b/src/components/mypage/userInfo/styles.ts index 322ea8b..46c5f57 100644 --- a/src/components/mypage/userInfo/styles.ts +++ b/src/components/mypage/userInfo/styles.ts @@ -1,12 +1,13 @@ import { colors } from '@/styles/colorPalette'; +import { typographyMap } from '@/styles/typography'; import styled from '@emotion/styled'; export const Container = styled.div` position: relative; display: flex; - padding: 50px 80px; + padding: 50px 150px; width: 100%; - min-height: 320px; + min-height: 300px; `; export const LeftBox = styled.div` @@ -25,27 +26,36 @@ export const ProfileImage = styled.img` `; export const UserName = styled.div` - font-size: 36px; - font-weight: 700; + ${typographyMap.t1} + ${typographyMap.bold} +`; + +export const Contact = styled.div` + display: flex; + align-items: center; + margin-top: 12px; + gap: 20px; +`; + +export const Block = styled.div` + display: flex; + align-items: center; + gap: 8px; `; export const UserEmail = styled.div` - font-size: 20px; - color: ${colors.gray200}; + ${typographyMap.t3} + color: ${colors.gray}; `; export const UserIntro = styled.div` - margin-top: 50px; - font-size: 25px; + margin-top: 28px; + ${typographyMap.t2} color: ${colors.gray300}; `; export const Edit = styled.button` + display: relative; position: absolute; - right: 60px; - padding: 5px 25px; - background-color: ${colors.gray100}; - border-radius: 30px; - font-size: 20px; - font-weight: 700; + right: 150px; `; diff --git a/src/components/nav/Nav.tsx b/src/components/nav/Nav.tsx index 247ccbc..c246ddb 100644 --- a/src/components/nav/Nav.tsx +++ b/src/components/nav/Nav.tsx @@ -1,30 +1,30 @@ -import { navItems } from "./navItems"; -import { Icon } from "../shared"; -import { Link } from "react-router-dom"; +import { navItems } from './navItems'; +import { Icon, StyledButton } from '../shared'; +import { Link } from 'react-router-dom'; import * as S from './styles'; const Nav = () => { - return ( - - - - - - - - {navItems.map(item => ( - - -

{item.label}

- - ))} -
- - Post - -
- ) -} + return ( + + + + + + + + {navItems.map((item) => ( + + +

{item.label}

+ + ))} +
+ + Post + +
+ ); +}; -export default Nav; \ No newline at end of file +export default Nav; diff --git a/src/components/nav/styles.ts b/src/components/nav/styles.ts index 98a3128..286a667 100644 --- a/src/components/nav/styles.ts +++ b/src/components/nav/styles.ts @@ -1,89 +1,67 @@ -import styled from "@emotion/styled"; -import { colors } from "../../styles/colorPalette"; - +import styled from '@emotion/styled'; +import { colors } from '../../styles/colorPalette'; +import { typographyMap } from '@/styles/typography'; export const NavContainer = styled.div` - position : sticky; - top : 0; - left : 0; - width : 400px; - height : 100%; - padding : 50px 50px; - box-sizing : border-box; - display : flex; - flex-direction : column; - align-items : center; - justify-content : flex-start; - gap : 50px; + position: sticky; + top: 0; + left: 0; + width: 400px; + height: 100%; + padding: 50px 50px; + box-sizing: border-box; + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-start; + gap: 50px; + ${typographyMap.t1} `; export const NavLogoBox = styled.div` - width : 100%; - height : 40px; - display : flex; - align-items : center; - justify-content : center; + width: 100%; + height: 40px; + display: flex; + align-items: center; + justify-content: center; - img { - width : 100%; - height : 100%; - object-fit : contain; - } + img { + width: 100%; + height: 100%; + object-fit: contain; + } `; export const NavItemBox = styled.div` - width : 100%; - height : auto; - display : flex; - flex-direction : column; - align-items : center; - justify-content : center; - gap : 30px; - background-color : white; - - a { - text-decoration : none; - width : 100%; - height : 50px; - padding : 0 0 0 30px; - box-sizing : border-box; - display : flex; - align-items : center; - justify-content : flex-start; - gap : 20px; - transition : all 0.1s ease; - - p { - font-size : 36px; - font-weight : 700; - color : black; - } + width: 100%; + height: auto; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 30px; + background-color: white; - &:hover { - cursor : pointer; - background-color : ${colors.gray50}; - } - } -`; + a { + text-decoration: none; + width: 100%; + height: 50px; + padding: 0 0 0 30px; + box-sizing: border-box; + display: flex; + align-items: center; + justify-content: flex-start; + gap: 20px; + transition: all 0.1s ease; -export const NavPostBtn = styled.button` - width : 100%; - height : 60px; - display : flex; - align-items : center; - justify-content : center; - background-color : ${colors.yellow}; - border : none; - border-radius : 999px; - - a { - color : white; - font-size : 36px; - font-weight : bold; + p { + font-weight: 700; + color: black; } &:hover { - cursor: pointer; - background-color : ${colors.yellowHover}; + cursor: pointer; + background-color: ${colors.gray50}; } -`; \ No newline at end of file + } +`; diff --git a/src/components/shared/StyledButton.ts b/src/components/shared/StyledButton.ts index 2f925f9..af7b3c2 100644 --- a/src/components/shared/StyledButton.ts +++ b/src/components/shared/StyledButton.ts @@ -1,23 +1,26 @@ import styled from '@emotion/styled'; import { Colors, colors } from '@/styles/colorPalette'; +import { layoutMap } from '@/styles/layout'; +import { Typograph, typographyMap } from '@/styles/typography'; interface ButtonProp { width?: string; background?: string; color?: Colors; + typography?: Typograph; + hover?: Colors; } const StyledButton = styled.button` - padding: 15px; - width: ${({ width }) => width}; - border-radius: 2em; - display: flex; - justify-content: center; - align-items: center; + ${layoutMap.styledButton} + width: ${({ width = '300px' }) => width}; background: ${({ background = colors.yellow }) => background}; color: ${({ color = 'white' }) => color}; - font-size: 20px; - font-weight: 700; + ${({ typography = 't1' }) => typographyMap[typography]} + + &:hover { + background-color: ${({ hover = colors.yellowHover }) => hover}; + } `; export default StyledButton; diff --git a/src/components/shared/icon/index.tsx b/src/components/shared/icon/index.tsx index 775ac74..12c2a58 100644 --- a/src/components/shared/icon/index.tsx +++ b/src/components/shared/icon/index.tsx @@ -1,10 +1,10 @@ import * as S from './styles'; -export type IconValues = - | 'logo' - | 'plus' - | 'arrow' - | 'cancel' +export type IconValues = + | 'logo' + | 'plus' + | 'arrow' + | 'cancel' | 'trophy' | 'file' | 'home' @@ -18,7 +18,11 @@ export type IconValues = | 'yeslike' | 'kakao' | 'google' - | 'question'; + | 'question' + | 'email' + | 'github' + | 'blog' + | 'settings'; interface IconProps { value: IconValues; @@ -75,259 +79,355 @@ const renderIcon = (value: IconValues) => { /> ); - case 'trophy': - return ( - - - - - - - - - - - - - - ); - case 'file': - return ( - + case 'trophy': + return ( + + - - ); - case 'home': - return ( - - - ); - case 'profile': - return ( - - - - ); - case 'threedot': - return ( - + + + + + + + ); + case 'file': + return ( + + + + + ); + case 'home': + return ( + + + + ); + case 'profile': + return ( + + + + + ); + case 'threedot': + return ( + + + + ); + case 'check': + return ( + + + + ); + case 'comment': + return ( + + + + ); + case 'nolike': + return ( + + + + ); + case 'yeslike': + return ( + + + + ); + case 'search': + return ( + + + + ); + case 'user': + return ( + + + + ); + case 'kakao': + return ( + + + + ); + case 'google': + return ( + + + + + + + ); + case 'question': + return ( + + + + ); + case 'email': + return ( + + - + + + + + + + + + - - ); - case 'check': - return ( - + + + ); + case 'github': + return ( + + - - ); - case 'comment': - return ( - - - - ); - case 'nolike': - return ( - - - - ); - case 'yeslike': - return ( - - - - ); - case 'search': - return ( - - - - ); - case 'user': - return ( - - - - ); - case 'kakao': - return ( - - - - ); - case 'google': - return ( - - - - - - - ); - case 'question': - return ( - + + + + + + ); + case 'blog': + return ( + + + - - ); + + + ); + case 'settings': + return ( + + + + + ); } }; diff --git a/src/hooks/useInfinity.ts b/src/hooks/useInfinity.ts new file mode 100644 index 0000000..c3a9f7c --- /dev/null +++ b/src/hooks/useInfinity.ts @@ -0,0 +1,19 @@ +import { useInfiniteQuery } from '@tanstack/react-query'; + +export async function fetchInfiniteData(api: string, pageParam = 1) { + const limit = 10; // 한 번에 불러올 데이터 수 + const response = await fetch(`${api}?page=${pageParam}&limit=${limit}`); + const data = await response.json(); + // 다음 페이지 계산: 'done'이 false면 다음 페이지 번호, true면 undefined + const nextPage = data.pageInfo.done ? undefined : pageParam + 1; + return { data, nextPage }; +} + +export const useGetInfiniteData = (key: string, api: string) => { + return useInfiniteQuery({ + queryKey: [key], + queryFn: ({ pageParam = 1 }) => fetchInfiniteData(api, pageParam), + initialPageParam: 1, + getNextPageParam: (lastPage) => lastPage.nextPage, + }); +}; diff --git a/src/pages/mypage/index.tsx b/src/pages/mypage/index.tsx index 7963ac3..941d360 100644 --- a/src/pages/mypage/index.tsx +++ b/src/pages/mypage/index.tsx @@ -1,12 +1,18 @@ import { UserInfo, TabMenu } from '@/components/mypage'; -import { Container } from './styles'; +import * as S from './styles'; +import { MainContainer } from '../main/styles'; +import Nav from '@/components/nav/Nav'; const MyPage = () => { return ( - - - - + +