File name
Commit message
Commit date
File name
Commit message
Commit date
09-03
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
'use client';
import {
useActionState,
useEffect,
useRef,
useState,
useTransition,
} from 'react';
import { FoxButton } from '@fox/core/components/fox-button';
import { FoxButtonGroup } from '@fox/core/components/fox-button-group';
import { FoxEmail } from '@fox/core/components/fox-email';
import { FoxInput } from '@fox/core/components/fox-input';
import { FoxModal } from '@fox/core/components/fox-modal';
import {
foxPasswordValidator,
foxValidationMessage,
foxValidators,
} from '@fox/core/validation';
import { useFeedback } from '@/app/_hooks/use-feedback';
import { DEFAULT_ADMIN_ROLE_CODE } from '@/lib/domain/admin-member';
import { DEFAULT_MOBILE_PHONE_PREFIX } from '@/lib/domain/phone-number';
import {
ADMIN_EMAIL_MESSAGES,
ADMIN_LOGIN_ID_HELP_TEXT,
ADMIN_LOGIN_ID_MESSAGES,
ADMIN_LOGIN_ID_VALIDATORS,
ADMIN_PASSWORD_ERROR_TEXT,
ADMIN_PASSWORD_HELP_TEXT,
ADMIN_PASSWORD_POLICY,
INITIAL_ADMIN_MEMBER_FORM_STATE,
adminEmailValidators,
} from '@/lib/domain/admin-member-form';
import {
checkAdminLoginId,
createAdminMemberAction,
type LoginIdCheckResult,
} from '../_actions';
import { AdminMemberFormFields } from './admin-member-form-fields';
import styles from './admin-member-modal.module.scss';
import { submitFormAction } from '@/app/_hooks/submit-form-action';
interface AdminMemberCreateModalProps {
onClose: () => void;
}
const EMAIL_DOMAIN_OPTIONS = [
'naver.com',
'gmail.com',
'daum.net',
'hanmail.net',
'nate.com',
];
export function AdminMemberCreateModal({
onClose,
}: AdminMemberCreateModalProps) {
const { showToast } = useFeedback();
const [state, formAction, isPending] = useActionState(
createAdminMemberAction,
INITIAL_ADMIN_MEMBER_FORM_STATE
);
const formRef = useRef<HTMLFormElement>(null);
const [loginId, setLoginId] = useState('');
const [checkResult, setCheckResult] = useState<LoginIdCheckResult>({
status: 'idle',
});
const [isChecking, startChecking] = useTransition();
const [phoneNumber, setPhoneNumber] = useState(DEFAULT_MOBILE_PHONE_PREFIX);
const [emailValue, setEmailValue] = useState(`@${EMAIL_DOMAIN_OPTIONS[0]}`);
const [isEmailTouched, setIsEmailTouched] = useState(false);
const [roleCode, setRoleCode] = useState<string>(DEFAULT_ADMIN_ROLE_CODE);
useEffect(() => {
if (state.status === 'success') {
showToast({ variant: 'success', message: '관리자를 등록했습니다.' });
onClose();
}
}, [state, showToast, onClose]);
const isLoginIdVerified =
checkResult.status === 'available' && checkResult.loginId === loginId.trim();
const errors = state.status === 'error' ? (state.errors ?? {}) : {};
const [emailLocal = '', emailDomain = ''] = emailValue.split('@');
const email = emailLocal && emailDomain ? emailValue : '';
const emailMessage =
errors.email ??
(isEmailTouched
? foxValidationMessage(
foxValidators.compose(adminEmailValidators(true))(email),
ADMIN_EMAIL_MESSAGES
)
: undefined);
const loginIdFailure =
checkResult.status === 'unavailable' || checkResult.status === 'failed'
? checkResult.message
: undefined;
const loginIdMessage =
loginIdFailure ??
errors.loginId ??
(isLoginIdVerified ? '사용할 수 있는 ID입니다.' : ADMIN_LOGIN_ID_HELP_TEXT);
return (
<FoxModal
dismissible={false}
open
onClose={onClose}
size="sm"
title="관리자 등록"
actions={
<FoxButtonGroup size="md">
<FoxButton type="default" size="md" label="취소" onAction={onClose} />
<FoxButton
type="primary"
size="md"
label="저장"
loading={isPending}
disabled={!isLoginIdVerified}
onAction={() => formRef.current?.requestSubmit()}
/>
</FoxButtonGroup>
}
>
<form ref={formRef} onSubmit={(event) => submitFormAction(event, formAction)} className={styles.formList}>
<FoxInput
size="md"
name="name"
label="이름"
requirement="required"
placeholder="이름을 입력하세요."
message={errors.name}
invalid={Boolean(errors.name)}
/>
<div className={styles.fieldWithAction}>
<FoxInput
size="md"
name="loginId"
label="ID"
requirement="required"
placeholder="관리자 ID를 입력하세요."
autoComplete="off"
value={loginId}
onChange={setLoginId}
message={loginIdMessage}
messageIcon={<i className="fox-ico fox-ico-ChatCenteredDots" aria-hidden="true" />}
invalid={Boolean(loginIdFailure) || Boolean(errors.loginId)}
validators={ADMIN_LOGIN_ID_VALIDATORS}
validationMessages={ADMIN_LOGIN_ID_MESSAGES}
/>
<FoxButton
type="secondary"
size="md"
fullWidth
label="중복 확인"
loading={isChecking}
disabled={loginId.trim().length === 0}
onAction={() =>
startChecking(async () => {
try {
setCheckResult(await checkAdminLoginId(loginId));
} catch {
setCheckResult({
status: 'failed',
message: '중복 확인에 실패했습니다. 잠시 후 다시 시도해 주세요.',
});
}
})
}
/>
</div>
<FoxInput
size="md"
type="password"
name="password"
label="비밀번호"
requirement="required"
placeholder="비밀번호를 입력하세요."
autoComplete="new-password"
message={errors.password ?? ADMIN_PASSWORD_HELP_TEXT}
messageIcon={<i className="fox-ico fox-ico-ChatCenteredDots" aria-hidden="true" />}
invalid={Boolean(errors.password)}
validators={[foxPasswordValidator({ ...ADMIN_PASSWORD_POLICY, required: true })]}
validationMessages={{ minlength: ADMIN_PASSWORD_ERROR_TEXT }}
/>
<AdminMemberFormFields
phoneNumber={phoneNumber}
onPhoneNumberChange={setPhoneNumber}
roleCode={roleCode}
onRoleCodeChange={setRoleCode}
errors={errors}
emailField={
<>
<FoxEmail
label="이메일"
requirement="required"
domainOptions={[...EMAIL_DOMAIN_OPTIONS]}
localPlaceholder="ID"
value={emailValue}
onChange={(next) => {
setEmailValue(next);
setIsEmailTouched(true);
}}
message={emailMessage}
invalid={Boolean(emailMessage)}
/>
<input type="hidden" name="email" value={email} />
</>
}
/>
{state.status === 'error' && state.message && (
<p role="alert">{state.message}</p>
)}
</form>
</FoxModal>
);
}