프로필 수정 업데이트

This commit is contained in:
2025-06-09 23:44:43 +09:00
parent 9764a96918
commit 0fb6d98421
3 changed files with 110 additions and 71 deletions
+88 -50
View File
@@ -1,59 +1,76 @@
<template>
<div class="profile-container">
<div class="card">
<h2> 프로필</h2>
<div class="card-header">
<h2> 프로필</h2>
</div>
<div class="profile-content">
<div class="profile-header">
<div class="profile-avatar">
<Avatar :image="user.avatar || null" :label="getInitials(user.name)" size="xlarge" shape="circle" />
<Avatar :image="user.avatar || null" :label="getInitials(user.name)" size="xlarge" shape="circle" class="shadow-2" />
</div>
<div class="profile-info">
<h3>{{ user.name }}</h3>
<p>{{ user.email }}</p>
<p><Badge :value="user.role === 'admin' ? '관리자' : '일반 사용자'" :severity="user.role === 'admin' ? 'success' : 'info'" /></p>
<div class="info-item">
<span>{{ user.username }} <small>(로그인 아이디)</small></span>
</div>
<div class="info-item">
<span>{{ user.email }}</span>
</div>
<div class="info-item">
<Badge :value="user.role === 'admin' ? '관리자' : '일반 사용자'" :severity="user.role === 'admin' ? 'success' : 'info'" />
</div>
</div>
</div>
<Divider />
<Divider>
<span class="text-sm text-500">프로필 정보 수정</span>
</Divider>
<div class="profile-form">
<form @submit.prevent="updateProfile">
<div class="p-fluid">
<div class="field">
<label for="name">이름</label>
<InputText id="name" v-model="form.name" :class="{'p-invalid': v$.name.$invalid && submitted}" />
<small v-if="v$.name.$invalid && submitted" class="p-error">{{ v$.name.$errors[0].$message }}</small>
<div class="p-fluid formgrid grid">
<div class="field col-12 md:col-6">
<label for="name" class="font-medium mb-2 block">이름</label>
<InputText id="name" v-model="form.name" :class="{'p-invalid': v$.name.$invalid && submitted}" class="w-full" />
<small v-if="v$.name.$invalid && submitted" class="p-error block mt-1">{{ v$.name.$errors[0].$message }}</small>
</div>
<div class="field">
<label for="email">이메일</label>
<InputText id="email" v-model="form.email" :class="{'p-invalid': v$.email.$invalid && submitted}" />
<small v-if="v$.email.$invalid && submitted" class="p-error">{{ v$.email.$errors[0].$message }}</small>
<div class="field col-12 md:col-6">
<label for="email" class="font-medium mb-2 block">이메일</label>
<InputText id="email" v-model="form.email" :class="{'p-invalid': v$.email.$invalid && submitted}" class="w-full" />
<small v-if="v$.email.$invalid && submitted" class="p-error block mt-1">{{ v$.email.$errors[0].$message }}</small>
</div>
<Divider />
<div class="field">
<label for="currentPassword">현재 비밀번호 (변경 시에만 입력)</label>
<Password id="currentPassword" v-model="form.currentPassword" :feedback="false" toggleMask />
<div class="col-12">
<Divider>
<span class="text-sm text-500">비밀번호 변경</span>
</Divider>
</div>
<div class="field">
<label for="newPassword"> 비밀번호 (변경 시에만 입력)</label>
<Password id="newPassword" v-model="form.newPassword" :class="{'p-invalid': v$.newPassword.$invalid && passwordChangeAttempted}" toggleMask />
<small v-if="v$.newPassword.$invalid && passwordChangeAttempted" class="p-error">{{ v$.newPassword.$errors[0].$message }}</small>
<div class="field col-12 md:col-4">
<label for="currentPassword" class="font-medium mb-2 block">현재 비밀번호</label>
<Password id="currentPassword" v-model="form.currentPassword" :feedback="false" toggleMask inputClass="w-full" />
<small class="text-500 block mt-1">변경 시에만 입력</small>
</div>
<div class="field">
<label for="confirmPassword">비밀번호 확인</label>
<Password id="confirmPassword" v-model="form.confirmPassword" :class="{'p-invalid': v$.confirmPassword.$invalid && passwordChangeAttempted}" :feedback="false" toggleMask />
<small v-if="v$.confirmPassword.$invalid && passwordChangeAttempted" class="p-error">{{ v$.confirmPassword.$errors[0].$message }}</small>
<div class="field col-12 md:col-4">
<label for="newPassword" class="font-medium mb-2 block"> 비밀번호</label>
<Password id="newPassword" v-model="form.newPassword" :class="{'p-invalid': v$.newPassword.$invalid && passwordChangeAttempted}" toggleMask inputClass="w-full" />
<small v-if="v$.newPassword.$invalid && passwordChangeAttempted" class="p-error block mt-1">{{ v$.newPassword.$errors[0].$message }}</small>
<small v-else class="text-500 block mt-1">변경 시에만 입력</small>
</div>
<div class="field col-12 md:col-4">
<label for="confirmPassword" class="font-medium mb-2 block">비밀번호 확인</label>
<Password id="confirmPassword" v-model="form.confirmPassword" :class="{'p-invalid': v$.confirmPassword.$invalid && passwordChangeAttempted}" :feedback="false" toggleMask inputClass="w-full" />
<small v-if="v$.confirmPassword.$invalid && passwordChangeAttempted" class="p-error block mt-1">{{ v$.confirmPassword.$errors[0].$message }}</small>
</div>
</div>
<div class="button-container">
<Button type="submit" label="프로필 업데이트" :loading="loading" />
<Button type="submit" label="프로필 업데이트" icon="pi pi-save" :loading="loading" class="p-button-raised" />
</div>
</form>
</div>
@@ -69,10 +86,10 @@ import { ref, reactive, computed, onMounted } from 'vue';
import { useToast } from 'primevue/usetoast';
import { useVuelidate } from '@vuelidate/core';
import { required, email, minLength, sameAs, helpers } from '@vuelidate/validators';
import axios from 'axios';
import authService from '@/services/authService';
import apiClient from '@/services/api';
const toast = useToast();
const API_URL = 'http://localhost:3030/api';
// 상태 변수
const loading = ref(false);
@@ -83,6 +100,7 @@ const passwordChangeAttempted = ref(false);
const user = reactive({
id: null,
name: '',
username: '',
email: '',
role: '',
avatar: null
@@ -90,6 +108,7 @@ const user = reactive({
// 폼 데이터
const form = reactive({
username: '',
name: '',
email: '',
currentPassword: '',
@@ -122,15 +141,13 @@ onMounted(async () => {
// 사용자 프로필 가져오기
const fetchUserProfile = async () => {
try {
const token = localStorage.getItem('token');
const response = await axios.get(`${API_URL}/auth/me`, {
headers: { Authorization: `Bearer ${token}` }
});
const response = await authService.getCurrentUser();
// 사용자 정보 설정
Object.assign(user, response.data);
// 폼 데이터 초기화
form.username = user.username;
form.name = user.name;
form.email = user.email;
} catch (error) {
@@ -157,8 +174,8 @@ const updateProfile = async () => {
loading.value = true;
try {
const token = localStorage.getItem('token');
const profileData = {
id: user.id,
name: form.name,
email: form.email
};
@@ -176,9 +193,7 @@ const updateProfile = async () => {
profileData.password = form.newPassword;
}
await axios.put(`${API_URL}/auth/profile`, profileData, {
headers: { Authorization: `Bearer ${token}` }
});
await apiClient.put('/api/auth/profile', profileData);
// 사용자 정보 업데이트
Object.assign(user, {
@@ -204,39 +219,60 @@ const updateProfile = async () => {
<style scoped>
.profile-container {
max-width: 800px;
max-width: 900px;
margin: 0 auto;
padding: 2rem;
}
.card {
background: #ffffff;
padding: 2rem;
border-radius: 10px;
box-shadow: 0 2px 1px -1px rgba(0,0,0,0.2), 0 1px 1px 0 rgba(0,0,0,0.14), 0 1px 3px 0 rgba(0,0,0,0.12);
border-radius: 12px;
box-shadow: 0 2px 12px 0 rgba(0,0,0,0.1);
overflow: hidden;
}
.card-header {
background: var(--primary-color, #3B82F6);
color: white;
padding: 1.5rem 2rem;
}
.card-header h2 {
margin: 0;
font-weight: 500;
}
.profile-content {
margin-top: 2rem;
padding: 2rem;
}
.profile-header {
display: flex;
align-items: center;
gap: 2rem;
gap: 2.5rem;
margin-bottom: 2rem;
}
.profile-avatar {
flex-shrink: 0;
}
.profile-info {
h3 {
margin: 0;
margin-bottom: 0.5rem;
margin-bottom: 1rem;
font-size: 1.5rem;
color: var(--text-color-secondary, #4b5563);
}
p {
margin: 0;
margin-bottom: 0.5rem;
color: #666;
.info-item {
margin-bottom: 0.75rem;
color: var(--text-color-secondary, #6b7280);
small {
color: var(--text-color-secondary, #9ca3af);
margin-left: 0.25rem;
}
}
}
@@ -252,6 +288,8 @@ const updateProfile = async () => {
display: flex;
justify-content: flex-end;
gap: 1rem;
margin-top: 2rem;
margin-top: 2.5rem;
padding-top: 1rem;
border-top: 1px solid var(--surface-border, #e5e7eb);
}
</style>