--- app/(protected)/(basic)/admins/_components/admin-member-create-modal.tsx
+++ app/(protected)/(basic)/admins/_components/admin-member-create-modal.tsx
... | ... | @@ -26,6 +26,7 @@ |
| 26 | 26 |
ADMIN_LOGIN_ID_HELP_TEXT, |
| 27 | 27 |
ADMIN_LOGIN_ID_MESSAGES, |
| 28 | 28 |
ADMIN_LOGIN_ID_VALIDATORS, |
| 29 |
+ ADMIN_PASSWORD_ERROR_TEXT, |
|
| 29 | 30 |
ADMIN_PASSWORD_HELP_TEXT, |
| 30 | 31 |
ADMIN_PASSWORD_POLICY, |
| 31 | 32 |
INITIAL_ADMIN_MEMBER_FORM_STATE, |
... | ... | @@ -124,17 +125,19 @@ |
| 124 | 125 |
) |
| 125 | 126 |
: undefined); |
| 126 | 127 |
|
| 127 |
- // 중복 확인 결과는 서버 검증 오류보다 먼저 보여준다 — 방금 누른 버튼의 답이기 때문이다. |
|
| 128 |
+ // 중복 확인 결과는 화면 규칙보다 먼저 보여준다 — 방금 누른 버튼의 답이기 때문이다. |
|
| 128 | 129 |
// 값이 나쁜 것(unavailable)과 확인을 못 한 것(failed)은 사유가 다르므로 둘 다 문구로 낸다. |
| 129 | 130 |
const loginIdFailure = |
| 130 | 131 |
checkResult.status === 'unavailable' || checkResult.status === 'failed' |
| 131 | 132 |
? checkResult.message |
| 132 | 133 |
: undefined; |
| 134 |
+ // **서버 오류는 성공 문구를 이긴다** — 확인을 통과한 뒤에도 저장이 ID 때문에 거부될 수 있는데 |
|
| 135 |
+ // (확인과 저장 사이의 선점, 형식 규칙 변경), '사용할 수 있는 ID입니다.'가 계속 떠 있으면 |
|
| 136 |
+ // 거부된 사실이 화면에서 사라진다. |
|
| 133 | 137 |
const loginIdMessage = |
| 134 | 138 |
loginIdFailure ?? |
| 135 |
- (isLoginIdVerified |
|
| 136 |
- ? '사용할 수 있는 ID입니다.' |
|
| 137 |
- : (errors.loginId ?? ADMIN_LOGIN_ID_HELP_TEXT)); |
|
| 139 |
+ errors.loginId ?? |
|
| 140 |
+ (isLoginIdVerified ? '사용할 수 있는 ID입니다.' : ADMIN_LOGIN_ID_HELP_TEXT); |
|
| 138 | 141 |
|
| 139 | 142 |
return ( |
| 140 | 143 |
<FoxModal |
... | ... | @@ -221,7 +224,7 @@ |
| 221 | 224 |
invalid={Boolean(errors.password)}
|
| 222 | 225 |
// 규칙은 도메인이 한 벌로 갖는다 — 저장 직전 서버 검증이 같은 값을 본다. |
| 223 | 226 |
validators={[foxPasswordValidator({ ...ADMIN_PASSWORD_POLICY, required: true })]}
|
| 224 |
- validationMessages={{ minlength: ADMIN_PASSWORD_HELP_TEXT }}
|
|
| 227 |
+ validationMessages={{ minlength: ADMIN_PASSWORD_ERROR_TEXT }}
|
|
| 225 | 228 |
/> |
| 226 | 229 |
|
| 227 | 230 |
<AdminMemberFormFields |
--- app/(protected)/(basic)/admins/_components/admin-member-edit-modal.tsx
+++ app/(protected)/(basic)/admins/_components/admin-member-edit-modal.tsx
... | ... | @@ -13,6 +13,7 @@ |
| 13 | 13 |
import { DEFAULT_ADMIN_ROLE_CODE, type AdminMember } from '@/lib/domain/admin-member';
|
| 14 | 14 |
import {
|
| 15 | 15 |
ADMIN_EMAIL_MESSAGES, |
| 16 |
+ ADMIN_PASSWORD_ERROR_TEXT, |
|
| 16 | 17 |
ADMIN_PASSWORD_HELP_TEXT, |
| 17 | 18 |
ADMIN_PASSWORD_POLICY, |
| 18 | 19 |
INITIAL_ADMIN_MEMBER_FORM_STATE, |
... | ... | @@ -139,7 +140,7 @@ |
| 139 | 140 |
invalid={Boolean(errors.password)}
|
| 140 | 141 |
// 비우면 "바꾸지 않음"이라 required를 걸지 않는다(등록 팝업과 다른 점). |
| 141 | 142 |
validators={[foxPasswordValidator(ADMIN_PASSWORD_POLICY)]}
|
| 142 |
- validationMessages={{ minlength: ADMIN_PASSWORD_HELP_TEXT }}
|
|
| 143 |
+ validationMessages={{ minlength: ADMIN_PASSWORD_ERROR_TEXT }}
|
|
| 143 | 144 |
/> |
| 144 | 145 |
<FoxButton |
| 145 | 146 |
type="secondary" |
--- lib/domain/admin-member-form.ts
+++ lib/domain/admin-member-form.ts
... | ... | @@ -34,7 +34,14 @@ |
| 34 | 34 |
export const ADMIN_LOGIN_ID_HELP_TEXT = |
| 35 | 35 |
'영어 소문자, 숫자를 조합하여 입력 후 중복여부를 확인하세요.'; |
| 36 | 36 |
export const ADMIN_PASSWORD_HELP_TEXT = |
| 37 |
- '영어 소문자, 숫자, 특수문자 중 2종류 이상 조합, 최소 10자리 이상'; |
|
| 37 |
+ '영어, 숫자, 특수문자 중 2종류 이상 조합, 최소 10자리 이상'; |
|
| 38 |
+ |
|
| 39 |
+/** |
|
| 40 |
+ * 비밀번호가 규칙에 걸렸을 때의 문구. **안내 문구와 글자가 달라야 한다** — 같으면 저장이 |
|
| 41 |
+ * 거부돼도 화면의 글자가 하나도 바뀌지 않아 사용자가 실패한 줄 모른다(실제로 그랬다). |
|
| 42 |
+ */ |
|
| 43 |
+export const ADMIN_PASSWORD_ERROR_TEXT = |
|
| 44 |
+ '비밀번호가 규칙에 맞지 않습니다. 영어·숫자·특수문자 중 2종류 이상으로 10자리 이상 입력해 주세요.'; |
|
| 38 | 45 |
|
| 39 | 46 |
|
| 40 | 47 |
const LOGIN_ID_MIN_LENGTH = 4; |
... | ... | @@ -141,11 +148,15 @@ |
| 141 | 148 |
}; |
| 142 | 149 |
|
| 143 | 150 |
/** |
| 144 |
- * 비밀번호 조합 종류 수 — 영문 소문자 / 숫자 / 특수문자 중 몇 종류가 섞였는지 센다. |
|
| 145 |
- * 시안이 "2종류 이상"만 요구하므로 대문자는 별도 종류로 세지 않는다(있어도 무방하다). |
|
| 151 |
+ * 비밀번호 조합 종류 수 — 영문 / 숫자 / 특수문자 중 몇 종류가 섞였는지 센다. |
|
| 152 |
+ * |
|
| 153 |
+ * **대문자도 영문 한 종류로 센다** — 화면이 쓰는 `@fox`의 `characterKinds`가 `/[a-zA-Z]/`로 |
|
| 154 |
+ * 판정하므로 여기가 `[a-z]`면 규칙이 갈린다. 실제로 `PASSWORD123`처럼 영문자가 전부 대문자인 |
|
| 155 |
+ * 값이 화면은 통과하고 저장만 거부되는 상태였다(오류 문구가 안내 문구와 같아 화면에 아무것도 |
|
| 156 |
+ * 드러나지 않았다). |
|
| 146 | 157 |
*/ |
| 147 | 158 |
function countCharacterKinds(password: string): number {
|
| 148 |
- const kinds = [/[a-z]/, /\d/, /[^a-zA-Z0-9]/]; |
|
| 159 |
+ const kinds = [/[a-zA-Z]/, /\d/, /[^a-zA-Z0-9]/]; |
|
| 149 | 160 |
return kinds.filter((pattern) => pattern.test(password)).length; |
| 150 | 161 |
} |
| 151 | 162 |
|
... | ... | @@ -197,13 +208,13 @@ |
| 197 | 208 |
|
| 198 | 209 |
if (!password) {
|
| 199 | 210 |
if (options.passwordRequired) {
|
| 200 |
- errors.password = ADMIN_PASSWORD_HELP_TEXT; |
|
| 211 |
+ errors.password = ADMIN_PASSWORD_ERROR_TEXT; |
|
| 201 | 212 |
} |
| 202 | 213 |
} else if ( |
| 203 | 214 |
password.length < ADMIN_PASSWORD_POLICY.minLength || |
| 204 | 215 |
countCharacterKinds(password) < ADMIN_PASSWORD_POLICY.kinds |
| 205 | 216 |
) {
|
| 206 |
- errors.password = ADMIN_PASSWORD_HELP_TEXT; |
|
| 217 |
+ errors.password = ADMIN_PASSWORD_ERROR_TEXT; |
|
| 207 | 218 |
} |
| 208 | 219 |
|
| 209 | 220 |
if (!phoneNumber) {
|
Add a comment
Delete comment
Once you delete this comment, you won't be able to recover it. Are you sure you want to delete this comment?