File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
import {
foxValidationMessage,
foxValidators,
type FoxValidationMessages,
type FoxValidator,
} from '@fox/core/validation';
import {
ADMIN_ROLE_OPTIONS,
type AdminRoleCode,
} from '@/lib/domain/admin-member';
export const ADMIN_LOGIN_ID_HELP_TEXT =
'영어 소문자, 숫자를 조합하여 입력 후 중복여부를 확인하세요.';
export const ADMIN_PASSWORD_HELP_TEXT =
'영어, 숫자, 특수문자 중 2종류 이상 조합, 최소 10자리 이상';
export const ADMIN_PASSWORD_ERROR_TEXT =
'비밀번호가 규칙에 맞지 않습니다. 영어·숫자·특수문자 중 2종류 이상으로 10자리 이상 입력해 주세요.';
const LOGIN_ID_MIN_LENGTH = 4;
const LOGIN_ID_MAX_LENGTH = 20;
export const ADMIN_LOGIN_ID_VALIDATORS: FoxValidator[] = [
foxValidators.required,
foxValidators.minLength(LOGIN_ID_MIN_LENGTH),
foxValidators.maxLength(LOGIN_ID_MAX_LENGTH),
foxValidators.pattern(/^[a-z0-9]+$/),
foxValidators.characterKinds(2),
];
export const ADMIN_LOGIN_ID_MESSAGES: FoxValidationMessages = {
required: 'ID를 입력해 주세요.',
minlength: `ID는 ${LOGIN_ID_MIN_LENGTH}~${LOGIN_ID_MAX_LENGTH}자로 입력해 주세요.`,
maxlength: `ID는 ${LOGIN_ID_MIN_LENGTH}~${LOGIN_ID_MAX_LENGTH}자로 입력해 주세요.`,
pattern: ADMIN_LOGIN_ID_HELP_TEXT,
characterKinds: ADMIN_LOGIN_ID_HELP_TEXT,
};
export const ADMIN_PASSWORD_POLICY = {
minLength: 10,
kinds: 2,
} as const;
const NAME_MAX_LENGTH = 50;
const EMAIL_MAX_LENGTH = 100;
export function adminEmailValidators(required: boolean): FoxValidator[] {
return [
...(required ? [foxValidators.required] : []),
foxValidators.email,
foxValidators.maxLength(EMAIL_MAX_LENGTH),
];
}
export const ADMIN_EMAIL_MESSAGES: FoxValidationMessages = {
required: '이메일을 입력해 주세요.',
email: '이메일 형식이 올바르지 않습니다.',
maxlength: `이메일은 ${EMAIL_MAX_LENGTH}자 이내로 입력해 주세요.`,
};
const PHONE_DIGITS_PATTERN = /^(\d{3})(\d{3,4})(\d{4})$/;
export type AdminMemberEditableValues = {
password: string;
phoneNumber: string;
email: string;
roleCode: AdminRoleCode;
};
export type AdminMemberCreateValues = AdminMemberEditableValues & {
name: string;
loginId: string;
};
export type AdminMemberFormErrors = Partial<
Record<keyof AdminMemberCreateValues, string>
>;
export type ValidationResult<T> =
| { ok: true; values: T }
| { ok: false; errors: AdminMemberFormErrors };
export type AdminMemberFormState =
| { status: 'idle' }
| { status: 'error'; message?: string; errors?: AdminMemberFormErrors }
| { status: 'success' };
export const INITIAL_ADMIN_MEMBER_FORM_STATE: AdminMemberFormState = {
status: 'idle',
};
function countCharacterKinds(password: string): number {
const kinds = [/[a-zA-Z]/, /\d/, /[^a-zA-Z0-9]/];
return kinds.filter((pattern) => pattern.test(password)).length;
}
export function validateAdminLoginId(loginId: string): string | null {
const errors = foxValidators.compose(ADMIN_LOGIN_ID_VALIDATORS)(loginId.trim());
return foxValidationMessage(errors, ADMIN_LOGIN_ID_MESSAGES) ?? null;
}
export function formatPhoneNumber(digits: string): string | null {
const matched = PHONE_DIGITS_PATTERN.exec(digits.trim());
return matched === null ? null : `${matched[1]}-${matched[2]}-${matched[3]}`;
}
export function toPhoneDigits(phoneNumber: string | null): string {
return (phoneNumber ?? '').replace(/\D/g, '');
}
function validateEditableValues(
values: AdminMemberEditableValues,
errors: AdminMemberFormErrors,
options: { passwordRequired: boolean; emailRequired: boolean }
): AdminMemberEditableValues {
const password = values.password;
const phoneNumber = values.phoneNumber.trim();
const email = values.email.trim();
if (!password) {
if (options.passwordRequired) {
errors.password = ADMIN_PASSWORD_ERROR_TEXT;
}
} else if (
password.length < ADMIN_PASSWORD_POLICY.minLength ||
countCharacterKinds(password) < ADMIN_PASSWORD_POLICY.kinds
) {
errors.password = ADMIN_PASSWORD_ERROR_TEXT;
}
if (!phoneNumber) {
errors.phoneNumber = '휴대전화 번호를 정확히 입력해 주세요.';
}
const emailError = foxValidationMessage(
foxValidators.compose(adminEmailValidators(options.emailRequired))(email),
ADMIN_EMAIL_MESSAGES
);
if (emailError) {
errors.email = emailError;
}
if (!ADMIN_ROLE_OPTIONS.some((option) => option.value === values.roleCode)) {
errors.roleCode = '역할을 선택해 주세요.';
}
return { password, phoneNumber, email, roleCode: values.roleCode };
}
export function validateAdminMemberCreate(
values: AdminMemberCreateValues
): ValidationResult<AdminMemberCreateValues> {
const errors: AdminMemberFormErrors = {};
const name = values.name.trim();
if (!name) {
errors.name = '이름을 입력해 주세요.';
} else if (name.length > NAME_MAX_LENGTH) {
errors.name = `이름은 ${NAME_MAX_LENGTH}자 이내로 입력해 주세요.`;
}
const loginIdError = validateAdminLoginId(values.loginId);
if (loginIdError) {
errors.loginId = loginIdError;
}
const editable = validateEditableValues(values, errors, {
passwordRequired: true,
emailRequired: true,
});
if (Object.keys(errors).length > 0) {
return { ok: false, errors };
}
return {
ok: true,
values: { ...editable, name, loginId: values.loginId.trim() },
};
}
export function validateAdminMemberUpdate(
values: AdminMemberEditableValues
): ValidationResult<AdminMemberEditableValues> {
const errors: AdminMemberFormErrors = {};
const editable = validateEditableValues(values, errors, {
passwordRequired: false,
emailRequired: false,
});
if (Object.keys(errors).length > 0) {
return { ok: false, errors };
}
return { ok: true, values: editable };
}