From 9324ac5c5374d6c2c31edbbf53d5b8d32e6cec3a Mon Sep 17 00:00:00 2001 From: seorang42 Date: Tue, 11 Feb 2025 11:59:30 +0900 Subject: [PATCH 01/17] =?UTF-8?q?:recycle:=20[refactor]=20:=20=EB=B9=84?= =?UTF-8?q?=EB=B0=80=EB=B2=88=ED=98=B8=20=EC=9E=AC=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=92=A4?= =?UTF-8?q?=EB=A1=9C=EA=B0=80=EA=B8=B0=20=EB=B2=84=ED=8A=BC=20=EB=88=84?= =?UTF-8?q?=EB=9D=BD=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/EditInformation.vue | 2 +- src/components/top-bar/NotificationModal.vue | 24 ++++++++----- src/components/top-bar/ProfileModal.vue | 4 +-- src/views/LoginView.vue | 38 ++++++++++---------- src/views/PwChangeEmail.vue | 26 ++++++++------ src/views/PwChangeView.vue | 18 ++++++---- src/views/PwCheckView.vue | 21 +++++++---- 7 files changed, 79 insertions(+), 54 deletions(-) diff --git a/src/components/common/EditInformation.vue b/src/components/common/EditInformation.vue index 93d5afeb..faaf5a71 100644 --- a/src/components/common/EditInformation.vue +++ b/src/components/common/EditInformation.vue @@ -192,7 +192,7 @@ const handlePwChange = () => { } const changePw = () => { - router.replace('/pw-check') + router.push('/pw-check') } const warningModalToggle = () => { diff --git a/src/components/top-bar/NotificationModal.vue b/src/components/top-bar/NotificationModal.vue index c6718bbe..1ca99a76 100644 --- a/src/components/top-bar/NotificationModal.vue +++ b/src/components/top-bar/NotificationModal.vue @@ -22,15 +22,20 @@
- - + + @@ -55,6 +60,7 @@ import { ref } from 'vue' import CommonIcons from '../common/CommonIcons.vue' import NotificationMessage from './NotificationMessage.vue' import { useRouter } from 'vue-router' +import NoContent from '../lists/NoContent.vue' const { isOpen } = defineProps<{ isOpen: boolean diff --git a/src/components/top-bar/ProfileModal.vue b/src/components/top-bar/ProfileModal.vue index 7066c883..3cfa2ef5 100644 --- a/src/components/top-bar/ProfileModal.vue +++ b/src/components/top-bar/ProfileModal.vue @@ -67,7 +67,7 @@ const emit = defineEmits<{ }>() const handleEdit = () => { - router.replace('/edit-information') + router.push('/edit-information') emit('close') } const openLogoutModal = () => { @@ -76,7 +76,7 @@ const openLogoutModal = () => { const closeLogoutModal = () => { isModalVisible.value = false isLogined.value = false - router.replace('/login') + router.push('/login') } const handleLogout = async () => { diff --git a/src/views/LoginView.vue b/src/views/LoginView.vue index 3ea3d5db..4f8ce630 100644 --- a/src/views/LoginView.vue +++ b/src/views/LoginView.vue @@ -14,8 +14,8 @@
-
+ class="flex flex-col gap-8"> +
-
+
- +
+ + + 비밀번호 재설정 + +
-
- 비밀번호 재설정 -
@@ -85,16 +85,16 @@ const handleLogin = async () => { } else if (res && role && Cookies.get('refreshToken')) { switch (role) { case 'ROLE_ADMIN': - router.replace('/member-management') + router.push('/member-management') break case 'ROLE_MANAGER': - router.replace('my-task') + router.push('my-task') break case 'ROLE_USER': - router.replace('/my-request') + router.push('/my-request') break default: - router.replace('/') + router.push('/') } } } catch (error) { diff --git a/src/views/PwChangeEmail.vue b/src/views/PwChangeEmail.vue index b08f7993..1b6959f8 100644 --- a/src/views/PwChangeEmail.vue +++ b/src/views/PwChangeEmail.vue @@ -21,8 +21,8 @@
-
+ class="flex flex-col gap-8"> +
-
+
- +
+ + + 로그인 + +
-
@@ -68,7 +74,7 @@ const email = ref('') const closeModal = () => { isModalVisible.value = !isModalVisible.value - router.replace('/login') + router.push('/login') } const closeFailModal = () => { isFailModalVisible.value = !isFailModalVisible.value diff --git a/src/views/PwChangeView.vue b/src/views/PwChangeView.vue index a8a7d0d4..64c70c52 100644 --- a/src/views/PwChangeView.vue +++ b/src/views/PwChangeView.vue @@ -56,13 +56,19 @@ 비밀번호가 일치하지 않아요

- +
+ + + 취소 + +
-
diff --git a/src/views/PwCheckView.vue b/src/views/PwCheckView.vue index 12d80485..f7b9b413 100644 --- a/src/views/PwCheckView.vue +++ b/src/views/PwCheckView.vue @@ -10,7 +10,7 @@ + :content="'비밀번호 재설정을 위해\n현재 비밀번호를 입력해주세요'" />
- +
+ + +
-
From 75d44cc1f19ea1194f5f1094e8a58cefc3b2df9d Mon Sep 17 00:00:00 2001 From: seorang42 Date: Tue, 11 Feb 2025 14:43:02 +0900 Subject: [PATCH 02/17] =?UTF-8?q?:recycle:=20[refactor]=20:=20=EB=B9=84?= =?UTF-8?q?=EB=B0=80=EB=B2=88=ED=98=B8=20=EC=9E=AC=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EB=AA=A8=EB=8B=AC=20=ED=91=9C=EC=8B=9C=20=EC=A4=91=20=EC=97=94?= =?UTF-8?q?=ED=84=B0=EB=A1=9C=20submit=20=EC=9E=AC=EC=8B=A4=ED=96=89=20?= =?UTF-8?q?=EB=B0=A9=EC=A7=80,=20=EB=AA=A8=EB=8B=AC=20=EC=99=B8=20?= =?UTF-8?q?=EC=8A=A4=ED=81=AC=EB=A1=A4=20=EB=B0=A9=EC=A7=80,=20replace=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/auth.ts | 2 +- src/components/common/EditInformation.vue | 2 +- src/components/common/ModalView.vue | 4 +- src/layout/TheView.vue | 4 +- src/router/index.ts | 18 --- src/utils/preventEnter.ts | 5 + src/views/PwChangeEmail.vue | 15 ++- src/views/PwChangeView.vue | 150 +++++++++++++++++----- src/views/PwCheckView.vue | 95 -------------- 9 files changed, 140 insertions(+), 155 deletions(-) create mode 100644 src/utils/preventEnter.ts delete mode 100644 src/views/PwCheckView.vue diff --git a/src/api/auth.ts b/src/api/auth.ts index 9fc92687..35cb1391 100644 --- a/src/api/auth.ts +++ b/src/api/auth.ts @@ -33,7 +33,7 @@ export const postLogin = async (nickName: string, password: string) => { } export const patchPassword = async (password: string) => { - const request = { password: password } + const request = { password } const response = await axiosInstance.patch('/api/members/password', request) return response.data } diff --git a/src/components/common/EditInformation.vue b/src/components/common/EditInformation.vue index faaf5a71..e5bf71c2 100644 --- a/src/components/common/EditInformation.vue +++ b/src/components/common/EditInformation.vue @@ -192,7 +192,7 @@ const handlePwChange = () => { } const changePw = () => { - router.push('/pw-check') + router.replace('/pw-change') } const warningModalToggle = () => { diff --git a/src/components/common/ModalView.vue b/src/components/common/ModalView.vue index e820515a..e5957198 100644 --- a/src/components/common/ModalView.vue +++ b/src/components/common/ModalView.vue @@ -20,13 +20,13 @@ v-if="type == 'warningType'" :name="warningIcon" /> -
+
+ class="flex text-sm font-bold text-body justify-center whitespace-pre-wrap text-center">
diff --git a/src/layout/TheView.vue b/src/layout/TheView.vue index e2ff4e25..b0b64ce0 100644 --- a/src/layout/TheView.vue +++ b/src/layout/TheView.vue @@ -12,12 +12,12 @@ diff --git a/src/views/PwCheckView.vue b/src/views/PwCheckView.vue deleted file mode 100644 index f7b9b413..00000000 --- a/src/views/PwCheckView.vue +++ /dev/null @@ -1,95 +0,0 @@ - - - From c3dce6ee7cb3b417f353c56bb5d8bbfc6a66bfee Mon Sep 17 00:00:00 2001 From: seorang42 Date: Tue, 11 Feb 2025 14:56:49 +0900 Subject: [PATCH 03/17] =?UTF-8?q?:bug:=20[fix]=20=EC=9E=91=EC=97=85=20?= =?UTF-8?q?=EC=97=86=EC=9D=84=20=EC=8B=9C=20=EB=86=92=EC=9D=B4=20=EB=B9=84?= =?UTF-8?q?=EC=A0=95=EC=83=81=EC=A0=81=EC=9C=BC=EB=A1=9C=20=ED=91=9C?= =?UTF-8?q?=EC=8B=9C=EB=90=98=EB=8A=94=20=EC=98=A4=EB=A5=98=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/task-board/TaskBoard.vue | 2 +- src/views/PwCheckView.vue | 202 ++++++++++++++++++++++++ 2 files changed, 203 insertions(+), 1 deletion(-) create mode 100644 src/views/PwCheckView.vue diff --git a/src/components/task-board/TaskBoard.vue b/src/components/task-board/TaskBoard.vue index 4a9bd9b1..5a5fd9d0 100644 --- a/src/components/task-board/TaskBoard.vue +++ b/src/components/task-board/TaskBoard.vue @@ -1,5 +1,5 @@ @@ -68,6 +68,7 @@ import { axiosInstance } from '@/utils/axios' import { getMainCategory } from '@/api/common' import type { Category, CategoryForm } from '@/types/common' import ModalView from '../common/ModalView.vue' +import axios from 'axios' const router = useRouter() const route = useRoute() @@ -77,6 +78,7 @@ const { categoryStep } = defineProps<{ }>() const isModalVisible = ref({ add: false, cancel: false, fail: false }) +const errorMessage = ref('') const categoryForm = ref(CATEGORY_FORM) @@ -87,7 +89,8 @@ const handleAddModal = () => { const handleCancelModal = () => { isModalVisible.value.cancel = !isModalVisible.value.cancel } -const handleFailModal = () => { +const handleFailModal = (message: string = '카테고리 정보를 확인해주세요') => { + errorMessage.value = message isModalVisible.value.fail = !isModalVisible.value.fail } @@ -121,8 +124,14 @@ const handleSubmit = async () => { await axiosInstance.post(postUrl, categoryForm.value) } isModalVisible.value.add = true - } catch { - handleFailModal() + } catch (error) { + if (axios.isAxiosError(error)) { + if (error.response?.data === 'TASK_013') { + handleFailModal('중복된 카테고리명\n혹은 고유코드입니다') + } else { + handleFailModal() + } + } } } @@ -134,7 +143,7 @@ const isCodeInvalidate = computed(() => { return isInvalidate ? 'code' : '' }) -const mainCategory = ref('') +const mainCategory = ref('1차 카테고리를 선택해주세요') const categoryOptions = ref([]) onMounted(async () => { const id = Number(route.params.id) diff --git a/src/components/task-management/CategoryLine.vue b/src/components/task-management/CategoryLine.vue index a4b52b47..aa01ce61 100644 --- a/src/components/task-management/CategoryLine.vue +++ b/src/components/task-management/CategoryLine.vue @@ -12,7 +12,7 @@
-

{{ main.code }}

+

{{ main.code }}

{{ main.name }}

diff --git a/src/views/CategoryFirstAddView.vue b/src/views/CategoryFirstAddView.vue index 1e40776e..b00888f0 100644 --- a/src/views/CategoryFirstAddView.vue +++ b/src/views/CategoryFirstAddView.vue @@ -1,6 +1,6 @@ diff --git a/src/views/CategorySecondAddView.vue b/src/views/CategorySecondAddView.vue index f2949fe9..ca290ddb 100644 --- a/src/views/CategorySecondAddView.vue +++ b/src/views/CategorySecondAddView.vue @@ -1,6 +1,6 @@ @@ -15,6 +30,10 @@ import { ref, watch } from 'vue' import { useRoute, useRouter } from 'vue-router' import TaskDetail from '@/components/task-detail/TaskDetail.vue' +import ModalView from '@/components/common/ModalView.vue' +import { useErrorStore } from '@/stores/error' +import { storeToRefs } from 'pinia' +import { computed } from 'vue' const route = useRoute() const router = useRouter() @@ -33,4 +52,10 @@ const handleModal = (id: string | null) => { router.replace({ query: {} }) } } + +const errorStore = useErrorStore() +const { header, body } = storeToRefs(errorStore) +const computedOnClick = computed(() => { + return errorStore.onClick ? errorStore.onClick : errorStore.clearError +}) diff --git a/src/router/index.ts b/src/router/index.ts index 196a3f0f..d962c9a1 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -1,3 +1,6 @@ +import { PERMITTED_URL } from '@/constants/common' +import { useErrorStore } from '@/stores/error' +import { useMemberStore } from '@/stores/member' import { createRouter, createWebHistory } from 'vue-router' const router = createRouter({ @@ -133,4 +136,55 @@ const router = createRouter({ ] }) +router.beforeEach(async (to, from, next) => { + const memberStore = useMemberStore() + const { setError } = useErrorStore() + + await memberStore.updateMemberInfoWithToken() + const { info } = memberStore + + const originUrl = to.path.split('/')[1] + + const redirectMap = { + ROLE_USER: '/my-request', + ROLE_MANAGER: '/my-task', + ROLE_ADMIN: '/member-management' + } + + if (info.role && to.path === '/login') { + return next(redirectMap[info.role]) + } + + if (!info.role) { + if (PERMITTED_URL.UNKNOWN.includes(originUrl)) { + return next() + } + if (to.path === '/login') { + return next() + } + setError('로그인이 필요합니다') + return next('/login') + } + + const permittedUrlMap = { + ROLE_USER: PERMITTED_URL.ROLE_USER, + ROLE_MANAGER: PERMITTED_URL.ROLE_MANAGER, + ROLE_ADMIN: PERMITTED_URL.ROLE_ADMIN + } + + if (from.path === redirectMap[info.role] && !permittedUrlMap[info.role].includes(originUrl)) { + return false + } + + if (!permittedUrlMap[info.role].includes(originUrl)) { + if (to.path === redirectMap[info.role]) { + return next() + } + setError('권한이 없는 페이지입니다') + return next(redirectMap[info.role]) + } + + return next() +}) + export default router diff --git a/src/stores/error.ts b/src/stores/error.ts new file mode 100644 index 00000000..a17b95b7 --- /dev/null +++ b/src/stores/error.ts @@ -0,0 +1,22 @@ +import { defineStore } from 'pinia' +import { ref } from 'vue' + +export const useErrorStore = defineStore('error', () => { + const header = ref('') + const body = ref('') + const onClick = ref<(() => void) | null>(null) + + const setError = (newHeader: string, newBody?: string, newOnClick?: () => void) => { + header.value = newHeader + if (newBody) body.value = newBody + if (newOnClick) onClick.value = newOnClick + } + + const clearError = () => { + header.value = '' + body.value = '' + onClick.value = null + } + + return { header, body, onClick, setError, clearError } +}) diff --git a/src/stores/member.ts b/src/stores/member.ts index 98fc3992..5f1c353f 100644 --- a/src/stores/member.ts +++ b/src/stores/member.ts @@ -3,7 +3,6 @@ import { axiosInstance } from '@/utils/axios' import { ref } from 'vue' import type { User } from '@/types/auth' import Cookies from 'js-cookie' -import { useRouter } from 'vue-router' export const useMemberStore = defineStore('memberInfo', () => { const INITIAL_INFO: User = { @@ -24,13 +23,11 @@ export const useMemberStore = defineStore('memberInfo', () => { const info = ref(INITIAL_INFO) const isLogined = ref(false) - const router = useRouter() async function updateMemberInfoWithToken() { - if (!Cookies.get('accessToken')) { - router.push('/login') - return - } + const token = Cookies.get('accessToken') + if (!token) return + try { const { data }: { data: User } = await axiosInstance.get('/api/members/info') info.value = data diff --git a/src/utils/axios.ts b/src/utils/axios.ts index 6bf608f9..2310dfc0 100644 --- a/src/utils/axios.ts +++ b/src/utils/axios.ts @@ -1,7 +1,9 @@ +import { useErrorStore } from '@/stores/error' import { useMemberStore } from '@/stores/member' import axios, { type AxiosInstance, type InternalAxiosRequestConfig } from 'axios' import Cookies from 'js-cookie' import { storeToRefs } from 'pinia' +import { redirectToLogin } from './redirectToLogin' const baseURL = import.meta.env.VITE_API_BASE_URL @@ -22,7 +24,6 @@ const getNewAccessToken = async () => { } catch { Cookies.remove('accessToken') Cookies.remove('refreshToken') - window.location.href = 'login' } } const setInterceptors = (instance: AxiosInstance) => { @@ -42,14 +43,15 @@ const setInterceptors = (instance: AxiosInstance) => { instance.interceptors.response.use( response => response, async error => { + const { setError } = useErrorStore() if (axios.isCancel(error)) { - console.log('요청이 취소되었습니다:', error.message) + setError('요청이 취소되었습니다:', error.message) } else if (error.response) { - console.log('상태확인 에러메세지:', error.response) switch (error.response.status) { case 401: if (error.response.data === 'AUTH_003') { Cookies.remove('refreshToken') + setError('유효하지 않은 토큰입니다', '다시 로그인 해주세요', redirectToLogin) } break case 403: { @@ -67,8 +69,8 @@ const setInterceptors = (instance: AxiosInstance) => { originalRequest.headers.Authorization = `Bearer ${newAccessToken}` isLogined.value = true return instance(originalRequest) - } catch (err) { - console.error('토큰 갱신 실패:', err) + } catch { + setError('토큰 갱신에 실패하였습니다', '다시 로그인 해주세요', redirectToLogin) } } } @@ -80,17 +82,18 @@ const setInterceptors = (instance: AxiosInstance) => { } else if (error.response.data === 'MEMBER_012') { return Promise.reject(new Error('MEMBER_DUPLICATED')) } + break case 404: - console.error('요청한 자원을 찾을 수 없습니다.') + setError('요청한 자원을 찾을 수 없습니다') break case 500: - console.error('서버 오류: 잠시 후 다시 시도하세요.') + setError('서버 오류', '잠시 후 다시 시도하세요') break default: - console.error(`에러 발생: ${error.response.status}`) + setError('에러 발생', `${error.response.status}`) } } else { - console.error('네트워크 오류: 서버에 연결할 수 없습니다.') + setError('네트워크 오류', '서버에 연결할 수 없습니다') } return Promise.reject(error) } diff --git a/src/utils/redirectToLogin.ts b/src/utils/redirectToLogin.ts new file mode 100644 index 00000000..5bdcb932 --- /dev/null +++ b/src/utils/redirectToLogin.ts @@ -0,0 +1,7 @@ +import { useErrorStore } from '@/stores/error' + +export const redirectToLogin = () => { + const { clearError } = useErrorStore() + clearError() + window.location.href = '/login' +} diff --git a/src/views/PwChangeView.vue b/src/views/PwChangeView.vue index e06760c0..d57fc6fb 100644 --- a/src/views/PwChangeView.vue +++ b/src/views/PwChangeView.vue @@ -42,8 +42,8 @@
@@ -94,11 +94,12 @@ class="button-large-primary"> 비밀번호 재설정 - + @click="router.replace('/edit-information')"> 취소 - + From b993d164e8dbd3c9c8923a74f0fc868a1cab50a5 Mon Sep 17 00:00:00 2001 From: seorang42 Date: Tue, 11 Feb 2025 23:54:40 +0900 Subject: [PATCH 13/17] =?UTF-8?q?:bug:=20[fix]=20ModalView=20z-index=20?= =?UTF-8?q?=EC=A1=B0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/ModalView.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/common/ModalView.vue b/src/components/common/ModalView.vue index 020e406c..60d90d4d 100644 --- a/src/components/common/ModalView.vue +++ b/src/components/common/ModalView.vue @@ -1,12 +1,12 @@