diff --git a/src/components/display/dashboard/admin/AddAdmin.jsx b/src/components/display/dashboard/admin/AddAdmin.jsx
index 0d042d0..ac7a3ce 100644
--- a/src/components/display/dashboard/admin/AddAdmin.jsx
+++ b/src/components/display/dashboard/admin/AddAdmin.jsx
@@ -16,7 +16,7 @@ export const AddAdmin = forwardRef(({ ...props }, ref) => {
@@ -28,3 +28,5 @@ export const AddAdmin = forwardRef(({ ...props }, ref) => {
);
});
+
+AddAdmin.displayName = 'AddAdmin';
diff --git a/src/components/layouts/DashboardLayout.jsx b/src/components/layouts/DashboardLayout.jsx
index 0730abc..5335841 100644
--- a/src/components/layouts/DashboardLayout.jsx
+++ b/src/components/layouts/DashboardLayout.jsx
@@ -12,6 +12,7 @@ import { DashboardNav } from '@components/navigation/DashboardNav';
import { Alert } from '@components/forms/modal/Alert';
import { Confirm } from '@components/forms/modal/Confirm';
import { Loading } from '@components/forms/modal/Loading';
+import NotFound from '../../pages/NotFound';
import '@/transitions/fade-slide.css';
@@ -75,16 +76,16 @@ export const DashboardLayout = ({ location }) => {
useEffect(() => {
if (isLoading) {
- showLoading({ message: '대시보드를 준비하는 중...' });
+ showLoading({ message: '페이지를 찾는 중...' });
} else {
hideLoading();
}
-
- if (isError) {
- navigate(-1);
- }
}, [isLoading, isError]);
+ if (isError) {
+ return ;
+ }
+
return (
diff --git a/src/components/navigation/AuthContext.jsx b/src/components/navigation/AuthContext.jsx
index 7e2027b..46849bf 100644
--- a/src/components/navigation/AuthContext.jsx
+++ b/src/components/navigation/AuthContext.jsx
@@ -26,19 +26,12 @@ export const AuthProvider = ({ children }) => {
setIsLoggedIn(true);
};
- const logout = async () => {
- try {
- await API.POST('/logout');
- } catch (error) {
- // console.error('Error during logout:', error);
- } finally {
- localStorage.removeItem('accessToken');
- localStorage.removeItem('refreshToken');
- localStorage.removeItem('user');
- setIsLoggedIn(false);
- setUser(null);
- window.location.href = '/';
- }
+ const logout = () => {
+ localStorage.removeItem('accessToken');
+ localStorage.removeItem('refreshToken');
+ localStorage.removeItem('user');
+ setIsLoggedIn(false);
+ setUser(null);
};
return (
diff --git a/src/components/navigation/Profile.jsx b/src/components/navigation/Profile.jsx
index f0345bb..065ed84 100644
--- a/src/components/navigation/Profile.jsx
+++ b/src/components/navigation/Profile.jsx
@@ -55,7 +55,14 @@ const Profile = ({ userName, logout }) => {
navigate('/mypage')}>
{userName} 님
-
+ {
+ logout();
+ window.location.href = '/';
+ }}
+ >
로그아웃
diff --git a/src/pages/Article.jsx b/src/pages/Article.jsx
index c0f8274..853156e 100644
--- a/src/pages/Article.jsx
+++ b/src/pages/Article.jsx
@@ -24,6 +24,8 @@ import 'prismjs/components/prism-jsx.min'; // JSX 언어 지원을 포함합니
import 'prismjs/plugins/line-numbers/prism-line-numbers.css'; // 코드 블럭에 줄 번호를 추가하기 위해 이 줄을 추가합니다
import 'prismjs/plugins/line-numbers/prism-line-numbers.min';
+import { formatDate } from '@/utils/formatDate';
+
const ArticleContainer = styled.div`
width: 100%;
margin: 0 auto;
@@ -121,7 +123,7 @@ export default function Article() {
- {post?.user?.name} | {post?.createdAt}
+ {post?.user?.name} | {formatDate(post?.created_at)}
diff --git a/src/pages/Login.jsx b/src/pages/Login.jsx
index 61fc4e0..beb5826 100644
--- a/src/pages/Login.jsx
+++ b/src/pages/Login.jsx
@@ -145,14 +145,10 @@ export default function Login() {
// console.log('response:', response);
- // get string body
- const access_token = login_res.data.access_token;
- const refresh_token = login_res.data.refresh_token;
-
// 서버에 user 정보 요청
const student_res = await API.GET(`/users/${data.student}`, {
headers: {
- Authorization: access_token,
+ Authorization: login_res.data.access_token,
},
});
@@ -161,10 +157,16 @@ export default function Login() {
// console.log('token:', { access_token, refresh_token });
// console.log('userInfo:', userInfo);
- alert('break!');
-
- if (access_token && refresh_token && userInfo) {
- login(access_token, refresh_token, userInfo); // 로그인 성공 시 AuthContext의 login 함수 호출
+ if (
+ login_res.data.access_token &&
+ login_res.data.refresh_token &&
+ userInfo
+ ) {
+ login(
+ login_res.data.access_token,
+ login_res.data.refresh_token,
+ userInfo,
+ ); // 로그인 성공 시 AuthContext의 login 함수 호출
// console.log('login user info:', userInfo); // Log user info
setError(''); // 에러 초기화
navigate('/');
@@ -173,9 +175,6 @@ export default function Login() {
setError('로그인에 실패했습니다. 다시 시도해주세요.');
}
} catch (error) {
- // 입력창 비우기
- setValue('student', '');
- setValue('password', '');
// 에러 처리
// console.error('Error:', error);
setError('로그인에 실패했습니다. 다시 시도해주세요.');
@@ -206,13 +205,9 @@ export default function Login() {
placeholder="2024000000"
{...register('student', {
required: '학번을 입력해주세요.',
- minLength: {
- value: 10,
- message: '학번은 10자리 숫자여야 합니다.',
- },
pattern: {
value: /^[0-9]{10}$/,
- message: '학번은 10자리 숫자여야 합니다.',
+ message: '올바른 학번을 적어주세요.',
},
})}
/>
@@ -229,16 +224,6 @@ export default function Login() {
placeholder="비밀번호"
{...register('password', {
required: '비밀번호를 입력해주세요',
- minLength: {
- value: 8,
- message: '비밀번호는 8자리 이상이여야 합니다. ',
- },
- pattern: {
- value:
- /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%^&*]).{8,20}$/,
- message:
- '비밀번호는 숫자, 영문 대문자·소문자, 특수문자를 포함해야 합니다.',
- },
})}
/>
{errors.password && (
@@ -258,4 +243,4 @@ export default function Login() {
);
-}
\ No newline at end of file
+}
diff --git a/src/pages/MyPage.jsx b/src/pages/MyPage.jsx
index 589e237..560c1fa 100644
--- a/src/pages/MyPage.jsx
+++ b/src/pages/MyPage.jsx
@@ -1,8 +1,8 @@
import { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { useForm } from 'react-hook-form';
+import { useQuery, useMutation } from 'react-query';
import styled from 'styled-components';
-import { useQuery, useMutation, useQueryClient } from 'react-query';
import useAlert from '@/hooks/modal/useAlert';
import { useAuth } from '@components/navigation/AuthContext';
@@ -161,12 +161,12 @@ const InputWrapper = styled.div`
export default function MyPage() {
const [userInfo, setUserInfo] = useState({
- studentNumber: '',
name: '',
email: '',
+ student_id: 0,
+ profile_picture: '',
generation: '',
major: '',
- profilePic: null,
});
const { isLoggedIn, logout, user } = useAuth();
const [imagePreview, setImagePreview] = useState(null);
@@ -186,28 +186,25 @@ export default function MyPage() {
if (!isLoggedIn) {
navigate('/login');
}
- }, [isLoggedIn, navigate]);
+ }, []);
// Fetch user data
- const { isLoading } = useQuery(
+ const { data, isLoading } = useQuery(
['userData', user?.student_id],
async () => {
const response = await API.GET(`/users/${user.student_id}`);
- if (response.status < 200 || response.status >= 300) {
- throw new Error('Failed to fetch user data');
- }
return response.data;
},
{
enabled: isLoggedIn,
onSuccess: (data) =>
setUserInfo({
- studentNumber: data.student_id,
name: data.name,
email: data.email,
+ student_id: data.student_id,
+ profile_picture: data.profile_picture || defaultProfilePic,
generation: data.generation,
major: data.major,
- profilePic: data.profile_picture || defaultProfilePic,
}),
onError: () => {
openAlert({
@@ -215,7 +212,6 @@ export default function MyPage() {
content: (
사용자 정보 불러오기에 실패했습니다. 다시 시도해주세요.
),
- onClose: closeAlert,
});
},
},
@@ -223,18 +219,69 @@ export default function MyPage() {
const imageUploadMutation = useMutation(
async (file) => {
- // 파일을 base64로 변환
+ if (file.size > 2 * 1024 * 1024) {
+ throw new Error('이미지 크기는 2MB를 초과할 수 없습니다.');
+ }
+
+ const compressImage = (file) => {
+ return new Promise((resolve, reject) => {
+ const reader = new FileReader();
+ reader.readAsDataURL(file);
+ reader.onload = (event) => {
+ const img = new Image();
+ img.src = event.target.result;
+ img.onload = () => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+ const maxWidth = 800;
+ const maxHeight = 800;
+ let width = img.width;
+ let height = img.height;
+
+ if (width > height) {
+ if (width > maxWidth) {
+ height *= maxWidth / width;
+ width = maxWidth;
+ }
+ } else {
+ if (height > maxHeight) {
+ width *= maxHeight / height;
+ height = maxHeight;
+ }
+ }
+
+ canvas.width = width;
+ canvas.height = height;
+ ctx.drawImage(img, 0, 0, width, height);
+
+ canvas.toBlob(
+ (blob) => {
+ resolve(blob);
+ },
+ 'image/jpeg',
+ 0.75, // 이미지 퀄리티 (원본 : 100%)
+ );
+ };
+ img.onerror = () =>
+ reject(new Error('올바른 형식의 이미지가 아닌 것 같아요.'));
+ };
+ reader.onerror = () =>
+ reject(new Error('이미지를 읽는 중 오류가 발생했어요.'));
+ });
+ };
+
+ const compressedFile = await compressImage(file);
+
const reader = new FileReader();
- reader.readAsDataURL(file);
+ reader.readAsDataURL(compressedFile);
return new Promise((resolve, reject) => {
reader.onloadend = async () => {
const base64String = reader.result;
- // userInfo 객체에 base64 인코딩된 이미지를 포함
const formData = {
...userInfo,
- profile_picture: base64String, // base64 데이터 전송
+ profile_picture: base64String,
};
try {
@@ -251,15 +298,57 @@ export default function MyPage() {
},
{
onSuccess: (data) => {
- setUserInfo((prev) => ({ ...prev, profilePic: data.profile_picture }));
+ setUserInfo((prev) => ({
+ ...prev,
+ profile_picture: data.profile_picture,
+ }));
setImagePreview(null);
},
- onError: () => {
+ onError: (error) => {
openAlert({
title: '이미지 업로드 실패',
- content: (
- 이미지 업로드에 실패했습니다. 다시 시도해주세요.
- ),
+ content: {error.message},
+ onClose: closeAlert,
+ });
+ },
+ },
+ );
+
+ const imageDeleteMutation = useMutation(
+ async (default_profile_path) => {
+ const image = await fetch(default_profile_path);
+ const blob = await image.blob();
+ const reader = new FileReader();
+
+ reader.readAsDataURL(blob);
+
+ const base64String = await new Promise((resolve, reject) => {
+ reader.onloadend = () => resolve(reader.result);
+ reader.onerror = () => reject(new Error('이미지 변환 실패'));
+ });
+
+ const formData = {
+ ...userInfo,
+ profile_picture: base64String,
+ };
+
+ const response = await API.PUT(`/users/${userInfo.student_id}`, {
+ body: formData,
+ });
+ return response.data;
+ },
+ {
+ onSuccess: (data) => {
+ setUserInfo((prev) => ({
+ ...prev,
+ profile_picture: data.profile_picture,
+ }));
+ setImagePreview(null);
+ },
+ onError: (error) => {
+ openAlert({
+ title: '이미지 업로드 실패',
+ content: {error.message},
onClose: closeAlert,
});
},
@@ -268,6 +357,7 @@ export default function MyPage() {
const handleImageUpload = (event) => {
const file = event.target.files[0];
+ console.log(file);
if (file) {
setImagePreview(URL.createObjectURL(file));
imageUploadMutation.mutate(file);
@@ -309,48 +399,46 @@ export default function MyPage() {
// hash를 생성하여 data 객체를 수정
const hashData = {
- currentPassword: data.currentPassword, // 기존 비밀번호 포함
- newPassword: data.newPassword, // 새 비밀번호
+ user_id: user.student_id,
+ old_password: data.currentPassword, // 기존 비밀번호 포함
+ password: data.newPassword, // 새 비밀번호
};
// 비밀번호 변경 요청
passwordChangeMutation.mutate(hashData);
};
- const handleDeleteAccount = () => {
+ const handleDeleteAccount = async () => {
const confirmDelete = window.confirm(
'계정을 삭제하면 복구할 수 없습니다. 정말로 삭제하시겠습니까?',
);
if (!confirmDelete) return;
// 계정 삭제 요청
- API.DELETE(`/users/${user.student_id}`, {
- headers: {
- Authorization: localStorage.getItem('sessionStorageToken'),
- },
- })
- .then(() => {
- openAlert({
- title: '계정 삭제 성공',
- content: 계정이 성공적으로 삭제되었습니다.,
- onClose: () => {
- logout();
- navigate('/login');
- },
- });
- })
- .catch(() => {
- openAlert({
- title: '계정 삭제 실패',
- content: 계정 삭제에 실패했습니다. 다시 시도해주세요.,
- onClose: closeAlert,
- });
+ try {
+ await API.DELETE(`/users/${user.student_id}`, {});
+ logout();
+ openAlert({
+ title: '계정 삭제됨',
+ content: 계정이 성공적으로 삭제되었습니다.,
+ onClose: () => {
+ closeAlert();
+ navigate('/');
+ },
});
+ } catch (error) {
+ openAlert({
+ title: '계정 삭제 실패',
+ content: 계정 삭제에 실패했습니다. 다시 시도해주세요.,
+ onClose: closeAlert,
+ });
+ }
};
return (
+
- {/* Account Info Section */}
+ {/* /* Account Info Section */}
-
);
diff --git a/src/pages/NewArticleEditor.jsx b/src/pages/NewArticleEditor.jsx
index 140822d..53b214d 100644
--- a/src/pages/NewArticleEditor.jsx
+++ b/src/pages/NewArticleEditor.jsx
@@ -31,6 +31,7 @@ import { Alert } from '@/components/forms/modal/Alert';
import useLoading from '@/hooks/modal/useLoading';
import { Text } from '@components/typograph/Text';
import { Loading } from '../components/forms/modal/Loading';
+import NotFound from './NotFound';
const Container = styled.div`
width: 100%;
@@ -208,12 +209,12 @@ export default function NewArticle() {
} else {
hideLoading();
}
-
- if (!adminData && !isLoading) {
- navigate('/board');
- }
}, [isLoading, adminData]);
+ if (!adminData && !isLoading) {
+ return ;
+ }
+
return (
{!isLoading && adminData && (
diff --git a/src/pages/SignUp.jsx b/src/pages/SignUp.jsx
index 965743c..eeaa2f0 100644
--- a/src/pages/SignUp.jsx
+++ b/src/pages/SignUp.jsx
@@ -164,13 +164,6 @@ export default function SignUp() {
})
.catch((error) => {
// console.error('Error:', error);
- setValue('username', '');
- setValue('student', '');
- setValue('password', '');
- setValue('mail', '');
- setValue('generation', '');
- setValue('major', '');
-
openAlert({
title: '회원가입 실패',
content: 회원가입에 실패했습니다. 다시 시도해주세요.,
@@ -266,7 +259,7 @@ export default function SignUp() {
);
-}
\ No newline at end of file
+}
diff --git a/src/utils/api.js b/src/utils/api.js
index bd57a66..05fdab9 100644
--- a/src/utils/api.js
+++ b/src/utils/api.js
@@ -44,8 +44,12 @@ api.interceptors.response.use(
refresh_token: localStorage.getItem('refreshToken'),
});
- localStorage.setItem('accessToken', data.accessToken);
- localStorage.setItem('refreshToken', data.refreshToken);
+ if (data.accessToken) {
+ localStorage.setItem('accessToken', data.accessToken);
+ }
+ if (data.refreshToken) {
+ localStorage.setItem('refreshToken', data.refreshToken);
+ }
// 실패했던 요청에 새로운 토큰 적용
originalRequest.headers.Authorization =