104 lines
3.3 KiB
TypeScript
104 lines
3.3 KiB
TypeScript
/**
|
||
* 로그인 모달 — 기본값 admin / admin
|
||
*/
|
||
import React, { useEffect, useState } from 'react';
|
||
import { loginUser, type LoginResponse } from '../utils/backendApi';
|
||
|
||
const DEFAULT_USERNAME = 'admin';
|
||
const DEFAULT_PASSWORD = 'admin';
|
||
|
||
interface Props {
|
||
open: boolean;
|
||
onClose: () => void;
|
||
onSuccess: (session: LoginResponse) => void;
|
||
}
|
||
|
||
const LoginModal: React.FC<Props> = ({ open, onClose, onSuccess }) => {
|
||
const [username, setUsername] = useState(DEFAULT_USERNAME);
|
||
const [password, setPassword] = useState(DEFAULT_PASSWORD);
|
||
const [error, setError] = useState<string | null>(null);
|
||
const [loading, setLoading] = useState(false);
|
||
|
||
useEffect(() => {
|
||
if (!open) return;
|
||
setUsername(DEFAULT_USERNAME);
|
||
setPassword(DEFAULT_PASSWORD);
|
||
setError(null);
|
||
setLoading(false);
|
||
}, [open]);
|
||
|
||
if (!open) return null;
|
||
|
||
const submit = async (e: React.FormEvent) => {
|
||
e.preventDefault();
|
||
setError(null);
|
||
setLoading(true);
|
||
try {
|
||
const res = await loginUser(username.trim(), password);
|
||
if (!res) {
|
||
setError('로그인에 실패했습니다. 아이디와 비밀번호를 확인하세요.');
|
||
return;
|
||
}
|
||
onSuccess(res);
|
||
onClose();
|
||
} catch (err) {
|
||
setError(err instanceof Error ? err.message : '로그인에 실패했습니다.');
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
return (
|
||
<div className="login-modal-backdrop" onClick={onClose}>
|
||
<div
|
||
className="login-modal"
|
||
role="dialog"
|
||
aria-labelledby="login-modal-title"
|
||
onClick={e => e.stopPropagation()}
|
||
>
|
||
<div className="login-modal-header">
|
||
<h2 id="login-modal-title">로그인</h2>
|
||
<button type="button" className="login-modal-close" onClick={onClose} aria-label="닫기">×</button>
|
||
</div>
|
||
<p className="login-modal-hint">
|
||
로그인 시 동일 계정의 설정이 모든 기기에서 공유됩니다. 로그인하지 않으면 이 브라우저(기기)별로 설정이 저장됩니다.
|
||
</p>
|
||
<form className="login-modal-form" onSubmit={submit}>
|
||
<label className="login-modal-field">
|
||
<span>아이디</span>
|
||
<input
|
||
type="text"
|
||
autoComplete="username"
|
||
value={username}
|
||
onChange={e => setUsername(e.target.value)}
|
||
disabled={loading}
|
||
/>
|
||
</label>
|
||
<label className="login-modal-field">
|
||
<span>비밀번호</span>
|
||
<input
|
||
type="text"
|
||
autoComplete="off"
|
||
className="login-modal-field--visible"
|
||
value={password}
|
||
onChange={e => setPassword(e.target.value)}
|
||
disabled={loading}
|
||
/>
|
||
</label>
|
||
{error && <p className="login-modal-error">{error}</p>}
|
||
<div className="login-modal-actions">
|
||
<button type="button" className="login-modal-btn login-modal-btn--ghost" onClick={onClose} disabled={loading}>
|
||
취소
|
||
</button>
|
||
<button type="submit" className="login-modal-btn login-modal-btn--primary" disabled={loading}>
|
||
{loading ? '로그인 중…' : '로그인'}
|
||
</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default LoginModal;
|