Skip to content

Commit 5c971d1

Browse files
authored
Merge pull request #120 from TaskFlow-CLAP/CLAP-323
CLAP-323 유효성 검사 및 UX 개선
2 parents 1fe986b + 37ff4c5 commit 5c971d1

File tree

6 files changed

+267
-52
lines changed

6 files changed

+267
-52
lines changed

src/components/EditInformation.vue

Lines changed: 81 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -22,26 +22,48 @@
2222
class="mt-3"
2323
:url="previewUrl || info.profileImageUrl"
2424
:size="96" />
25-
26-
<label
27-
for="fileInput"
28-
class="mt-3 text-xs text-primary1 font-bold cursor-pointer"
29-
>변경</label
30-
>
25+
<div class="flex gap-6">
26+
<label
27+
for="fileInput"
28+
class="mt-3 text-xs text-primary1 font-bold cursor-pointer hover:underline"
29+
>변경</label
30+
>
31+
<label
32+
for="fileDelete"
33+
class="mt-3 text-xs text-red-1 font-bold cursor-pointer hover:underline"
34+
>삭제</label
35+
>
36+
</div>
3137
<input
3238
id="fileInput"
3339
type="file"
3440
@change="handleFileUpload"
3541
accept="image/*"
3642
class="hidden" />
43+
<button
44+
id="fileDelete"
45+
type="button"
46+
@click="handleFileDelete"
47+
class="hidden" />
3748
</div>
38-
39-
<div class="flex flex-col">
49+
<div class="flex flex-col relative">
4050
<p class="text-body text-xs font-bold">이름</p>
51+
<span class="absolute top-1 right-2 text-xs text-gray-500"> {{ name.length }} / 10 </span>
4152
<input
42-
class="input-box h-11 mt-2 text-black"
53+
:class="[
54+
'block w-full px-4 py-4 border rounded focus:outline-none h-11 mt-2 text-black',
55+
isInvalid ? 'border-red-1' : 'border-border-1'
56+
]"
4357
placeholder="이름을 입력해주세요"
44-
v-model="name" />
58+
v-model="name"
59+
maxlength="10"
60+
ref="nameInput"
61+
@blur="validateName" />
62+
<span
63+
v-show="isInvalid"
64+
class="text-red-1 text-xs font-bold mt-1"
65+
>이름에는 특수문자가 포함될 수 없습니다.</span
66+
>
4567
</div>
4668
<div class="flex flex-col">
4769
<p class="text-body text-xs font-bold">아이디</p>
@@ -94,7 +116,7 @@
94116
</template>
95117

96118
<script lang="ts" setup>
97-
import { ref, watchEffect } from 'vue'
119+
import { nextTick, ref, watchEffect } from 'vue'
98120
import { useRouter } from 'vue-router'
99121
import ModalView from './ModalView.vue'
100122
import FormButtonContainer from './common/FormButtonContainer.vue'
@@ -112,10 +134,14 @@ const name = ref(info.value.name)
112134
const agitCheck = ref(info.value.notificationSettingInfo.agit)
113135
const emailCheck = ref(info.value.notificationSettingInfo.email)
114136
const kakaoWorkCheck = ref(info.value.notificationSettingInfo.kakaoWork)
137+
const imageDelete = ref(info.value.profileImageUrl == null ? true : false)
115138
116139
const selectedFile = ref<File | null>(null)
117140
const previewUrl = ref<string | null>(null)
118141
142+
const isInvalid = ref(false)
143+
const nameInput = ref<HTMLInputElement | null>(null)
144+
119145
const isModalVisible = ref(false)
120146
const isWarnningModalVisible = ref(false)
121147
@@ -128,6 +154,16 @@ watchEffect(() => {
128154
}
129155
})
130156
157+
const validateName = () => {
158+
const regex = /[!@#$%^&*(),.?":{}|<>]/g
159+
isInvalid.value = regex.test(name.value)
160+
161+
if (isInvalid.value) {
162+
nextTick(() => {
163+
nameInput.value?.focus()
164+
})
165+
}
166+
}
131167
const handleCancel = () => {
132168
router.back()
133169
}
@@ -160,31 +196,43 @@ const handleFileUpload = (event: Event) => {
160196
selectedFile.value = target.files[0]
161197
previewUrl.value = URL.createObjectURL(selectedFile.value)
162198
}
199+
imageDelete.value = false
163200
}
164201
165-
const handleSubmit = async () => {
166-
const formData = new FormData()
167-
const memberInfo = {
168-
name: name.value,
169-
agitNotification: agitCheck.value,
170-
emailNotification: emailCheck.value,
171-
kakaoWorkNotification: kakaoWorkCheck.value
172-
}
173-
const jsonMemberInfo = JSON.stringify(memberInfo)
174-
const newBlob = new Blob([jsonMemberInfo], { type: 'application/json' })
175-
176-
formData.append('memberInfo', newBlob)
177-
178-
if (selectedFile.value) {
179-
formData.append('profileImage', selectedFile.value)
180-
}
202+
const handleFileDelete = () => {
203+
imageDelete.value = true
204+
previewUrl.value = ''
205+
info.value.profileImageUrl = ''
206+
}
181207
182-
try {
183-
await patchEditInfo(formData)
184-
isModalVisible.value = true
185-
await memberStore.updateMemberInfoWithToken()
186-
} catch (error) {
187-
console.error('요청 실패:', error)
208+
const handleSubmit = async () => {
209+
if (isInvalid.value == false) {
210+
const formData = new FormData()
211+
const memberInfo = {
212+
name: name.value,
213+
isProfileImageDeleted: imageDelete.value,
214+
agitNotification: agitCheck.value,
215+
emailNotification: emailCheck.value,
216+
kakaoWorkNotification: kakaoWorkCheck.value
217+
}
218+
const jsonMemberInfo = JSON.stringify(memberInfo)
219+
const newBlob = new Blob([jsonMemberInfo], { type: 'application/json' })
220+
221+
formData.append('memberInfo', newBlob)
222+
223+
if (selectedFile.value && imageDelete.value == false) {
224+
formData.append('profileImage', selectedFile.value)
225+
} else if (imageDelete.value == true) {
226+
selectedFile.value = null
227+
}
228+
229+
try {
230+
await patchEditInfo(formData)
231+
isModalVisible.value = true
232+
await memberStore.updateMemberInfoWithToken()
233+
} catch (error) {
234+
console.error('요청 실패:', error)
235+
}
188236
}
189237
}
190238
</script>

src/constants/iconPath.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,3 +247,19 @@ export const menuDotIcon = {
247247
height: 20,
248248
fill: '#71717A'
249249
}
250+
251+
export const offEyeIcon = {
252+
path: 'M23.257 10H23.262H23.252H23.257ZM22.552 9.48C22.652 9.798 22.94 9.998 23.257 10C23.327 10 23.404 9.98 23.482 9.96C23.872 9.84 24.092 9.41 23.962 9.02C23.932 8.93 20.932 0 12 0C3.06797 0 0.0669704 8.93 0.0369704 9.02C-0.0930296 9.41 0.12697 9.83 0.51697 9.96C0.91697 10.09 1.33697 9.87 1.46697 9.48L1.46997 9.475C1.60297 9.085 4.20697 1.5 12.01 1.5C19.852 1.5 22.442 9.15 22.552 9.48ZM8.49997 10C8.49997 9.07174 8.86872 8.1815 9.5251 7.52513C10.1815 6.86875 11.0717 6.5 12 6.5C12.9282 6.5 13.8185 6.86875 14.4748 7.52513C15.1312 8.1815 15.5 9.07174 15.5 10C15.5 10.9283 15.1312 11.8185 14.4748 12.4749C13.8185 13.1313 12.9282 13.5 12 13.5C11.0717 13.5 10.1815 13.1313 9.5251 12.4749C8.86872 11.8185 8.49997 10.9283 8.49997 10ZM12 5C10.6739 5 9.40212 5.52678 8.46444 6.46447C7.52675 7.40215 6.99997 8.67392 6.99997 10C6.99997 11.3261 7.52675 12.5979 8.46444 13.5355C9.40212 14.4732 10.6739 15 12 15C13.3261 15 14.5978 14.4732 15.5355 13.5355C16.4732 12.5979 17 11.3261 17 10C17 8.67392 16.4732 7.40215 15.5355 6.46447C14.5978 5.52678 13.3261 5 12 5Z',
253+
width: 24,
254+
height: 15,
255+
fill: 'black'
256+
}
257+
export const onEyeIcon = {
258+
path: 'M23.257 10H23.262H23.252H23.257ZM22.552 9.48C22.652 9.798 22.94 9.998 23.257 10C23.327 10 23.404 9.98 23.482 9.96C23.872 9.84 24.092 9.41 23.962 9.02C23.932 8.93 20.932 0 12 0C3.06797 0 0.0669704 8.93 0.0369704 9.02C-0.0930296 9.41 0.12697 9.83 0.51697 9.96C0.91697 10.09 1.33697 9.87 1.46697 9.48L1.46997 9.475C1.60297 9.085 4.20697 1.5 12.01 1.5C19.852 1.5 22.442 9.15 22.552 9.48ZM6.99997 10C6.99997 8.67392 7.52675 7.40215 8.46444 6.46447C9.40212 5.52678 10.6739 5 12 5C13.3261 5 14.5978 5.52678 15.5355 6.46447C16.4732 7.40215 17 8.67392 17 10C17 11.3261 16.4732 12.5979 15.5355 13.5355C14.5978 14.4732 13.3261 15 12 15C10.6739 15 9.40212 14.4732 8.46444 13.5355C7.52675 12.5979 6.99997 11.3261 6.99997 10Z',
259+
width: 24,
260+
height: 15,
261+
fill: 'black'
262+
// fill: 'none',
263+
// Option: {
264+
// }
265+
}

src/views/LoginView.vue

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
<template>
22
<div class="max-w-400">
3+
<ModalView
4+
:is-open="isModalVisible"
5+
type="failType"
6+
@close="closeModal">
7+
<template #header>{{ messageHeader }}</template>
8+
<template #body>{{ messageBody }}</template>
9+
</ModalView>
310
<div class="py-16">
411
<TitleContainer
512
:title="'TaskFlow\n로그인'"
@@ -34,7 +41,7 @@
3441
</form>
3542
<div class="flex w-full justify-center">
3643
<RouterLink
37-
class="text-body font-bold text-[12px] hover:underline"
44+
class="text-body font-bold text-[12px]"
3845
to="/pw-change-email"
3946
>비밀번호 재설정</RouterLink
4047
>
@@ -49,13 +56,23 @@ import { postLogin } from '@/api/auth'
4956
import { useMemberStore } from '@/stores/member'
5057
import TitleContainer from '@/components/common/TitleContainer.vue'
5158
import Cookies from 'js-cookie'
59+
import ModalView from '@/components/ModalView.vue'
5260
5361
const router = useRouter()
5462
5563
const nickname = ref('')
5664
const password = ref('')
5765
const memberStore = useMemberStore()
5866
67+
const messageHeader = ref('')
68+
const messageBody = ref('')
69+
70+
const isModalVisible = ref(false)
71+
72+
const closeModal = () => {
73+
isModalVisible.value = false
74+
}
75+
5976
const handleLogin = async () => {
6077
try {
6178
const loginData = {
@@ -83,7 +100,19 @@ const handleLogin = async () => {
83100
}
84101
}
85102
} catch (error) {
86-
console.error('로그인 실패:', error)
103+
switch (error?.response?.status) {
104+
case 401:
105+
isModalVisible.value = !isModalVisible.value
106+
messageHeader.value = '일치하는 정보가 없습니다'
107+
messageBody.value = '닉네임과 비밀번호를 다시 확인해 주세요'
108+
break
109+
110+
case 500:
111+
isModalVisible.value = !isModalVisible.value
112+
messageHeader.value = '서버에 문제가 발생했습니다'
113+
messageBody.value = '잠시후 다시 이용해주세요'
114+
break
115+
}
87116
}
88117
}
89118
</script>

src/views/PwChange.vue

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<ModalView
44
:isOpen="isModalOpen"
55
type="successType"
6-
@close="pwChange">
6+
@close="closeModal">
77
<template #header> 비밀번호가 변경 되었습니다 </template>
88
<template #body> 다시 로그인 해주세요 </template>
99
</ModalView>
@@ -27,16 +27,35 @@
2727
v-model="newPw"
2828
placeholder="새 비밀번호를 입력해주세요"
2929
required
30-
class="input-box" />
30+
ref="passwordInput"
31+
:class="[
32+
'block w-full px-4 py-4 border rounded focus:outline-none',
33+
isInvalid ? 'border-red-1' : 'border-border-1'
34+
]"
35+
@blur="validatePassword" />
36+
<p
37+
v-show="isInvalid"
38+
class="text-red-1 text-xs font-bold mt-1">
39+
대문자, 소문자, 숫자, 특수문자 포함 8자-20자 입력해주세요
40+
</p>
3141
</div>
3242
<div class="mb-8">
3343
<input
3444
type="password"
3545
id="checkPw"
3646
v-model="checkPw"
47+
ref="checkPwInput"
3748
placeholder="새 비밀번호를 다시 입력해주세요"
3849
required
39-
class="input-box" />
50+
:class="[
51+
'block w-full px-4 py-4 border rounded focus:outline-none',
52+
isDifferent ? 'border-red-1' : 'border-border-1'
53+
]" />
54+
<p
55+
v-show="isDifferent"
56+
class="text-red-1 text-xs font-bold mt-1">
57+
비밀번호가 일치하지 않아요
58+
</p>
4059
</div>
4160
<button
4261
type="submit"
@@ -49,7 +68,7 @@
4968
</template>
5069

5170
<script setup lang="ts">
52-
import { ref } from 'vue'
71+
import { nextTick, ref } from 'vue'
5372
import { useRouter } from 'vue-router'
5473
import ModalView from '../components/ModalView.vue'
5574
import { deleteLogout, patchPassword } from '@/api/auth'
@@ -65,22 +84,43 @@ const newPw = ref('')
6584
const checkPw = ref('')
6685
const isModalOpen = ref(false)
6786
const router = useRouter()
87+
const isDifferent = ref(false)
88+
const checkPwInput = ref<HTMLInputElement | null>(null)
89+
const isInvalid = ref(false)
90+
const passwordInput = ref<HTMLInputElement | null>(null)
6891
69-
const toggleModal = () => {
92+
const validatePassword = () => {
93+
const regex = /^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[!@#$%^&*()_+{}\[\]:;<>,.?/~`-]).{8,20}$/
94+
isInvalid.value = !regex.test(newPw.value)
95+
96+
if (isInvalid.value) {
97+
nextTick(() => {
98+
passwordInput.value?.focus()
99+
})
100+
}
101+
}
102+
103+
const openModal = () => {
104+
isModalOpen.value = !isModalOpen.value
105+
}
106+
const closeModal = () => {
70107
isModalOpen.value = !isModalOpen.value
108+
router.push('/login')
109+
deleteLogout()
71110
}
72111
73112
const handleChange = () => {
74113
if (newPw.value === checkPw.value) {
75114
patchPassword(newPw.value)
76-
toggleModal()
115+
pwChange()
116+
openModal()
77117
} else {
118+
isDifferent.value = true
119+
checkPwInput.value?.focus()
78120
}
79121
}
80122
81123
const pwChange = () => {
82124
isLogined.value = false
83-
deleteLogout()
84-
router.push('/login')
85125
}
86126
</script>

0 commit comments

Comments
 (0)