File name
Commit message
Commit date
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, useId, useState } from 'react';
import { FoxButton } from '@fox/core/components/fox-button';
import { FoxInput } from '@fox/core/components/fox-input';
import { FoxEyeIcon, FoxEyeSlashIcon } from '@fox/core/icons';
import { login, type LoginFormState } from '../_actions';
import styles from '../login.module.scss';
const initialState: LoginFormState = {};
/** 시안의 안내 문구. 오류가 없을 때 이 자리에 보인다. */
const HELPER_TEXT = '비밀번호 5회 불일치 시 계정 로그인이 차단됩니다.';
/**
* 로그인 폼 — 시안 login-area(5432:16137)의 제목 아래 부분이다. 입력·라벨·버튼은 시안이
* 디자인시스템 컴포넌트를 그대로 쓰고 있어 FoxInput/FoxButton으로 덮인다(치수·서체가 시안과
* 같다). `label`을 주면 FoxInput이 FoxFormLabel까지 함께 그린다.
*
* 비밀번호 보기 토글은 로그인 화면 전용이다 — FoxInput에 그 기능이 없고, `icon` 슬롯은
* `aria-hidden="true"`라 버튼을 넣으면 보조기술에서 사라진다(사용자 확정). 그래서 슬롯 대신
* 같은 자리에 버튼을 겹쳐 둔다.
*
* 오류 문구는 FoxInput의 `message`가 아니라 시안의 안내문 자리에 함께 둔다 — 어느 칸이
* 틀렸는지 알려주지 않는 것이 보안상 맞고(계정 존재 여부가 새지 않는다), 입력칸 아래에
* 메시지가 끼면 보기 토글의 세로 위치도 어긋난다.
*/
export function LoginForm() {
const [state, formAction, isPending] = useActionState(login, initialState);
const [isPasswordVisible, setIsPasswordVisible] = useState(false);
const passwordId = useId();
return (
<form action={formAction} className={styles.fields}>
<FoxInput
size="lg"
label="ID"
name="loginId"
type="text"
autoComplete="username"
placeholder="아이디를 입력하세요."
required
state={state.error ? 'error' : 'default'}
/>
<div className={styles.passwordField}>
<FoxInput
id={passwordId}
size="lg"
label="PW"
name="password"
type={isPasswordVisible ? 'text' : 'password'}
autoComplete="current-password"
placeholder="비밀번호를 입력하세요."
required
state={state.error ? 'error' : 'default'}
/>
<button
type="button"
className={styles.passwordToggle}
aria-controls={passwordId}
aria-pressed={isPasswordVisible}
aria-label={isPasswordVisible ? '비밀번호 숨기기' : '비밀번호 보기'}
onClick={() => setIsPasswordVisible((visible) => !visible)}
>
{isPasswordVisible ? (
<FoxEyeSlashIcon weight="bold" />
) : (
<FoxEyeIcon weight="bold" />
)}
</button>
</div>
<p className={`${styles.helper} ${state.error ? styles.error : ''}`}>
{state.error ?? HELPER_TEXT}
</p>
<FoxButton
type="primary"
size="lg"
htmlType="submit"
fullWidth
label={isPending ? '로그인 중...' : '로그인'}
disabled={isPending}
className={styles.submit}
/>
</form>
);
}