계정 관리 이메일/전화번호 실시간 validation 및 오류 표시 기능 추가
- 이메일/전화번호 형식 및 중복 검증 기능 추가 - 실시간 validation 오류 메시지 표시 - 입력 중이거나 오류 발생 시 추가 버튼 비활성화 - 공백 trim 처리하여 중복 검사 정확도 향상 - validation 오류 시 편집 모드 유지하여 수정 가능 - 빈 입력란 blur 시 자동 제거 - 삭제 버튼 항상 활성화 - 다국어 지원 (한국어/영어) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -97,8 +97,22 @@ export const UserLoginAuthInfoWrap = ({
|
|||||||
const handleAddEmail = () => {
|
const handleAddEmail = () => {
|
||||||
// 현재 편집 중인 항목을 읽기전용으로 고정
|
// 현재 편집 중인 항목을 읽기전용으로 고정
|
||||||
if (editableEmailIndex >= 0) {
|
if (editableEmailIndex >= 0) {
|
||||||
|
const currentEmail = newEmails[editableEmailIndex];
|
||||||
|
|
||||||
|
// 유효성 검사: 값이 있고, 형식이 올바르고, 중복이 아닐 때만 readOnly로 변경
|
||||||
|
if (currentEmail && currentEmail.trim() && isValidEmail(currentEmail)) {
|
||||||
|
// 중복 검사 - trim하여 비교
|
||||||
|
const trimmedEmail = currentEmail.trim();
|
||||||
|
const allEmails = [
|
||||||
|
...(authMethodData?.emails?.map(e => e.content?.trim()) || []),
|
||||||
|
...newEmails.map(e => e.trim()).filter((_, i) => i !== editableEmailIndex)
|
||||||
|
].filter(e => e); // 빈 문자열 제거
|
||||||
|
|
||||||
|
if (!allEmails.includes(trimmedEmail)) {
|
||||||
setReadOnlyEmails(prev => new Set([...prev, editableEmailIndex]));
|
setReadOnlyEmails(prev => new Set([...prev, editableEmailIndex]));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 새로운 편집 가능한 항목 추가
|
// 새로운 편집 가능한 항목 추가
|
||||||
setEditableEmailIndex(newEmails.length);
|
setEditableEmailIndex(newEmails.length);
|
||||||
@@ -108,8 +122,22 @@ export const UserLoginAuthInfoWrap = ({
|
|||||||
const handleAddPhone = () => {
|
const handleAddPhone = () => {
|
||||||
// 현재 편집 중인 항목을 읽기전용으로 고정
|
// 현재 편집 중인 항목을 읽기전용으로 고정
|
||||||
if (editablePhoneIndex >= 0) {
|
if (editablePhoneIndex >= 0) {
|
||||||
|
const currentPhone = newPhones[editablePhoneIndex];
|
||||||
|
|
||||||
|
// 유효성 검사: 값이 있고, 형식이 올바르고, 중복이 아닐 때만 readOnly로 변경
|
||||||
|
if (currentPhone && currentPhone.trim() && isValidPhone(currentPhone)) {
|
||||||
|
// 중복 검사 - trim하여 비교
|
||||||
|
const trimmedPhone = currentPhone.trim();
|
||||||
|
const allPhones = [
|
||||||
|
...(authMethodData?.phones?.map(p => p.content?.trim()) || []),
|
||||||
|
...newPhones.map(p => p.trim()).filter((_, i) => i !== editablePhoneIndex)
|
||||||
|
].filter(p => p); // 빈 문자열 제거
|
||||||
|
|
||||||
|
if (!allPhones.includes(trimmedPhone)) {
|
||||||
setReadOnlyPhones(prev => new Set([...prev, editablePhoneIndex]));
|
setReadOnlyPhones(prev => new Set([...prev, editablePhoneIndex]));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 새로운 편집 가능한 항목 추가
|
// 새로운 편집 가능한 항목 추가
|
||||||
setEditablePhoneIndex(newPhones.length);
|
setEditablePhoneIndex(newPhones.length);
|
||||||
@@ -193,43 +221,112 @@ export const UserLoginAuthInfoWrap = ({
|
|||||||
return phoneRegex.test(phone);
|
return phoneRegex.test(phone);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 이메일 오류 메시지 반환
|
||||||
|
const getEmailError = (index: number, email: string): string | null => {
|
||||||
|
// 값이 없으면 오류 표시 안 함
|
||||||
|
if (!email || !email.trim()) return null;
|
||||||
|
|
||||||
|
// 형식 검증
|
||||||
|
if (!isValidEmail(email)) {
|
||||||
|
return t('account.invalidEmailFormat') || '이메일 형식이 올바르지 않습니다.';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 중복 검증
|
||||||
|
const allEmails = [
|
||||||
|
...(authMethodData?.emails?.map(e => e.content) || []),
|
||||||
|
...newEmails
|
||||||
|
];
|
||||||
|
const currentEmailWithoutSelf = allEmails.filter((e, i) => {
|
||||||
|
const emailIndex = i - (authMethodData?.emails?.length || 0);
|
||||||
|
return e.trim() && emailIndex !== index;
|
||||||
|
});
|
||||||
|
if (currentEmailWithoutSelf.includes(email)) {
|
||||||
|
return t('account.duplicateEmail') || '중복된 이메일입니다.';
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 전화번호 오류 메시지 반환
|
||||||
|
const getPhoneError = (index: number, phone: string): string | null => {
|
||||||
|
// 값이 없으면 오류 표시 안 함
|
||||||
|
if (!phone || !phone.trim()) return null;
|
||||||
|
|
||||||
|
// 형식 검증
|
||||||
|
if (!isValidPhone(phone)) {
|
||||||
|
return t('account.invalidPhoneFormat') || '전화번호 형식이 올바르지 않습니다. (010으로 시작하는 11자리)';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 중복 검증
|
||||||
|
const allPhones = [
|
||||||
|
...(authMethodData?.phones?.map(p => p.content) || []),
|
||||||
|
...newPhones
|
||||||
|
];
|
||||||
|
const currentPhoneWithoutSelf = allPhones.filter((p, i) => {
|
||||||
|
const phoneIndex = i - (authMethodData?.phones?.length || 0);
|
||||||
|
return p.trim() && phoneIndex !== index;
|
||||||
|
});
|
||||||
|
if (currentPhoneWithoutSelf.includes(phone)) {
|
||||||
|
return t('account.duplicatePhone') || '중복된 전화번호입니다.';
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 전체 validation 오류 확인 (이메일 + 전화번호)
|
||||||
|
const hasAnyValidationError = () => {
|
||||||
|
// 이메일 오류 확인
|
||||||
|
const hasEmailError = newEmails.some((email, index) => {
|
||||||
|
if (!email || !email.trim()) return false;
|
||||||
|
return getEmailError(index, email) !== null;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 전화번호 오류 확인
|
||||||
|
const hasPhoneError = newPhones.some((phone, index) => {
|
||||||
|
if (!phone || !phone.trim()) return false;
|
||||||
|
return getPhoneError(index, phone) !== null;
|
||||||
|
});
|
||||||
|
|
||||||
|
return hasEmailError || hasPhoneError;
|
||||||
|
};
|
||||||
|
|
||||||
// 이메일 추가 버튼 활성화 조건
|
// 이메일 추가 버튼 활성화 조건
|
||||||
const isEmailAddButtonEnabled = () => {
|
const isEmailAddButtonEnabled = () => {
|
||||||
|
// 이메일 또는 전화번호 입력 중이면 비활성화
|
||||||
|
if (editableEmailIndex >= 0 || editablePhoneIndex >= 0) return false;
|
||||||
|
|
||||||
if (newEmails.length === 0) return true; // 처음은 활성화
|
if (newEmails.length === 0) return true; // 처음은 활성화
|
||||||
|
|
||||||
const lastEmailIndex = newEmails.length - 1;
|
const lastEmailIndex = newEmails.length - 1;
|
||||||
const lastEmail = newEmails[lastEmailIndex];
|
const lastEmail = newEmails[lastEmailIndex];
|
||||||
|
|
||||||
// 마지막 항목이 편집 가능하고, 유효한 형식이며, 중복이 없으면 활성화
|
// 값이 없으면 비활성화
|
||||||
return lastEmailIndex >= editableEmailIndex &&
|
if (!lastEmail || !lastEmail.trim()) return false;
|
||||||
lastEmail &&
|
|
||||||
lastEmail.trim() &&
|
// 이메일 또는 전화번호에 validation 오류가 있으면 비활성화
|
||||||
isValidEmail(lastEmail) &&
|
return !hasAnyValidationError();
|
||||||
!hasDuplicateEmail();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 전화번호 추가 버튼 활성화 조건
|
// 전화번호 추가 버튼 활성화 조건
|
||||||
const isPhoneAddButtonEnabled = () => {
|
const isPhoneAddButtonEnabled = () => {
|
||||||
|
// 이메일 또는 전화번호 입력 중이면 비활성화
|
||||||
|
if (editableEmailIndex >= 0 || editablePhoneIndex >= 0) return false;
|
||||||
|
|
||||||
if (newPhones.length === 0) return true; // 처음은 활성화
|
if (newPhones.length === 0) return true; // 처음은 활성화
|
||||||
|
|
||||||
const lastPhoneIndex = newPhones.length - 1;
|
const lastPhoneIndex = newPhones.length - 1;
|
||||||
const lastPhone = newPhones[lastPhoneIndex];
|
const lastPhone = newPhones[lastPhoneIndex];
|
||||||
|
|
||||||
// 마지막 항목이 편집 가능하고, 유효한 형식이며, 중복이 없으면 활성화
|
// 값이 없으면 비활성화
|
||||||
return lastPhoneIndex >= editablePhoneIndex &&
|
if (!lastPhone || !lastPhone.trim()) return false;
|
||||||
lastPhone &&
|
|
||||||
lastPhone.trim() &&
|
// 이메일 또는 전화번호에 validation 오류가 있으면 비활성화
|
||||||
isValidPhone(lastPhone) &&
|
return !hasAnyValidationError();
|
||||||
!hasDuplicatePhone();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 삭제 버튼 활성화 조건 (전체 항목이 1개만 남으면 비활성화)
|
// 삭제 버튼 활성화 조건
|
||||||
const isDeleteButtonEnabled = () => {
|
const isDeleteButtonEnabled = () => {
|
||||||
const totalEmailCount = (authMethodData?.emails?.length || 0) + newEmails.length;
|
return true; // 항상 활성화
|
||||||
const totalPhoneCount = (authMethodData?.phones?.length || 0) + newPhones.length;
|
|
||||||
const totalCount = totalEmailCount + totalPhoneCount;
|
|
||||||
|
|
||||||
return totalCount > 1;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 중복 이메일 검증
|
// 중복 이메일 검증
|
||||||
@@ -442,13 +539,25 @@ export const UserLoginAuthInfoWrap = ({
|
|||||||
value={displayValue}
|
value={displayValue}
|
||||||
placeholder="example@domain.com"
|
placeholder="example@domain.com"
|
||||||
onChange={(e) => handleNewEmailChange(index, e.target.value)}
|
onChange={(e) => handleNewEmailChange(index, e.target.value)}
|
||||||
onFocus={() => setEditableEmailIndex(index)}
|
onFocus={(e) => {
|
||||||
onBlur={() => {
|
if (isReadOnly) {
|
||||||
if (email && isValidEmail(email)) {
|
e.target.blur();
|
||||||
setEditableEmailIndex(-1);
|
} else {
|
||||||
|
setEditableEmailIndex(index);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
onBlur={() => {
|
||||||
|
// 값이 없거나 공백만 있으면 입력란 제거
|
||||||
|
if (!email || !email.trim()) {
|
||||||
|
handleRemoveNewEmail(index);
|
||||||
|
} else if (!getEmailError(index, email)) {
|
||||||
|
// validation 오류가 없을 때만 편집 모드 해제
|
||||||
|
setEditableEmailIndex(-1);
|
||||||
|
}
|
||||||
|
// validation 오류가 있으면 편집 모드 유지 (편집 가능 상태)
|
||||||
|
}}
|
||||||
readOnly={isReadOnly}
|
readOnly={isReadOnly}
|
||||||
|
style={isReadOnly ? { cursor: 'default' } : undefined}
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
className="icon-btn minus"
|
className="icon-btn minus"
|
||||||
@@ -460,6 +569,12 @@ export const UserLoginAuthInfoWrap = ({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
{(() => {
|
||||||
|
const firstError = newEmails.map((email, index) => getEmailError(index, email)).find(error => error);
|
||||||
|
return firstError ? (
|
||||||
|
<div className="error-message"><p>{firstError}</p></div>
|
||||||
|
) : null;
|
||||||
|
})()}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="group">
|
<div className="group">
|
||||||
@@ -501,13 +616,25 @@ export const UserLoginAuthInfoWrap = ({
|
|||||||
value={displayValue}
|
value={displayValue}
|
||||||
placeholder={t('account.enterPhoneNumber')}
|
placeholder={t('account.enterPhoneNumber')}
|
||||||
onChange={(e) => handleNewPhoneChange(index, e.target.value)}
|
onChange={(e) => handleNewPhoneChange(index, e.target.value)}
|
||||||
onFocus={() => setEditablePhoneIndex(index)}
|
onFocus={(e) => {
|
||||||
onBlur={() => {
|
if (isReadOnly) {
|
||||||
if (phone && isValidPhone(phone)) {
|
e.target.blur();
|
||||||
setEditablePhoneIndex(-1);
|
} else {
|
||||||
|
setEditablePhoneIndex(index);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
onBlur={() => {
|
||||||
|
// 값이 없거나 공백만 있으면 입력란 제거
|
||||||
|
if (!phone || !phone.trim()) {
|
||||||
|
handleRemoveNewPhone(index);
|
||||||
|
} else if (!getPhoneError(index, phone)) {
|
||||||
|
// validation 오류가 없을 때만 편집 모드 해제
|
||||||
|
setEditablePhoneIndex(-1);
|
||||||
|
}
|
||||||
|
// validation 오류가 있으면 편집 모드 유지 (편집 가능 상태)
|
||||||
|
}}
|
||||||
readOnly={isReadOnly}
|
readOnly={isReadOnly}
|
||||||
|
style={isReadOnly ? { cursor: 'default' } : undefined}
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
className="icon-btn minus"
|
className="icon-btn minus"
|
||||||
@@ -519,6 +646,12 @@ export const UserLoginAuthInfoWrap = ({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
{(() => {
|
||||||
|
const firstError = newPhones.map((phone, index) => getPhoneError(index, phone)).find(error => error);
|
||||||
|
return firstError ? (
|
||||||
|
<div className="error-message"><p>{firstError}</p></div>
|
||||||
|
) : null;
|
||||||
|
})()}
|
||||||
<div className="notice-bar">{t('account.tabChangeResetNotice')}</div>
|
<div className="notice-bar">{t('account.tabChangeResetNotice')}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -368,6 +368,10 @@
|
|||||||
"passwordNoRepeatingCharacters": "Password cannot contain the same character 3 times in a row.",
|
"passwordNoRepeatingCharacters": "Password cannot contain the same character 3 times in a row.",
|
||||||
"passwordNoConsecutiveSequence": "Password cannot contain 3 or more consecutive characters or numbers.",
|
"passwordNoConsecutiveSequence": "Password cannot contain 3 or more consecutive characters or numbers.",
|
||||||
"passwordNoKeyboardSequence": "Password cannot contain 3 consecutive characters from keyboard layout.",
|
"passwordNoKeyboardSequence": "Password cannot contain 3 consecutive characters from keyboard layout.",
|
||||||
|
"invalidEmailFormat": "Invalid email format.",
|
||||||
|
"duplicateEmail": "Duplicate email address.",
|
||||||
|
"invalidPhoneFormat": "Invalid phone number format. (11 digits starting with 010)",
|
||||||
|
"duplicatePhone": "Duplicate phone number.",
|
||||||
"errors": {
|
"errors": {
|
||||||
"invalidPassword": "Incorrect password.",
|
"invalidPassword": "Incorrect password.",
|
||||||
"updatePasswordFail": "Password update failed.",
|
"updatePasswordFail": "Password update failed.",
|
||||||
|
|||||||
@@ -368,6 +368,10 @@
|
|||||||
"passwordNoRepeatingCharacters": "동일한 문자를 3번 연속으로 사용할 수 없습니다.",
|
"passwordNoRepeatingCharacters": "동일한 문자를 3번 연속으로 사용할 수 없습니다.",
|
||||||
"passwordNoConsecutiveSequence": "연속된 문자 또는 숫자를 3자리 이상 사용할 수 없습니다.",
|
"passwordNoConsecutiveSequence": "연속된 문자 또는 숫자를 3자리 이상 사용할 수 없습니다.",
|
||||||
"passwordNoKeyboardSequence": "키보드 배열 순서로 3자리 연속 사용할 수 없습니다.",
|
"passwordNoKeyboardSequence": "키보드 배열 순서로 3자리 연속 사용할 수 없습니다.",
|
||||||
|
"invalidEmailFormat": "이메일 형식이 올바르지 않습니다.",
|
||||||
|
"duplicateEmail": "중복된 이메일입니다.",
|
||||||
|
"invalidPhoneFormat": "전화번호 형식이 올바르지 않습니다. (010으로 시작하는 11자리)",
|
||||||
|
"duplicatePhone": "중복된 전화번호입니다.",
|
||||||
"errors": {
|
"errors": {
|
||||||
"invalidPassword": "패스워드가 틀립니다.",
|
"invalidPassword": "패스워드가 틀립니다.",
|
||||||
"updatePasswordFail": "패스워드 업데이트 실패입니다.",
|
"updatePasswordFail": "패스워드 업데이트 실패입니다.",
|
||||||
|
|||||||
@@ -110,8 +110,19 @@ export const UserAddAccountPage = () => {
|
|||||||
const handleAddEmail = () => {
|
const handleAddEmail = () => {
|
||||||
// 현재 편집 중인 항목을 읽기전용으로 고정
|
// 현재 편집 중인 항목을 읽기전용으로 고정
|
||||||
if (editableEmailIndex >= 0) {
|
if (editableEmailIndex >= 0) {
|
||||||
|
const currentEmail = newEmails[editableEmailIndex];
|
||||||
|
|
||||||
|
// 유효성 검사: 값이 있고, 형식이 올바르고, 중복이 아닐 때만 readOnly로 변경
|
||||||
|
if (currentEmail && currentEmail.trim() && isValidEmail(currentEmail)) {
|
||||||
|
// 중복 검사 - trim하여 비교
|
||||||
|
const trimmedEmail = currentEmail.trim();
|
||||||
|
const allEmails = newEmails.map(e => e.trim()).filter((_, i) => i !== editableEmailIndex).filter(e => e);
|
||||||
|
|
||||||
|
if (!allEmails.includes(trimmedEmail)) {
|
||||||
setReadOnlyEmails(prev => new Set([...prev, editableEmailIndex]));
|
setReadOnlyEmails(prev => new Set([...prev, editableEmailIndex]));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 새로운 편집 가능한 항목 추가
|
// 새로운 편집 가능한 항목 추가
|
||||||
setEditableEmailIndex(newEmails.length);
|
setEditableEmailIndex(newEmails.length);
|
||||||
@@ -121,8 +132,19 @@ export const UserAddAccountPage = () => {
|
|||||||
const handleAddPhone = () => {
|
const handleAddPhone = () => {
|
||||||
// 현재 편집 중인 항목을 읽기전용으로 고정
|
// 현재 편집 중인 항목을 읽기전용으로 고정
|
||||||
if (editablePhoneIndex >= 0) {
|
if (editablePhoneIndex >= 0) {
|
||||||
|
const currentPhone = newPhones[editablePhoneIndex];
|
||||||
|
|
||||||
|
// 유효성 검사: 값이 있고, 형식이 올바르고, 중복이 아닐 때만 readOnly로 변경
|
||||||
|
if (currentPhone && currentPhone.trim() && isValidPhone(currentPhone)) {
|
||||||
|
// 중복 검사 - trim하여 비교
|
||||||
|
const trimmedPhone = currentPhone.trim();
|
||||||
|
const allPhones = newPhones.map(p => p.trim()).filter((_, i) => i !== editablePhoneIndex).filter(p => p);
|
||||||
|
|
||||||
|
if (!allPhones.includes(trimmedPhone)) {
|
||||||
setReadOnlyPhones(prev => new Set([...prev, editablePhoneIndex]));
|
setReadOnlyPhones(prev => new Set([...prev, editablePhoneIndex]));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 새로운 편집 가능한 항목 추가
|
// 새로운 편집 가능한 항목 추가
|
||||||
setEditablePhoneIndex(newPhones.length);
|
setEditablePhoneIndex(newPhones.length);
|
||||||
@@ -251,32 +273,99 @@ export const UserAddAccountPage = () => {
|
|||||||
return phoneRegex.test(phone);
|
return phoneRegex.test(phone);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 이메일 오류 메시지 반환
|
||||||
|
const getEmailError = (index: number, email: string): string | null => {
|
||||||
|
// 값이 없으면 오류 표시 안 함
|
||||||
|
if (!email || !email.trim()) return null;
|
||||||
|
|
||||||
|
// 형식 검증
|
||||||
|
if (!isValidEmail(email)) {
|
||||||
|
return t('account.invalidEmailFormat') || '이메일 형식이 올바르지 않습니다.';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 중복 검증
|
||||||
|
const allEmails = newEmails;
|
||||||
|
const currentEmailWithoutSelf = allEmails.filter((e, i) => {
|
||||||
|
return e.trim() && i !== index;
|
||||||
|
});
|
||||||
|
if (currentEmailWithoutSelf.includes(email)) {
|
||||||
|
return t('account.duplicateEmail') || '중복된 이메일입니다.';
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 전화번호 오류 메시지 반환
|
||||||
|
const getPhoneError = (index: number, phone: string): string | null => {
|
||||||
|
// 값이 없으면 오류 표시 안 함
|
||||||
|
if (!phone || !phone.trim()) return null;
|
||||||
|
|
||||||
|
// 형식 검증
|
||||||
|
if (!isValidPhone(phone)) {
|
||||||
|
return t('account.invalidPhoneFormat') || '전화번호 형식이 올바르지 않습니다. (010으로 시작하는 11자리)';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 중복 검증
|
||||||
|
const allPhones = newPhones;
|
||||||
|
const currentPhoneWithoutSelf = allPhones.filter((p, i) => {
|
||||||
|
return p.trim() && i !== index;
|
||||||
|
});
|
||||||
|
if (currentPhoneWithoutSelf.includes(phone)) {
|
||||||
|
return t('account.duplicatePhone') || '중복된 전화번호입니다.';
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 전체 validation 오류 확인 (이메일 + 전화번호)
|
||||||
|
const hasAnyValidationError = () => {
|
||||||
|
// 이메일 오류 확인
|
||||||
|
const hasEmailError = newEmails.some((email, index) => {
|
||||||
|
if (!email || !email.trim()) return false;
|
||||||
|
return getEmailError(index, email) !== null;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 전화번호 오류 확인
|
||||||
|
const hasPhoneError = newPhones.some((phone, index) => {
|
||||||
|
if (!phone || !phone.trim()) return false;
|
||||||
|
return getPhoneError(index, phone) !== null;
|
||||||
|
});
|
||||||
|
|
||||||
|
return hasEmailError || hasPhoneError;
|
||||||
|
};
|
||||||
|
|
||||||
// 이메일 추가 버튼 활성화 조건
|
// 이메일 추가 버튼 활성화 조건
|
||||||
const isEmailAddButtonEnabled = () => {
|
const isEmailAddButtonEnabled = () => {
|
||||||
if (newEmails.length === 0) return true;
|
// 이메일 또는 전화번호 입력 중이면 비활성화
|
||||||
|
if (editableEmailIndex >= 0 || editablePhoneIndex >= 0) return false;
|
||||||
|
|
||||||
|
if (newEmails.length === 0) return true; // 처음은 활성화
|
||||||
|
|
||||||
const lastEmailIndex = newEmails.length - 1;
|
const lastEmailIndex = newEmails.length - 1;
|
||||||
const lastEmail = newEmails[lastEmailIndex];
|
const lastEmail = newEmails[lastEmailIndex];
|
||||||
|
|
||||||
return lastEmailIndex >= editableEmailIndex &&
|
// 값이 없으면 비활성화
|
||||||
lastEmail &&
|
if (!lastEmail || !lastEmail.trim()) return false;
|
||||||
lastEmail.trim() &&
|
|
||||||
isValidEmail(lastEmail) &&
|
// 이메일 또는 전화번호에 validation 오류가 있으면 비활성화
|
||||||
!hasDuplicateEmail();
|
return !hasAnyValidationError();
|
||||||
};
|
};
|
||||||
|
|
||||||
// 전화번호 추가 버튼 활성화 조건
|
// 전화번호 추가 버튼 활성화 조건
|
||||||
const isPhoneAddButtonEnabled = () => {
|
const isPhoneAddButtonEnabled = () => {
|
||||||
if (newPhones.length === 0) return true;
|
// 이메일 또는 전화번호 입력 중이면 비활성화
|
||||||
|
if (editableEmailIndex >= 0 || editablePhoneIndex >= 0) return false;
|
||||||
|
|
||||||
|
if (newPhones.length === 0) return true; // 처음은 활성화
|
||||||
|
|
||||||
const lastPhoneIndex = newPhones.length - 1;
|
const lastPhoneIndex = newPhones.length - 1;
|
||||||
const lastPhone = newPhones[lastPhoneIndex];
|
const lastPhone = newPhones[lastPhoneIndex];
|
||||||
|
|
||||||
return lastPhoneIndex >= editablePhoneIndex &&
|
// 값이 없으면 비활성화
|
||||||
lastPhone &&
|
if (!lastPhone || !lastPhone.trim()) return false;
|
||||||
lastPhone.trim() &&
|
|
||||||
isValidPhone(lastPhone) &&
|
// 이메일 또는 전화번호에 validation 오류가 있으면 비활성화
|
||||||
!hasDuplicatePhone();
|
return !hasAnyValidationError();
|
||||||
};
|
};
|
||||||
|
|
||||||
// 중복 검증
|
// 중복 검증
|
||||||
@@ -294,8 +383,7 @@ export const UserAddAccountPage = () => {
|
|||||||
|
|
||||||
// 삭제 버튼 활성화 조건
|
// 삭제 버튼 활성화 조건
|
||||||
const isDeleteButtonEnabled = () => {
|
const isDeleteButtonEnabled = () => {
|
||||||
const totalCount = newEmails.length + newPhones.length;
|
return true; // 항상 활성화
|
||||||
return totalCount > 1;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 저장 버튼 활성화 조건 체크
|
// 저장 버튼 활성화 조건 체크
|
||||||
@@ -562,9 +650,14 @@ export const UserAddAccountPage = () => {
|
|||||||
setEditableEmailIndex(index);
|
setEditableEmailIndex(index);
|
||||||
}}
|
}}
|
||||||
onBlur={() => {
|
onBlur={() => {
|
||||||
if (email && isValidEmail(email)) {
|
// 값이 없거나 공백만 있으면 입력란 제거
|
||||||
|
if (!email || !email.trim()) {
|
||||||
|
handleRemoveNewEmail(index);
|
||||||
|
} else if (!getEmailError(index, email)) {
|
||||||
|
// validation 오류가 없을 때만 편집 모드 해제
|
||||||
setEditableEmailIndex(-1);
|
setEditableEmailIndex(-1);
|
||||||
}
|
}
|
||||||
|
// validation 오류가 있으면 편집 모드 유지 (편집 가능 상태)
|
||||||
}}
|
}}
|
||||||
readOnly={isReadOnly}
|
readOnly={isReadOnly}
|
||||||
/>
|
/>
|
||||||
@@ -578,6 +671,12 @@ export const UserAddAccountPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
{(() => {
|
||||||
|
const firstError = newEmails.map((email, index) => getEmailError(index, email)).find(error => error);
|
||||||
|
return firstError ? (
|
||||||
|
<div className="error-message"><p>{firstError}</p></div>
|
||||||
|
) : null;
|
||||||
|
})()}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="ua-group">
|
<div className="ua-group">
|
||||||
@@ -610,9 +709,14 @@ export const UserAddAccountPage = () => {
|
|||||||
setEditablePhoneIndex(index);
|
setEditablePhoneIndex(index);
|
||||||
}}
|
}}
|
||||||
onBlur={() => {
|
onBlur={() => {
|
||||||
if (phone && isValidPhone(phone)) {
|
// 값이 없거나 공백만 있으면 입력란 제거
|
||||||
|
if (!phone || !phone.trim()) {
|
||||||
|
handleRemoveNewPhone(index);
|
||||||
|
} else if (!getPhoneError(index, phone)) {
|
||||||
|
// validation 오류가 없을 때만 편집 모드 해제
|
||||||
setEditablePhoneIndex(-1);
|
setEditablePhoneIndex(-1);
|
||||||
}
|
}
|
||||||
|
// validation 오류가 있으면 편집 모드 유지 (편집 가능 상태)
|
||||||
}}
|
}}
|
||||||
readOnly={isReadOnly}
|
readOnly={isReadOnly}
|
||||||
/>
|
/>
|
||||||
@@ -626,6 +730,12 @@ export const UserAddAccountPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
{(() => {
|
||||||
|
const firstError = newPhones.map((phone, index) => getPhoneError(index, phone)).find(error => error);
|
||||||
|
return firstError ? (
|
||||||
|
<div className="error-message" style={{ marginTop: '10px' }}><p>{firstError}</p></div>
|
||||||
|
) : null;
|
||||||
|
})()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user