import React, { useState } from 'react' import { useNavigate } from 'react-router-dom' import api from '../api/client' import { verify2fa, signup, findId, resetPasswordHelper } from '../api/uiws' type Helper = null | 'signup' | 'findId' | 'resetPw' export default function LoginPage() { const nav = useNavigate() const [form, setForm] = useState({ username: '', password: '' }) const [error, setError] = useState('') const [loading, setLoading] = useState(false) // UIWS 2FA — 1차 통과 후 코드 입력 단계 const [twofa, setTwofa] = useState<{ verifyToken: string; maskedEmail: string } | null>(null) const [code, setCode] = useState('') // 로그인 보조 3종 모달 const [helper, setHelper] = useState(null) const afterAuthenticated = async () => { const me = await api.get('/auth/me') localStorage.setItem('hrm_user', JSON.stringify(me.data.data)) nav('/dashboard') } const login = async (e: React.FormEvent) => { e.preventDefault() setLoading(true); setError('') try { const { data } = await api.post('/auth/login', form) const payload = data.data || {} if (payload.twofa === 'true') { // 2FA 활성 — 이메일 인증코드 입력 단계로 전환(access 토큰 미발급) setTwofa({ verifyToken: payload.verifyToken, maskedEmail: payload.maskedEmail || '' }) return } localStorage.setItem('hrm_token', payload.token) await afterAuthenticated() } catch (err: any) { const msg = err?.response?.data?.message || '' setError(msg.includes('잠겼') || msg.includes('승인') ? msg : '아이디 또는 비밀번호가 올바르지 않습니다.') } finally { setLoading(false) } } const verify = async (e: React.FormEvent) => { e.preventDefault() if (!twofa) return setLoading(true); setError('') try { const { data } = await verify2fa(twofa.verifyToken, code) localStorage.setItem('hrm_token', data.data.token) await afterAuthenticated() } catch { setError('인증 코드가 올바르지 않거나 만료되었습니다.') } finally { setLoading(false) } } return (

GUARDiA HRM

AI 기반 통합 인사관리 플랫폼

{!twofa ? (
setForm({ ...form, username: e.target.value })} className="w-full border border-slate-200 rounded-lg px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="admin" required />
setForm({ ...form, password: e.target.value })} className="w-full border border-slate-200 rounded-lg px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="••••••••" required />
{error &&

{error}

} {/* UIWS 로그인 보조 3종 */}
| |

GUARDiA HRM v1.0 | 온프레미스 AI 인사관리

) : (

2차 인증

{twofa.maskedEmail ? `${twofa.maskedEmail} 으로 발송된 인증 코드를 입력하세요.` : '이메일로 발송된 인증 코드를 입력하세요.'}

setCode(e.target.value)} className="w-full border border-slate-200 rounded-lg px-4 py-2.5 text-center tracking-widest text-lg focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="6자리 코드" maxLength={6} required /> {error &&

{error}

}
)}
{helper && setHelper(null)} />}
) } /** 로그인 보조 3종 모달 — 회원가입 / 아이디찾기 / 비밀번호 초기화. */ function HelperModal({ kind, onClose }: { kind: Exclude; onClose: () => void }) { const [f, setF] = useState({ username: '', password: '', displayName: '', email: '' }) const [msg, setMsg] = useState('') const [busy, setBusy] = useState(false) const set = (k: string, v: string) => setF({ ...f, [k]: v }) const TITLE: Record = { signup: '회원가입', findId: '아이디 찾기', resetPw: '비밀번호 초기화' } const submit = async (e: React.FormEvent) => { e.preventDefault() setBusy(true); setMsg('') try { if (kind === 'signup') { const { data } = await signup({ username: f.username, password: f.password, displayName: f.displayName, email: f.email }) setMsg(data.data?.message || '처리되었습니다.') } else if (kind === 'findId') { const { data } = await findId({ displayName: f.displayName, email: f.email }) setMsg(data.data?.found ? `회원님의 아이디: ${data.data.maskedUsername}` : '일치하는 계정을 찾을 수 없습니다.') } else { const { data } = await resetPasswordHelper({ username: f.username, email: f.email }) setMsg(data.data?.message || '처리되었습니다.') } } catch { setMsg('요청 처리 중 오류가 발생했습니다.') } finally { setBusy(false) } } const input = (k: string, ph: string, type = 'text') => ( set(k, e.target.value)} className="w-full border border-slate-200 rounded-lg px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder={ph} required /> ) return (
e.stopPropagation()} onSubmit={submit} className="bg-white rounded-2xl shadow-2xl p-7 w-full max-w-sm space-y-4">

{TITLE[kind]}

{kind === 'signup' && <> {input('username', '아이디')} {input('password', '비밀번호(4자 이상)', 'password')} {input('displayName', '이름')} {input('email', '이메일', 'email')} } {kind === 'findId' && <> {input('displayName', '이름')} {input('email', '이메일', 'email')} } {kind === 'resetPw' && <> {input('username', '아이디')} {input('email', '이메일', 'email')} } {msg &&

{msg}

}
) }