diff --git a/index.html b/index.html
index 6d4c8f3..b41f49f 100644
--- a/index.html
+++ b/index.html
@@ -2,7 +2,7 @@
-
+
ThinkTank
diff --git a/package.json b/package.json
index 9fbfe3d..57378d2 100644
--- a/package.json
+++ b/package.json
@@ -4,7 +4,7 @@
"version": "0.0.0",
"type": "module",
"scripts": {
- "dev": "vite",
+ "dev": "vite --host",
"build": "tsc && vite build",
"preview": "vite preview",
"format": "prettier --check ./src",
diff --git a/src/App.tsx b/src/App.tsx
index b2f172e..1431cda 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,31 +1,28 @@
import { Route, Routes, useNavigate } from 'react-router-dom';
import { routers } from './routes';
import Layout from './routes/Layout';
-import { Suspense } from 'react';
-import { ErrorBoundary } from './components/shared';
import ProtectedRoute from './routes/protectedRoute';
+import { ErrorBoundary } from './components/shared';
function App() {
const navigate = useNavigate();
return (
- loading>}>
-
- }>
- {routers.map(({ path, element: Component, isProtected }) =>
- isProtected ? (
- } />}
- />
- ) : (
- } />
- ),
- )}
-
-
-
+
+ }>
+ {routers.map(({ path, element: Component, isProtected }) =>
+ isProtected ? (
+ } />}
+ />
+ ) : (
+ } />
+ ),
+ )}
+
+
);
}
diff --git a/src/apis/article.ts b/src/apis/article.ts
index 1856c0a..dff19fc 100644
--- a/src/apis/article.ts
+++ b/src/apis/article.ts
@@ -1,6 +1,5 @@
import { ArticleDetail, ArticleList } from '@/types';
import instance from './instance';
-
export const getArticle = async (postId: string) => {
const response = await instance.get(`/api/posts/${postId}`);
return response.data as ArticleDetail;
@@ -31,15 +30,20 @@ export const postArticle = async (
return response?.data;
};
-export const postCheck = async (
- formData: Pick,
- postId: string,
-) => {
- const newFormData = {
- code: formData.code,
- language: formData.language,
- postId,
- };
- const response = await instance.post('api/posts/submit', newFormData);
+interface postCheckState {
+ code: string;
+ language: string;
+ postId: string;
+}
+
+export const postCheck = async (formData: postCheckState) => {
+ const response = await instance.post('api/posts/submit', formData);
+ return response.data;
+};
+
+export const deleteArticle = async (postId: number): Promise => {
+ const response = await instance.delete('/api/posts', {
+ data: { postId: postId },
+ });
return response.data;
};
diff --git a/src/apis/auth.ts b/src/apis/auth.ts
index 30dfe57..1b62f36 100644
--- a/src/apis/auth.ts
+++ b/src/apis/auth.ts
@@ -1,19 +1,8 @@
-
-
import { getAccess } from '@/hooks/auth/useLocalStorage';
import instance from './instance';
export const getNewToken = async () => {
const expiredToken = getAccess();
-
- try {
- const response = await instance.post('/api/reissue', { expiredToken: expiredToken });
- const newAccessToken = response.data.accessToken;
- console.log('토큰 재발급 성공', newAccessToken);
- return newAccessToken;
- } catch (error) {
- console.error('토큰 재발급 실패', error);
- throw error;
- }
+ const response = await instance.post('/api/reissue', { expiredToken: expiredToken });
+ return response.data.accessToken;
};
-
diff --git a/src/apis/comment.ts b/src/apis/comment.ts
index a6b9df3..1822ca2 100644
--- a/src/apis/comment.ts
+++ b/src/apis/comment.ts
@@ -3,10 +3,17 @@ import instance from './instance';
export const getComments = async (postId: string) => {
const response = await instance.get(`api/posts/${postId}/comments`);
- return response.data as CommentDate[];
+ return response.data as CommentDate;
};
export const postComment = async (comment: string, postId: string) => {
const response = await instance.post('/api/comments', { postId, content: comment });
return response.data;
};
+
+export const deleteComment = async (postId: string, commentId: number) => {
+ const response = await instance.delete('/api/comments', {
+ data: { postId, commentId },
+ });
+ return response.data;
+};
diff --git a/src/apis/instance.ts b/src/apis/instance.ts
index d577abc..72ba1f0 100644
--- a/src/apis/instance.ts
+++ b/src/apis/instance.ts
@@ -2,7 +2,6 @@ import axios from 'axios';
import { getNewToken } from './auth';
import { getAccess, setAccess } from '@/hooks/auth/useLocalStorage';
-
const instance = axios.create({
baseURL: import.meta.env.VITE_BASE_URL,
headers: {
@@ -24,23 +23,16 @@ instance.interceptors.response.use(
const originalRequest = error.config;
const access = getAccess();
if (error.response?.status === 401 && !originalRequest._alreadyRefreshed && access) {
-
originalRequest._alreadyRefreshed = true; // 무한 요청 방지
- try {
- const newAccessToken = await getNewToken();
- if (newAccessToken) {
- setAccess(newAccessToken);
- originalRequest.headers['Authorization'] = `Bearer ${newAccessToken}`;
- return instance(originalRequest);
- }
- } catch (refreshError) {
- console.error('토큰 발급 실패', refreshError);
- // window.location.href = '/login';
- throw refreshError;
+ const newAccessToken = await getNewToken();
+ if (newAccessToken) {
+ setAccess(newAccessToken);
+ originalRequest.headers['Authorization'] = `Bearer ${newAccessToken}`;
+ return instance(originalRequest);
}
}
throw error;
},
);
-export default instance;
\ No newline at end of file
+export default instance;
diff --git a/src/apis/mypage.ts b/src/apis/mypage.ts
index 2ffcb33..004e822 100644
--- a/src/apis/mypage.ts
+++ b/src/apis/mypage.ts
@@ -1,7 +1,7 @@
import { MypageArticles } from '@/types/mypage';
import instance from './instance';
-/** MYPAGE 게시글 조회 */
+/* MYPAGE 게시글 조회 */
export const getMypageArticles = async ({ page, size, value, email }: MypageArticles) => {
try {
const response = await instance.get('/api/users/profile', {
@@ -21,7 +21,7 @@ export const getMypageArticles = async ({ page, size, value, email }: MypageArti
}
};
-/** 타인 프로필 조회 */
+/* 타인 프로필 조회 */
export const getOthersProfile = async (email: string | null) => {
try {
const response = await instance.get('/api/users/profile', {
diff --git a/src/apis/user.ts b/src/apis/user.ts
index c6c2b56..c2e7de6 100644
--- a/src/apis/user.ts
+++ b/src/apis/user.ts
@@ -1,20 +1,32 @@
import { Login, SignUp, User } from '@/types';
import instance from './instance';
+import { AxiosError } from 'axios';
-/** 로그인 **/
-export const postLogin = async ({ email, password }: Login) => {
- const data = { email, password };
- const response = await instance.post('/api/login', data);
- return response.data;
-};
-
-/** 회원가입 */
+/* 회원가입 */
export const postSignup = async (data: SignUp) => {
const response = await instance.post('/api/signup', data);
return response?.data;
};
-/** User 정보 불러오기 */
+/* 로그인 */
+export const postLogin = async (data: Login) => {
+ try {
+ const response = await instance.post('/api/login', data);
+ return response.data;
+ } catch (error) {
+ if (error instanceof AxiosError) {
+ throw error;
+ }
+ }
+};
+
+/* 카카오 로그인 */
+export const getKakaoLogin = async () => {
+ const response = await instance.get('/api/login/oauth2/kakao');
+ return response.data;
+};
+
+/* User 정보 불러오기 */
export const getUserInfo = async () => {
try {
const response = await instance.get('/api/mypage/users');
@@ -24,8 +36,14 @@ export const getUserInfo = async () => {
}
};
-/** User 정보 수정 */
+/* User 정보 수정 */
export const putUser = async (data: Omit) => {
const response = await instance.put('/api/mypage/users', data);
return response.data;
};
+
+/* 탈퇴 */
+export const deleteUser = async () => {
+ const response = await instance.delete('/api/mypage/users');
+ return response.data;
+};
diff --git a/src/assets/images/TT.svg b/src/assets/images/TT.svg
new file mode 100644
index 0000000..8859fba
--- /dev/null
+++ b/src/assets/images/TT.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/components/detail/commentItem/index.tsx b/src/components/detail/commentItem/index.tsx
index e07aa6e..0f44eaa 100644
--- a/src/components/detail/commentItem/index.tsx
+++ b/src/components/detail/commentItem/index.tsx
@@ -1,24 +1,67 @@
-import { Icon, Text } from '@/components/shared';
+import { Text } from '@/components/shared';
import { Comment } from '@/types/comment';
import getTimeDifference from '@/utils/getTimeDifference';
-import { ForwardedRef, forwardRef } from 'react';
+import { ForwardedRef, forwardRef, useState } from 'react';
+import UserCircle from '@/components/shared/UserCircle';
import * as S from './styles';
-import UserCircle from '@/components/shared/UserCircle';
+import { animationMap } from '@/styles/framerMotion';
+import { AnimatePresence } from 'framer-motion';
+import { deleteComment } from '@/apis/comment';
+import { postIdStore } from '@/stores/post';
+import { useQueryClient } from '@tanstack/react-query';
const CommentItem = forwardRef(
- ({ content, createdAt, user }: Comment, ref: ForwardedRef) => {
+ (
+ { content, createdAt, user, commentId }: Comment,
+ ref: ForwardedRef,
+ ) => {
+ const [isExpand, setIsExpand] = useState(false);
+ const postId = postIdStore((state) => state.postId);
+ const queryClient = useQueryClient();
return (
- {user.nickname}
-
+
+ {user.nickname}
+
+
{getTimeDifference(createdAt)}
- {content}
-
+
+ {content}
+
+ setIsExpand((prev) => !prev)}
+ />
+
+ {isExpand && (
+
+
+ deleteComment(postId, commentId).then(() => {
+ queryClient.invalidateQueries({ queryKey: ['comments', postId] });
+ setIsExpand(false);
+ })
+ }
+ >
+ 삭제
+
+
+ )}
+
);
},
diff --git a/src/components/detail/commentItem/styles.ts b/src/components/detail/commentItem/styles.ts
index 4628303..cb86984 100644
--- a/src/components/detail/commentItem/styles.ts
+++ b/src/components/detail/commentItem/styles.ts
@@ -1,11 +1,15 @@
+import { Icon } from '@/components/shared';
import { layoutMap } from '@/styles/layout';
import { css } from '@emotion/react';
import styled from '@emotion/styled';
+import { motion } from 'framer-motion';
export const Container = styled.div`
${layoutMap.shadowBox}
flex-direction: row;
gap: 30px;
+ align-items: center;
+ position: relative;
`;
export const UserBox = styled.div`
@@ -23,9 +27,17 @@ export const contentCss = css`
flex: 1;
`;
-export const threedotCss = css`
+export const ThreedotIcon = styled(Icon)`
${layoutMap.shadowBox}
margin-left: auto;
border-radius: 100%;
- padding: 10px;
+ padding: 5px;
+`;
+
+export const Test = styled(motion.div)`
+ ${layoutMap.shadowBox}
+ transform: translateX(-100%);
+ position: absolute;
+ right: 0;
+ padding: 20px;
`;
diff --git a/src/components/detail/commentList/index.tsx b/src/components/detail/commentList/index.tsx
index a899b2a..40184eb 100644
--- a/src/components/detail/commentList/index.tsx
+++ b/src/components/detail/commentList/index.tsx
@@ -9,9 +9,9 @@ const CommentList = () => {
const { data, ref } = useGetComments(postId);
return (
- {data?.pages.comments.map((comment, index) => {
- const commentLength = data.pages.comments.length;
- if (commentLength - 2 === index && !data.pages.pageInfo.isDone) {
+ {data?.pages.map((comment, index) => {
+ const commentLength = data.pages.length;
+ if (commentLength - 2 === index && !data.pageParams.isDone) {
return ;
} else {
return ;
diff --git a/src/components/loader/skeleton/index.tsx b/src/components/loader/skeleton/index.tsx
index ed2333d..160defc 100644
--- a/src/components/loader/skeleton/index.tsx
+++ b/src/components/loader/skeleton/index.tsx
@@ -6,20 +6,24 @@ import * as S from './styles';
const SkeletonBox = () => {
return (
<>
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
>
- )
-}
+ );
+};
-export default SkeletonBox;
\ No newline at end of file
+export default SkeletonBox;
diff --git a/src/components/loader/skeleton/styles.ts b/src/components/loader/skeleton/styles.ts
index c99b154..485a852 100644
--- a/src/components/loader/skeleton/styles.ts
+++ b/src/components/loader/skeleton/styles.ts
@@ -1,48 +1,46 @@
-import { colors } from "@/styles/colorPalette";
-import styled from "@emotion/styled";
+import { colors } from '@/styles/colorPalette';
+import styled from '@emotion/styled';
export const UserBox = styled.div`
- width : 100%;
- height : 50px;
- display : flex;
- align-items : center;
- justify-content : flex-start;
- gap : 20px;
- margin: 50px 0 0 0;
+ width: 600px;
+ display: flex;
+ align-items: center;
+ justify-content: flex-start;
+ gap: 20px;
+ margin: 50px 0 0 0;
`;
export const AvatarBlock = styled.div`
- display : flex;
- align-items : center;
- justify-content : center;
- border-radius : 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 50%;
- &.avatar {
- border : none;
- outline : none;
- }
+ &.avatar {
+ border: none;
+ outline: none;
+ }
`;
export const InfoBlock = styled.div`
- width : 100%;
- height : 100px;
- display : flex;
- flex-direction : column;
- justify-content : space-between;
+ height: 100px;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
- h3 {
- font-size : 20px;
- font-weight : 500;
- color : black;
- }
+ h3 {
+ font-size: 20px;
+ font-weight: 500;
+ color: black;
+ }
- p {
- font-size : 16px;
- font-weight : 500;
- color : ${colors.gray200};
- }
+ p {
+ font-size: 16px;
+ font-weight: 500;
+ color: ${colors.gray200};
+ }
`;
export const ContentBox = styled.div`
- margin : 50px 0 0 0;
-`;
\ No newline at end of file
+ margin: 50px 0 0 0;
+`;
diff --git a/src/components/loginForm/index.tsx b/src/components/loginForm/index.tsx
index 47e73fc..3acdc23 100644
--- a/src/components/loginForm/index.tsx
+++ b/src/components/loginForm/index.tsx
@@ -6,6 +6,7 @@ import loginImage from '@/assets/images/loginImage.jpg';
import { postLogin } from '@/apis/user';
import { Login } from '@/types/auth';
import { setAccess } from '@/hooks/auth/useLocalStorage';
+import { AxiosError } from 'axios';
export default function LoginForm() {
const navigate = useNavigate();
@@ -23,16 +24,39 @@ export default function LoginForm() {
const response = await postLogin(data);
const accessToken = response.accessToken;
setAccess(accessToken);
- console.log('로그인 성공:', response);
navigate(-1);
} catch (error) {
- setError('email', {
- type: 'manual',
- message: '이메일이 존재하지 않거나 비밀번호가 일치하지 않습니다.',
- });
+ if (error instanceof AxiosError && error.response) {
+ const status = error.response.status;
+ if (status === 404) {
+ setError('email', {
+ type: 'manual',
+ message: '요청하신 회원을 찾을 수 없습니다.',
+ });
+ } else if (status === 400) {
+ setError('password', {
+ type: 'manual',
+ message: '입력하신 비밀번호가 정확하지 않습니다.',
+ });
+ }
+ }
+ throw error;
}
};
+ // const kakaoLogin = async () => {
+ // window.open(import.meta.env.VITE_KAKAO_URL);
+ // try {
+ // const response = await getKakaoLogin();
+ // const accessToken = response.accessToken;
+ // setAccess(accessToken);
+ // console.log('로그인 성공:', response);
+ // navigate(-1);
+ // } catch (error) {
+ // console.log(error);
+ // }
+ // };
+
return (
@@ -44,10 +68,6 @@ export default function LoginForm() {
,.?/-]).{6,20}$/,
- message: '6~20자의 영문, 숫자, 특수문자를 모두 포함해주세요.',
- },
})}
label="비밀번호"
type="password"
@@ -74,13 +89,13 @@ export default function LoginForm() {
로그인
-
+ {/*
간편하게 로그인하세요!
-
+
카카오 로그인
-
+ */}
);
diff --git a/src/components/main/MainListItem/index.tsx b/src/components/main/MainListItem/index.tsx
index d7d5b22..f268252 100644
--- a/src/components/main/MainListItem/index.tsx
+++ b/src/components/main/MainListItem/index.tsx
@@ -3,6 +3,8 @@ import * as S from './styles';
import Article from '@/components/shared/article';
import { ArticleType } from '@/types';
import getTimeDifference from '@/utils/getTimeDifference';
+import { Link } from 'react-router-dom';
+import { useEffect, useState } from 'react';
interface ListItemProps {
listItem: ArticleType;
@@ -12,6 +14,27 @@ const MainListItem = ({ listItem }: ListItemProps) => {
// 구조 분해 할당으로 author과 그 외 나머지 정보들로 분리
const { user, ...articleDetails } = listItem;
const createDate = getTimeDifference(listItem.createdAt);
+ const [iconSize, setIconSize] = useState(105);
+
+ useEffect(() => {
+ console.log(window.innerWidth);
+ const handleResize = () => {
+ if (window.innerWidth <= 900) {
+ setIconSize(70);
+ } else {
+ setIconSize(105);
+ }
+ };
+
+ window.addEventListener('resize', handleResize);
+
+ // 초기 크기 설정
+ handleResize();
+
+ return () => {
+ window.removeEventListener('resize', handleResize);
+ };
+ }, []);
return (
@@ -20,15 +43,21 @@ const MainListItem = ({ listItem }: ListItemProps) => {
{user.profileImage ? (
) : (
-
+
)}
- {user.nickname}
- {createDate}
+
+
+ {user.nickname}
+
+
+
+ {createDate}
+
-
+
);
};
diff --git a/src/components/main/MainListItem/styles.ts b/src/components/main/MainListItem/styles.ts
index f668bb2..38c703a 100644
--- a/src/components/main/MainListItem/styles.ts
+++ b/src/components/main/MainListItem/styles.ts
@@ -1,4 +1,5 @@
import { colors } from "@/styles/colorPalette";
+import { typographyMap } from "@/styles/typography";
import styled from "@emotion/styled";
export const MltContainer = styled.div`
@@ -11,6 +12,11 @@ export const MltContainer = styled.div`
align-items : center;
justify-content : center;
gap : 30px;
+
+ @media (max-width: 570px) {
+ width : 90vw;
+ margin : 30px 0 0 0;
+ }
`;
export const MlUserBox = styled.div`
@@ -20,6 +26,7 @@ export const MlUserBox = styled.div`
align-items : center;
justify-content : flex-start;
gap : 20px;
+ margin : 15px 0 0 0;
`;
export const AvatarBlock = styled.div`
@@ -38,4 +45,21 @@ export const MlInfoBlock = styled.div`
display : flex;
flex-direction : column;
justify-content : space-between;
+
+ a {
+ &:hover{
+ text-decoration: underline;
+ }
+ }
+
+ @media (max-width : 900px) {
+ height : 80%;
+ #userP {
+ ${typographyMap.t4};
+ }
+
+ #createP {
+ ${typographyMap.t6};
+ }
+ }
`;
\ No newline at end of file
diff --git a/src/components/main/mainExtra/MainExtra.tsx b/src/components/main/mainExtra/MainExtra.tsx
index 4882eac..b2e45cb 100644
--- a/src/components/main/mainExtra/MainExtra.tsx
+++ b/src/components/main/mainExtra/MainExtra.tsx
@@ -1,22 +1,37 @@
import { Icon, Text } from '@/components/shared';
import * as S from './styles';
import { bestMakers, mostSolvedProblems } from '@/consts/mainExtraData';
+import { useModalContext } from '@/contexts/ModalContext';
const MainExtra = () => {
+ const { open } = useModalContext();
return (
-
-
+
+
+ open({
+ title: '아직 구현되지 않은 서비스입니다.',
+ description: '추후 구현 예정입니다.',
+ onButtonClick: () => {},
+ buttonLabel: '확인',
+ })
+ }
+ />
-
- Best Maker
+
+
+ Best Maker
+
{bestMakers.map((maker) => (
-
+
{maker.id}. {maker.name}
))}
@@ -24,11 +39,13 @@ const MainExtra = () => {
- Most Solved
+
+ Most Solved
+
{mostSolvedProblems.map((problem) => (
-
+
{problem.id}. {problem.title}
))}
diff --git a/src/components/main/mainExtra/styles.ts b/src/components/main/mainExtra/styles.ts
index f04a4af..52aee69 100644
--- a/src/components/main/mainExtra/styles.ts
+++ b/src/components/main/mainExtra/styles.ts
@@ -9,7 +9,7 @@ export const MeContainer = styled.div`
left : 0;
width : 380px;
height : 100%;
- padding : 30px;
+ padding : 50px 30px;
box-sizing : border-box;
display : flex;
flex-direction : column;
@@ -55,7 +55,7 @@ export const MeBox = styled.div`
width : 100%;
height : auto;
margin : 50px 0 30px 0;
- padding : 0 20px;
+ padding : 0 5px;
box-sizing : border-box;
display : flex;
flex-direction : column;
diff --git a/src/components/main/mainList/MainList.tsx b/src/components/main/mainList/MainList.tsx
index 629fa0b..abf3720 100644
--- a/src/components/main/mainList/MainList.tsx
+++ b/src/components/main/mainList/MainList.tsx
@@ -1,8 +1,8 @@
import * as S from './styles';
import { ArticleType } from '@/types/article';
import React from 'react';
-import Loading from '@/components/loader';
import useGetPosts from '@/hooks/post/useGetPosts';
+import CircleLoader from '@/components/loader/circleLoader';
import MainListItem from '../MainListItem';
const MainList = () => {
@@ -16,13 +16,16 @@ const MainList = () => {
{data?.pages.map((page, index) => (
- {page.posts.map((item: ArticleType) => (
-
- ))}
+ {page.posts
+ .slice()
+ .reverse()
+ .map((item: ArticleType) => (
+
+ ))}
))}
- {isFetchingNextPage && }
+ {isFetchingNextPage && }
);
diff --git a/src/components/main/mainList/styles.ts b/src/components/main/mainList/styles.ts
index e815dd6..08773ee 100644
--- a/src/components/main/mainList/styles.ts
+++ b/src/components/main/mainList/styles.ts
@@ -3,7 +3,7 @@ import { colors } from "../../../styles/colorPalette";
export const MlContainer = styled.div`
flex-grow : 1;
- padding : 0 50px;
+ padding : 20px 50px;
box-sizing : border-box;
display : flex;
flex-direction : column;
@@ -11,8 +11,9 @@ export const MlContainer = styled.div`
border-left : 1px solid ${colors.gray50};
border-right : 1px solid ${colors.gray50};
- @media (max-width : 1050px) {
+ @media (max-width : 1300px) {
border : none;
- width : 75vw;
+ width : 95vw;
+ overflow-x : hidden;
}
`;
\ No newline at end of file
diff --git a/src/components/mypage/articles/index.tsx b/src/components/mypage/articles/index.tsx
index 31ede4a..7bd50ec 100644
--- a/src/components/mypage/articles/index.tsx
+++ b/src/components/mypage/articles/index.tsx
@@ -47,12 +47,11 @@ const ArticlesMenu = ({ value }: Pick) => {
) : (
data?.pages.map((page) =>
- page.posts.map((post: ArticleType) => {
- console.log('포스트', post);
-
+ page.posts.map((post: ArticleType) => (
+
- ;
- }),
+
+ )),
)
)}
diff --git a/src/components/mypage/articles/styles.ts b/src/components/mypage/articles/styles.ts
index 9cc8532..c28d83b 100644
--- a/src/components/mypage/articles/styles.ts
+++ b/src/components/mypage/articles/styles.ts
@@ -4,11 +4,17 @@ import styled from '@emotion/styled';
export const Container = styled.div`
${layoutMap.flexCenter}
- gap: 20px;
+ gap: 40px;
+ margin-top: 40px;
`;
-export const Block = styled.div`
- ${layoutMap.shadowBox}
+export const Box = styled.div`
+ @media (min-width: 1050px) {
+ min-width: 950px;
+ }
+ @media (max-width: 1049px) {
+ min-width: 750px;
+ }
`;
export const Title = styled.div`
diff --git a/src/components/mypage/profileTable/index.tsx b/src/components/mypage/profileTable/index.tsx
index 7ae9fcd..eec2bfd 100644
--- a/src/components/mypage/profileTable/index.tsx
+++ b/src/components/mypage/profileTable/index.tsx
@@ -1,27 +1,62 @@
-import { useEffect } from 'react';
+import { useEffect, useRef } from 'react';
import * as S from './styles';
import { getUserInfo, putUser } from '@/apis/user';
-import { User } from '@/types/auth';
import { UserCircle } from '@/components/shared';
import { SubmitHandler, useForm } from 'react-hook-form';
-interface UserModify extends Omit {}
+import { UserModify } from '@/types';
+import { useQuery } from '@tanstack/react-query';
+import { useModalContext } from '@/contexts/ModalContext';
+import { AxiosError } from 'axios';
+
const ProfileTable = () => {
- const { register, handleSubmit, reset } = useForm();
+ const { register, handleSubmit, reset } = useForm({});
+ const initialValues = useRef(null);
+ const { open } = useModalContext();
+
+ const { data: userData } = useQuery({
+ queryKey: ['userData'],
+ queryFn: async () => {
+ return await getUserInfo();
+ },
+ staleTime: 1000 * 60 * 5,
+ gcTime: 1000 * 60 * 30,
+ });
useEffect(() => {
- const fetchUserData = async () => {
- const userData = await getUserInfo();
- reset(userData);
- };
- fetchUserData();
- }, []);
+ if (userData) {
+ initialValues.current = userData as UserModify;
+ reset(userData as UserModify);
+ }
+ }, [userData, reset]);
const onSubmit: SubmitHandler = async (data: UserModify) => {
+ if (initialValues.current?.nickname === data.nickname) {
+ data.nickname = null;
+ }
+
try {
await putUser(data);
- alert('수정 완료');
+ open({
+ title: '프로필 수정이 완료되었습니다.',
+ onButtonClick: () => {},
+ hasCancelButton: false,
+ buttonLabel: '확인',
+ });
} catch (error) {
- alert('수정 실패');
+ let errorMessage = '알 수 없는 에러가 발생했습니다. 다시 시도해주세요.';
+ if (error instanceof AxiosError && error.response) {
+ errorMessage = error.response.data.message;
+ }
+
+ open({
+ title: '프로필 수정 실패',
+ description: errorMessage,
+ onButtonClick: () => {},
+ hasCancelButton: false,
+ buttonLabel: '확인',
+ });
+
+ reset(userData as UserModify);
}
};
@@ -29,17 +64,18 @@ const ProfileTable = () => {
-
+
프로필 사진
별명
깃허브
블로그
-
+ 자기소개
+
-
+
-
+
@@ -50,14 +86,16 @@ const ProfileTable = () => {
-
+
+
+
+
적용
- reset()}>취소
+ reset(userData)}>취소
-
);
};
diff --git a/src/components/mypage/profileTable/styles.ts b/src/components/mypage/profileTable/styles.ts
index fe7308e..b34a46b 100644
--- a/src/components/mypage/profileTable/styles.ts
+++ b/src/components/mypage/profileTable/styles.ts
@@ -1,5 +1,6 @@
import { colors } from '@/styles/colorPalette';
import { layoutMap } from '@/styles/layout';
+import { typographyMap } from '@/styles/typography';
import styled from '@emotion/styled';
export const Container = styled.div`
@@ -12,22 +13,23 @@ export const Container = styled.div`
export const Table = styled.div`
display: flex;
- height: 550px;
+ height: 700px;
border-top: 1px solid ${colors.gray200};
`;
export const Thead = styled.div`
+ ${layoutMap.flexCenter}
flex: 1;
+ min-width: 150px;
background: ${colors.yellow};
- text-align: center;
- font-size: 24px;
- font-weight: 500;
+ ${typographyMap.semibold}
+ ${typographyMap.t4}
`;
export const Tbody = styled.div`
+ display: table;
flex: 4;
- font-size: 20px;
- font-weight: 400;
+ ${typographyMap.t5}
`;
export const Th = styled.div<{ height: number }>`
@@ -62,6 +64,16 @@ export const Input = styled.input`
padding: 10px 20px;
border-radius: 5px;
border: 1px solid ${colors.gray};
- font-size: 20px;
+ ${typographyMap.t5}
+ outline: none;
+`;
+
+export const TextArea = styled.textarea`
+ padding: 10px 20px;
+ border-radius: 5px;
+ border: 1px solid ${colors.gray};
+ ${typographyMap.t5}
outline: none;
+ width: 70%;
+ height: 60%;
`;
diff --git a/src/components/mypage/solved/SolvedBox.tsx b/src/components/mypage/solved/SolvedBox.tsx
deleted file mode 100644
index 3d0ea7e..0000000
--- a/src/components/mypage/solved/SolvedBox.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import { SolvedArticles } from '@/types';
-import * as S from './styles';
-
-export const SolvedBox = ({ data }: { data: SolvedArticles }) => {
- return (
-
- {data.postId}
- {data.title}
-
- );
-};
-
-export default SolvedBox;
diff --git a/src/components/mypage/solved/index.tsx b/src/components/mypage/solved/index.tsx
index f6e3459..f2f266f 100644
--- a/src/components/mypage/solved/index.tsx
+++ b/src/components/mypage/solved/index.tsx
@@ -1,10 +1,11 @@
import { useInfiniteQuery } from '@tanstack/react-query';
-import * as S from '../articles/styles';
+import * as S from './styles';
import { getMypageArticles } from '@/apis/mypage';
import { useEffect, useRef } from 'react';
import { MypageArticles, SolvedArticles } from '@/types';
import SkeletonBox from '@/components/loader/skeleton';
-import SolvedBox from './SolvedBox';
+import { Text } from '@/components/shared';
+import { CodeBox } from '@/components/post';
const SolvedMenu = ({ value }: Pick) => {
const queryEmail = new URLSearchParams(location.search).get('user');
@@ -47,9 +48,17 @@ const SolvedMenu = ({ value }: Pick) => {
) : (
data?.pages.map((page) =>
page.posts.map((post: SolvedArticles) => (
-
-
-
+
+
+ {post.postNumber}. {post.title}
+
+
+
)),
)
)}
diff --git a/src/components/mypage/solved/styles.ts b/src/components/mypage/solved/styles.ts
index d0aad1c..d6b19e7 100644
--- a/src/components/mypage/solved/styles.ts
+++ b/src/components/mypage/solved/styles.ts
@@ -1,9 +1,29 @@
+import { layoutMap } from '@/styles/layout';
+import { typographyMap } from '@/styles/typography';
import styled from '@emotion/styled';
export const Container = styled.div`
+ ${layoutMap.flexCenter}
+ gap: 40px;
+ margin-top: 40px;
+`;
+
+export const Block = styled.div`
+ ${layoutMap.shadowBox}
display: flex;
- flex-direction: column;
- justify-content: center;
- width: 100%;
- gap: 43px;
+ gap: 30px;
+ @media (min-width: 1050px) {
+ min-width: 950px;
+ }
+ @media (max-width: 1049px) {
+ min-width: 750px;
+ }
+`;
+
+export const Title = styled.div`
+ ${typographyMap.t1}
+`;
+
+export const Content = styled.div`
+ ${typographyMap.t3}
`;
diff --git a/src/components/mypage/tabMenu/index.tsx b/src/components/mypage/tabMenu/index.tsx
index 6c80321..7343e6d 100644
--- a/src/components/mypage/tabMenu/index.tsx
+++ b/src/components/mypage/tabMenu/index.tsx
@@ -1,5 +1,5 @@
import { ArticlesMenu, SolvedMenu } from '@/components/mypage';
-import * as S from './styles.ts';
+import * as S from './styles';
import { useState } from 'react';
const TabMenu = () => {
diff --git a/src/components/mypage/tabMenu/styles.ts b/src/components/mypage/tabMenu/styles.ts
index 20df9e1..c82a917 100644
--- a/src/components/mypage/tabMenu/styles.ts
+++ b/src/components/mypage/tabMenu/styles.ts
@@ -4,6 +4,7 @@ import styled from '@emotion/styled';
export const Container = styled.div`
width: 100%;
+ height: 100vh;
`;
export const TabMenu = styled.ul`
@@ -12,6 +13,14 @@ export const TabMenu = styled.ul`
justify-content: space-around;
border-bottom: 1px solid ${colors.gray};
${typographyMap.t2}
+
+ @media (max-width: 900px) {
+ ${typographyMap.t4}
+ }
+
+ @media (max-width: 570px) {
+ ${typographyMap.t4}
+ }
`;
export const TabBox = styled.li<{ active: boolean }>`
diff --git a/src/components/mypage/userInfo/index.tsx b/src/components/mypage/userInfo/index.tsx
index 3805804..6a5c55d 100644
--- a/src/components/mypage/userInfo/index.tsx
+++ b/src/components/mypage/userInfo/index.tsx
@@ -1,5 +1,4 @@
-import { Icon, UserCircle } from '@/components/shared';
-import * as S from './styles';
+import { Icon, UserCircle, Text } from '@/components/shared';
import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { User } from '@/types/auth.ts';
@@ -7,9 +6,12 @@ import { IconValues } from '@/components/shared/icon';
import { getUserInfo } from '@/apis/user';
import { getOthersProfile } from '@/apis/mypage';
+import * as S from './styles';
+
const UserInfo = () => {
const navigate = useNavigate();
const [data, setData] = useState(null);
+ const [isOwner, setIsOwner] = useState(false);
const updateUserData = (userData: User) => {
setData(userData);
@@ -33,8 +35,13 @@ const UserInfo = () => {
updateUserData(userData);
}
};
+
+ if (location.pathname.includes('mypage')) {
+ setIsOwner(true);
+ }
+
fetchUserData();
- }, [location.search, navigate]);
+ }, []);
const contactInfo = [
{ key: 'email', value: data?.email, icon: 'email' },
@@ -48,17 +55,21 @@ const UserInfo = () => {
- navigate('modify')}>
-
-
- {data?.nickname}
+ {isOwner && (
+ navigate('modify')}>
+
+
+ )}
+
+ {data?.nickname}
+
{contactInfo.map(
(info) =>
info.value && (
-
- {info.value}
+
+ {info.value}
),
)}
diff --git a/src/components/mypage/userInfo/styles.ts b/src/components/mypage/userInfo/styles.ts
index 46c5f57..cddee5e 100644
--- a/src/components/mypage/userInfo/styles.ts
+++ b/src/components/mypage/userInfo/styles.ts
@@ -12,6 +12,7 @@ export const Container = styled.div`
export const LeftBox = styled.div`
flex: 1;
+ margin-right: 30px;
`;
export const RightBox = styled.div`
@@ -25,11 +26,6 @@ export const ProfileImage = styled.img`
border-radius: 10em;
`;
-export const UserName = styled.div`
- ${typographyMap.t1}
- ${typographyMap.bold}
-`;
-
export const Contact = styled.div`
display: flex;
align-items: center;
@@ -41,17 +37,17 @@ export const Block = styled.div`
display: flex;
align-items: center;
gap: 8px;
+ ${typographyMap.t4};
`;
-export const UserEmail = styled.div`
- ${typographyMap.t3}
- color: ${colors.gray};
+export const Info = styled.div`
+ margin-bottom: 5px;
`;
export const UserIntro = styled.div`
margin-top: 28px;
${typographyMap.t2}
- color: ${colors.gray300};
+ color: ${colors.gray400};
`;
export const Edit = styled.button`
diff --git a/src/components/nav/Nav.tsx b/src/components/nav/Nav.tsx
index 3278a19..4c11675 100644
--- a/src/components/nav/Nav.tsx
+++ b/src/components/nav/Nav.tsx
@@ -3,28 +3,86 @@ import { navItems } from '@/consts/navItems';
import { Link } from 'react-router-dom';
import * as S from './styles';
+import { useEffect, useState } from 'react';
+import { ButtonSize } from '@/styles/button';
+import { useModalContext } from '@/contexts/ModalContext';
+
+const notComplete = ['Search', 'Problem', 'Setting'];
const Nav = () => {
+ const [iconSize, setIconSize] = useState(48);
+ const [buttonSize, setButtonSize] = useState('large');
+ const [buttonContent, setButtonContent] = useState('post');
+ const { open } = useModalContext();
+ useEffect(() => {
+ const handleResize = () => {
+ if (window.innerWidth <= 570) {
+ setIconSize(36);
+ setButtonSize('medium');
+ setButtonContent('+');
+ } else if (window.innerWidth <= 900) {
+ setIconSize(48);
+ setButtonSize('medium');
+ setButtonContent('+');
+ } else {
+ setIconSize(48);
+ setButtonSize('large');
+ setButtonContent('post');
+ }
+ };
+
+ window.addEventListener('resize', handleResize);
+
+ // 초기 크기 설정
+ handleResize();
+
+ return () => {
+ window.removeEventListener('resize', handleResize);
+ };
+ }, []);
+
+ const onClickNonComplete = () => {
+ open({
+ title: '아직 구현되지 않은 서비스입니다.',
+ description: '추후 구현 예정입니다.',
+ onButtonClick: () => {},
+ buttonLabel: '확인',
+ });
+ };
+
return (
-
-
-
+
- {navItems.map((item) => (
-
-
-
- {item.label}
-
-
- ))}
+ {navItems.map((item) => {
+ if (notComplete.includes(item.label)) {
+ return (
+
+
+
+ {item.label}
+
+
+ );
+ } else {
+ return (
+
+
+
+ {item.label}
+
+
+ );
+ }
+ })}
-
- Post
-
+
+
+ {buttonContent}
+
+
);
};
diff --git a/src/components/nav/styles.ts b/src/components/nav/styles.ts
index 8cd9e16..207ed1f 100644
--- a/src/components/nav/styles.ts
+++ b/src/components/nav/styles.ts
@@ -8,7 +8,7 @@ export const NavContainer = styled.div`
left: 0;
width: 420px;
height: 100%;
- padding: 50px 50px;
+ padding: 0 50px;
box-sizing: border-box;
display: flex;
flex-direction: column;
@@ -17,33 +17,37 @@ export const NavContainer = styled.div`
gap: 50px;
${typographyMap.t1}
- @media (max-width : 1050px) {
+ @media (max-width : 900px) {
+ top: 60px;
+ left: 30px;
+ width: 100px;
+ padding: 0 20px;
+ gap: 30px;
+ }
+
+ @media (max-width: 570px) {
+ position: fixed;
+ top: 0;
+ left: 0;
flex-direction: row;
- padding : 20px 50px;
background-color: ${colors.white};
- border-radius : 0 0 30px 30px;
- box-shadow : 0 15px 20px rgba(0, 0, 0, 0.05);
- width : 100%;
- height : 150px;
- z-index : 999;
+ border-radius: 0 0 30px 30px;
+ box-shadow: 0 15px 20px rgba(0, 0, 0, 0.05);
+ width: 100%;
+ height: 80px;
+ z-index: 999;
}
`;
export const NavLogoBox = styled.div`
- width : 100%;
- height : 150px;
+ width: 100%;
+ height: 150px;
display: flex;
align-items: center;
justify-content: center;
- img {
- width: 100%;
- height: 100%;
- object-fit: contain;
- }
-
- @media (max-width : 1050px) {
- display : none;
+ @media (max-width: 900px) {
+ display: none;
}
`;
@@ -56,17 +60,31 @@ export const NavItemBox = styled.div`
justify-content: center;
gap: 30px;
+ .disabledItem {
+ width: 100%;
+ display: flex;
+ justify-content: flex-start;
+ align-items: center;
+ gap: 20px;
+ border-radius: 50px;
+
+ &: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;
+ border-radius: 50px;
&:hover {
cursor: pointer;
@@ -74,21 +92,40 @@ export const NavItemBox = styled.div`
}
}
- @media (max-width : 1050px) {
- flex-direction: row;
+ @media (max-width: 900px) {
+ .disabledItem {
+ p {
+ display: none;
+ }
- a {
- display : flex;
- flex-direction: column;
- width : 100%;
- height : 100%;
+ &:hover {
+ background-color: transparent;
+ }
+ }
+ a {
+ margin: 0;
p {
- ${typographyMap.t4};
+ display: none;
}
&:hover {
- background-color : transparent;
+ background-color: transparent;
+ }
+ }
+ }
+
+ @media (max-width: 570px) {
+ flex-direction: row;
+
+ a {
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+ height: 100%;
+
+ p {
+ ${typographyMap.t6};
}
}
}
diff --git a/src/components/post/codeBox/index.tsx b/src/components/post/codeBox/index.tsx
index b036689..367eeae 100644
--- a/src/components/post/codeBox/index.tsx
+++ b/src/components/post/codeBox/index.tsx
@@ -19,7 +19,7 @@ const CodeBox = ({ layout = true, readOnly = false, code, language }: CodeBoxPro
return (
- 언어
+ 언어
{language ? (
<>{language}>
) : (
diff --git a/src/components/post/posttestCaseBox/index.tsx b/src/components/post/posttestCaseBox/index.tsx
index 61ee581..5fc0834 100644
--- a/src/components/post/posttestCaseBox/index.tsx
+++ b/src/components/post/posttestCaseBox/index.tsx
@@ -4,16 +4,14 @@ import { postFormStore } from '@/stores/post';
import { TestCase } from '@/types';
import * as S from './styles';
+import TestCaseItem from '../tesCaseItem';
const defalutValue = [{ example: '', result: '' }];
const PostTestCaseBox = () => {
const updatePostForm = postFormStore((state) => state.updatePostForm);
const [testCases, setTestCases] = useState(defalutValue);
- useEffect(() => {
- updatePostForm({ testCases });
- }, [testCases, updatePostForm]);
- const handleChange = ({
+ const onChangeTestCase = ({
index,
event,
}: {
@@ -30,8 +28,19 @@ const PostTestCaseBox = () => {
setTestCases(updateTestCase);
updatePostForm({ testCases: updateTestCase });
};
+ const onDeleteTestCase = (index: number) => {
+ if (testCases.length > 1) {
+ const updateTestCase = testCases.filter((_, i) => i !== index);
+ setTestCases(updateTestCase);
+ updatePostForm({ testCases: updateTestCase });
+ }
+ };
+ useEffect(() => {
+ updatePostForm({ testCases });
+ }, [testCases, updatePostForm]);
+
return (
-
+
테스트 케이스
{
/>
- Example
-
- {testCases.map((testCase, index) => (
-
- Return
-
- {testCases.map((testCase, index) => (
-
+ );
+ })}
-
+
);
};
diff --git a/src/components/post/posttestCaseBox/styles.ts b/src/components/post/posttestCaseBox/styles.ts
index 3458568..8566b83 100644
--- a/src/components/post/posttestCaseBox/styles.ts
+++ b/src/components/post/posttestCaseBox/styles.ts
@@ -1,9 +1,7 @@
-import { colors } from '@/styles/colorPalette';
import { layoutMap } from '@/styles/layout';
import styled from '@emotion/styled';
-export const TestCaseContainer = styled.div`
- width: 800px;
+export const Container = styled.div`
padding: 20px;
bottom: 0;
`;
@@ -17,26 +15,14 @@ export const TitleBox = styled.div`
export const ContentBox = styled.div`
${layoutMap.shadowBox}
- display: grid;
- grid-template-columns: 2fr 1fr;
- grid-template-rows: 30px 1fr;
- grid-auto-flow: column;
+ display: flex;
min-height: 150px;
max-height: 200px;
overflow: scroll;
overflow-x: hidden;
- grid-gap: 10px;
+ gap: 10px;
`;
-
-export const TestBlock = styled.div`
+export const TestCaseTitleBlock = styled.div`
display: flex;
- flex-direction: column;
- gap: 10px;
-
- > textarea {
- resize: none;
- border: 1px solid ${colors.blue};
- border-radius: 8px;
- outline-color: ${colors.yellowActive};
- }
+ justify-content: space-between;
`;
diff --git a/src/components/post/tesCaseItem/index.tsx b/src/components/post/tesCaseItem/index.tsx
new file mode 100644
index 0000000..f525121
--- /dev/null
+++ b/src/components/post/tesCaseItem/index.tsx
@@ -0,0 +1,38 @@
+import { Icon } from '@/components/shared';
+import { TestCase } from '@/types';
+import { ChangeEvent } from 'react';
+
+import * as S from './styles';
+
+interface TestCaseItemProps {
+ testCase: TestCase;
+ onChange: ({
+ index,
+ event,
+ }: {
+ index: number;
+ event: ChangeEvent;
+ }) => void;
+ index: number;
+ onDelete: (index: number) => void;
+}
+
+const TestCaseItem = ({ testCase, onChange, index, onDelete }: TestCaseItemProps) => {
+ return (
+
+
+ );
+};
+
+export default TestCaseItem;
diff --git a/src/components/post/tesCaseItem/styles.ts b/src/components/post/tesCaseItem/styles.ts
new file mode 100644
index 0000000..646e12d
--- /dev/null
+++ b/src/components/post/tesCaseItem/styles.ts
@@ -0,0 +1,32 @@
+import { colors } from '@/styles/colorPalette';
+import styled from '@emotion/styled';
+
+export const Container = styled.div`
+ display: flex;
+ flex-direction: row;
+ gap: 10px;
+ align-items: center;
+
+ > textarea {
+ resize: none;
+ border: 1px solid ${colors.blue};
+ border-radius: 8px;
+ outline-color: ${colors.yellowActive};
+ flex: 1;
+ }
+
+ &:hover > div:last-child {
+ opacity: 1;
+ visibility: visible;
+ }
+
+ div:last-child {
+ opacity: 0;
+ visibility: hidden;
+ transition:
+ opacity 0.3s ease,
+ visibility 0.3s ease;
+ background-color: ${colors.red};
+ border-radius: 100%;
+ }
+`;
diff --git a/src/components/post/textareaField/index.tsx b/src/components/post/textareaField/index.tsx
index ad2a78c..8a095a2 100644
--- a/src/components/post/textareaField/index.tsx
+++ b/src/components/post/textareaField/index.tsx
@@ -20,7 +20,7 @@ const TextareaField = ({ label, name, value, onChange }: TextareaFieldProps) =>
};
return (
setHasFocus(false)}
hasFocus={hasFocus}
>
diff --git a/src/components/shared/ErrorBoundary.tsx b/src/components/shared/ErrorBoundary.tsx
index 23fa8d7..0d30b0a 100644
--- a/src/components/shared/ErrorBoundary.tsx
+++ b/src/components/shared/ErrorBoundary.tsx
@@ -70,6 +70,7 @@ class ErrorBoundary extends Component<
description: '로그인 후 이용 가능한 서비스입니다.',
type: 'alert',
buttonLabel: '이동',
+ hasCancelButton: true,
});
}
}
diff --git a/src/components/shared/StyledButton.ts b/src/components/shared/StyledButton.ts
index cdd4104..91b3b9e 100644
--- a/src/components/shared/StyledButton.ts
+++ b/src/components/shared/StyledButton.ts
@@ -14,6 +14,7 @@ interface ButtonProp {
size?: ButtonSize;
bold?: boolean;
hover?: Colors;
+ resize?: string;
}
const StyledButton = styled.button`
@@ -32,6 +33,10 @@ const StyledButton = styled.button`
&:active {
transform: scale(0.95);
}
+
+ @media (max-width: 900px) {
+ width: ${({ resize = '200px' }) => resize};
+ }
`;
export default StyledButton;
diff --git a/src/components/shared/Text.tsx b/src/components/shared/Text.tsx
index 787d3fa..ac32273 100644
--- a/src/components/shared/Text.tsx
+++ b/src/components/shared/Text.tsx
@@ -8,18 +8,26 @@ interface TextProps {
bold?: Typograph;
color?: Colors;
ellipsis?: number;
+ clickable?: boolean;
}
const Text = styled.p`
${({ typography = 't3' }) => typographyMap[typography]};
${({ bold = 'semibold' }) => typographyMap[bold]};
color: ${({ color = 'black' }) => colors[color]};
+ line-height: normal;
${({ ellipsis }) => css`
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
width: ${ellipsis}px;
`}
+ ${({ clickable = false }) =>
+ clickable
+ ? css`
+ cursor: pointer;
+ `
+ : null};
`;
export default Text;
diff --git a/src/components/shared/article/index.tsx b/src/components/shared/article/index.tsx
index 092cacb..3742dc5 100644
--- a/src/components/shared/article/index.tsx
+++ b/src/components/shared/article/index.tsx
@@ -4,19 +4,19 @@ import InfoStatus from '../infoStatus';
import { useNavigate } from 'react-router-dom';
import { ArticleType } from '@/types';
import { useLike } from '@/hooks/like/useLike';
-import { IconValues } from '../icon';
+import Icon, { IconValues } from '../icon';
import Text from '../Text';
+import { useModalContext } from '@/contexts/ModalContext';
+import { deleteArticle } from '@/apis/article';
-// ArticleItem 타입에서 author 제외한 타입 정의
type ArticleTypes = Omit;
interface ArticleProps {
threedot?: ReactNode;
- statusFlag: string;
+ statusFlag?: string;
article: ArticleTypes;
}
-// string에서 들여쓰기를
태그로 인식
const formatContent = (content: string, maxLines: number = Infinity): ReactNode => {
const lines = content.split('\n');
const limitedLines = lines.slice(0, maxLines);
@@ -40,15 +40,18 @@ const formatContent = (content: string, maxLines: number = Infinity): ReactNode
);
};
-// 게시글 컴포넌트 (마이페이지에서 활용 가능)
-const Article = ({ article, threedot, statusFlag }: ArticleProps) => {
+const Article = ({ article, statusFlag }: ArticleProps) => {
const navigate = useNavigate();
+ const { open } = useModalContext();
- const { likeCount, likeType, toggleLike } = useLike(article.postId, article.likeCount, article.likeType);
+ const { likeCount, likeType, toggleLike } = useLike(
+ article.postId,
+ article.likeCount,
+ article.likeType,
+ );
const iconValue: IconValues = likeType ? 'yeslike' : 'nolike';
- // statusFlag가 open인 경우(상세페이지인 경우) 문제 내용 그대로, 나머지 경우에서는 10번째 라인까지 제한
const contentNode =
statusFlag === 'open'
? formatContent(article.content, 8)
@@ -58,29 +61,45 @@ const Article = ({ article, threedot, statusFlag }: ArticleProps) => {
navigate(`/detail/${article.postId}`);
};
+ const onHandleSetting = (e: React.MouseEvent) => {
+ e.stopPropagation();
+
+ open({
+ title: '설정',
+ type: 'setting',
+ buttonLabel: '삭제',
+ onButtonClick: async () => {
+ try {
+ await deleteArticle(article.postId);
+ } catch (error) {
+ console.error(error);
+ }
+ },
+ });
+ };
+
return (
- {article.title}
- {/* 마이페이지에서는 threedot 위치 입니다. */}
- {threedot}
+
+ {article.title}
+
+ {statusFlag === 'open' && (
+ onHandleSetting(e)} />
+ )}
{contentNode}
- {statusFlag === 'open' && (
- <>
- {
- e.stopPropagation();
- toggleLike();
- }}
- />
-
-
- >
- )}
+ {
+ e.stopPropagation();
+ toggleLike();
+ }}
+ />
+
+
);
diff --git a/src/components/shared/article/styles.ts b/src/components/shared/article/styles.ts
index 2e3414c..2538100 100644
--- a/src/components/shared/article/styles.ts
+++ b/src/components/shared/article/styles.ts
@@ -34,12 +34,22 @@ export const ArticleContainer = styled.div`
cursor: pointer;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.2);
}
+
+ @media (max-width : 900px) {
+ height : 400px;
+ }
`;
export const ArTopBox = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
+
+ @media (max-width : 900px) {
+ p {
+ ${typographyMap.t3};
+ }
+ }
`;
export const ArContentBlock = styled.p`
@@ -47,6 +57,10 @@ export const ArContentBlock = styled.p`
${typographyMap.t4};
${typographyMap.regular};
color: black;
+
+ @media (max-width : 900px) {
+ ${typographyMap.t6};
+ }
`;
export const ArDataBlock = styled.div`
diff --git a/src/components/shared/authBackground/index.tsx b/src/components/shared/authBackground/index.tsx
index d204b84..68152b2 100644
--- a/src/components/shared/authBackground/index.tsx
+++ b/src/components/shared/authBackground/index.tsx
@@ -3,7 +3,7 @@ import * as S from './styles';
const AuthBackground = () => {
return (
<>
-
+
>
);
diff --git a/src/components/shared/icon/index.tsx b/src/components/shared/icon/index.tsx
index 2c19e3e..008aad2 100644
--- a/src/components/shared/icon/index.tsx
+++ b/src/components/shared/icon/index.tsx
@@ -2,6 +2,7 @@ import { MouseEvent } from 'react';
import { Colors, colors } from '@/styles/colorPalette';
import * as S from './styles';
+import { Link } from 'react-router-dom';
export type IconValues =
| 'logo'
@@ -50,12 +51,14 @@ const renderIcon = (value: IconValues, color: Colors) => {
switch (value) {
case 'logo':
return (
-
+
+
+
);
case 'plus':
@@ -69,7 +72,7 @@ const renderIcon = (value: IconValues, color: Colors) => {
);
case 'arrow':
return (
-