Skip to content
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
09dd262
✨ [feat] : 로그인 API 연결
BaekJiyeon02 Feb 2, 2025
89d9fa7
✨ [feat] : 액세스 토큰 쿠키에 저장
BaekJiyeon02 Feb 2, 2025
0f024ee
✨ [feat] : 로그인 시 각 역할마다 경로 지정
BaekJiyeon02 Feb 2, 2025
d8712e2
✨ [feat] : Pinia 회원정보 저장하여 상태관리
BaekJiyeon02 Feb 2, 2025
c3ae5cd
✨ [feat] : Pinia에 저장된 회원정보 Top, Side에 적용
BaekJiyeon02 Feb 2, 2025
61c54f0
✨ [feat] : 비밀번호 재설정 API 연결
BaekJiyeon02 Feb 2, 2025
e512e46
♻️ [refactor] : 불필요한 코드 제거
BaekJiyeon02 Feb 2, 2025
45d3815
:sparkles: [feat] : 폼데이터 요청 api 형식 분리
Minkyu0424 Feb 1, 2025
61df8a1
:recycle: [refactor] : prop대신 직접 가져오기, 파일 초기값 배열로
Minkyu0424 Feb 1, 2025
11462ed
:recycle: [refactor] : 1,2차 카테고리 타입 선언 및 위치 변경
Minkyu0424 Feb 1, 2025
6a6b4a6
:sparkles: [feat] : 카테고리용 드롭다운 개별 생성
Minkyu0424 Feb 1, 2025
61ed646
:sparkles: [feat] : 1, 2차 카테고리 조회 api 연결
Minkyu0424 Feb 1, 2025
d1a8a41
:sparkles: [feat] : 요청 생성 api 연결
Minkyu0424 Feb 1, 2025
65b0f37
:sparkles: [feat] : 정보 누락시 경고 텍스트 출력
Minkyu0424 Feb 2, 2025
cd970aa
:sparkles: [feat] : 요청 완료 후 모달 및 라우팅
Minkyu0424 Feb 2, 2025
8c202cf
:recycle: [refactor] : axios instance 객체 분리해서 import
Minkyu0424 Feb 2, 2025
281050c
:recycle: [refactor] : 카테고리 드롭다운 placeholder없이, input placeholder 상수화 취소
Minkyu0424 Feb 2, 2025
9f952ae
♻️ [refactor] : axios 인스턴스 이슈 반영
BaekJiyeon02 Feb 2, 2025
f97d35b
:recycle: [refactor] : 1,2차 카테고리 타입 선언 및 위치 변경
Minkyu0424 Feb 1, 2025
c49d1fb
:sparkles: [feat] : 요청 생성 api 연결
Minkyu0424 Feb 1, 2025
d6b475b
:recycle: [refactor] : 1,2차 카테고리 타입 선언 및 위치 변경
Minkyu0424 Feb 1, 2025
81bb0ca
:sparkles: [feat] : 요청 생성 api 연결
Minkyu0424 Feb 1, 2025
6404730
♻️ [refactor] : 역할 변수에 따른 값 수정
BaekJiyeon02 Feb 2, 2025
f9d5023
Merge branch 'develop' into CLAP-205
BaekJiyeon02 Feb 2, 2025
1bb1826
♻️ [refactor] : 사용하지 않는 값 제거
BaekJiyeon02 Feb 2, 2025
9e76611
♻️ [refactor] : 토큰 axios파일에서 처리되므로 불필요하여 제거
BaekJiyeon02 Feb 2, 2025
6ccd304
♻️ [refactor] : 불필요한 예외처리 제거
BaekJiyeon02 Feb 2, 2025
de257f7
♻️ [refactor] : request body 추가 및 타입 선언
BaekJiyeon02 Feb 2, 2025
008fa11
♻️ [refactor] : 불필요 코드 제거
BaekJiyeon02 Feb 2, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions src/api/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { axiosInstance } from '@/utils/axios'
import Cookies from 'js-cookie'

export const postLogin = async (nickname: string, password: string) => {
const body = {
nickname: nickname,
password: password
}
const sessionIdValue = '123'
try {
const response = await axiosInstance.post('/api/auths/login', body, {
headers: {
sessionId: sessionIdValue
}
})

Cookies.set('accessToken', response.data.accessToken, {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

혹시 순수 js를 사용하여 쿠키를 저장하고 사용하지 않고
js-cookie 라이브러리를 사용하시게 된 이유가 있을까요?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

우선 vue-cookies나 순수 js를 사용한 쿠키보다 보안 안정성 측면과 TypeScript 지원을 보았을때에도 js-cookie 라이브러리가 우수하다고 알고 있습니다. 또한 추후 토큰 변경 감지를 위한 watchEffect 를 사용할 것을 염두하여 선택하였습니다!
더 나아가면 개발자가 편하려고 사용하는 것은 이유가 되지 않지만, 사용하기 매우 편하다는 장점도 있습니다😳

path: '/',
sameSite: 'strict'
})

return response.data
} catch (error) {
console.error('로그인 오류:', error)
throw error
}
}

export const patchPassword = async (password: string) => {
const accessToken = Cookies.get('accessToken')

if (!accessToken) {
console.error('Access token is missing')
return
}

try {
const response = await axiosInstance.patch('/api/members/password', password, {
headers: {
Authorization: `Bearer ${accessToken}`
}
})
return response.data
} catch (error) {
console.log('api error:', error)
}
}
28 changes: 19 additions & 9 deletions src/components/SideBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,14 @@

<div class="flex w-full justify-between px-6 py-4 bg-white">
<div class="flex w-full items-center gap-3">
<!-- 프로필 사진 API 필요 -->
<div class="w-10 h-10 rounded-full bg-background-1" />
<img
v-if="info?.imageUrl"
class="rounded-[50%] w-10 h-10"
:src="info.imageUrl"
alt="프로필 이미지" />
<div
v-else
class="w-10 h-10 rounded-full bg-background-1" />
<div class="flex flex-col gap-1">
<p class="text-xs text-body font-bold">{{ name }}</p>
<p class="text-sm text-black">{{ nickname }}</p>
Expand All @@ -61,22 +67,26 @@

<script setup lang="ts">
import { useRoute } from 'vue-router'
import { computed, ref } from 'vue'
import { computed } from 'vue'
import CommonIcons from './common/CommonIcons.vue'
import { hamburgerIcon } from '@/constants/iconPath'
import { SIDE_USER_MENU, SIDE_MANAGER_MENU, SIDE_ADMIN_MENU } from '@/constants/menu'
import { useMemberStore } from '@/stores/member'
import { storeToRefs } from 'pinia'

const memberStore = useMemberStore()
const { info } = storeToRefs(memberStore)

const route = useRoute()

// 회원 역할, 닉네임 필요
const role = ref('manager')
const name = ref('백지연')
const nickname = ref('Chloe.yeon')
const role = computed(() => info.value.memberRole)
const name = computed(() => info.value.memberName)
const nickname = computed(() => info.value.nickname)

const filteredMenu = computed(() => {
return role.value === 'user'
return role.value === 'ROLE_USER'
? SIDE_USER_MENU
: role.value === 'manager'
: role.value === 'ROLE_MANAGER'
? SIDE_MANAGER_MENU
: SIDE_ADMIN_MENU
})
Expand Down
22 changes: 18 additions & 4 deletions src/components/TopBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,14 @@
v-show="isLogined"
class="flex items-center gap-6">
<NotificationIcon :new-notification="12" />
<!-- 프로필 사진 API 필요 -->
<div class="rounded-[50%] bg-zinc-100 p-5" />
<img
v-if="info?.imageUrl"
class="rounded-[50%] w-10 h-10"
:src="info.imageUrl"
alt="프로필 이미지" />
<div
v-else
class="rounded-[50%] bg-zinc-100 p-5" />
</div>
</div>
</div>
Expand All @@ -25,13 +31,21 @@
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { ref, onMounted } from 'vue'
import CommonIcons from './common/CommonIcons.vue'
import SideBar from './SideBar.vue'
import { hamburgerIcon } from '../constants/iconPath'
import NotificationIcon from './icons/NotificationIcon.vue'
import { storeToRefs } from 'pinia'
import { useMemberStore } from '@/stores/member'

const memberStore = useMemberStore()
const { info } = storeToRefs(memberStore)

onMounted(async () => {
await memberStore.updateMemberInfoWithToken()
})

// 로그인 정보 필요
const isSideOpen = ref(false)
const isLogined = ref(true)

Expand Down
1 change: 1 addition & 0 deletions src/components/request-task/RequestTask.vue
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ const handleSubmit = async () => {
const formData = new FormData()
const taskInfo = {
categoryId: category2.value.id,

title: title.value,
description: description.value
}
Expand Down
57 changes: 57 additions & 0 deletions src/stores/member.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { defineStore } from 'pinia'
import { axiosInstance } from '@/utils/axios'
import { ref } from 'vue'
import type { User } from '@/types/auth'
import Cookies from 'js-cookie'

export const useMemberStore = defineStore('memberInfo', () => {
const info = ref<User>({
memberId: 0,
memberName: '',
nickname: '',
imageUrl: '',
memberRole: '',
memberStatus: ''
})

async function updateMemberInfoWithToken() {
try {
const accessToken = Cookies.get('accessToken')

if (!accessToken) {
console.error('Access token error')
return
}

const response = await axiosInstance.get('/api/members/info', {
headers: {
Authorization: `Bearer ${accessToken}`
}
})

console.log('API Response:', response.data)
updateMemberInfo(response.data)
} catch (error) {
console.error('updata error:', error)
}
}

function updateMemberInfo(responseData: any) {
info.value = {
memberId: 0,
memberName: responseData.name || '',
nickname: responseData.nicknanme || '',
imageUrl: responseData.profileImageUrl || '',
memberRole: responseData.role || '',
memberStatus: ''
}

console.log('Updated member info:', info.value)
}

return {
info,
updateMemberInfo,
updateMemberInfoWithToken
}
})
9 changes: 9 additions & 0 deletions src/types/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,12 @@ export interface UserTypes {
created_at: string
updated_at: string
}

export interface User {
memberId: number
memberName: string
nickname: string
imageUrl: string
memberRole: string
memberStatus: string
}
27 changes: 26 additions & 1 deletion src/views/LoginView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,36 @@

<script setup lang="ts">
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { postLogin } from '@/api/auth'

const router = useRouter()

const nickname = ref('')
const password = ref('')

const handleLogin = async () => {
// 로그인 API 필요
try {
const res = await postLogin(nickname.value, password.value)

if (res) {
switch (res.memberInfo.memberRole) {
case 'ROLE_ADMIN':
router.push('/member-management')
break
case 'ROLE_MANAGER':
router.push('my-request')
break
case 'ROLE_USER':
router.push('/my-request')
break
default:
router.push('/')
}
}
} catch (error) {
// 로그인 실패 시 에러 처리
console.error('로그인 실패:', error)
}
}
</script>
4 changes: 3 additions & 1 deletion src/views/PwChange.vue
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import ModalView from '../components/ModalView.vue'
import { patchPassword } from '@/api/auth'

const newPw = ref('')
const checkPw = ref('')
Expand All @@ -61,9 +62,10 @@ const toggleModal = () => {

const handleChange = () => {
if (newPw.value === checkPw.value) {
const response = patchPassword(newPw.value)
console.log(response)
console.log('비밀번호 변경 성공!')
toggleModal()
// 비밀번호 재설정 API 호출 필요
} else {
alert('비밀번호가 일치하지 않습니다.')
}
Expand Down