// LoginScreen — KARE Partner App PA-001 // AuthLayout, mobile 375px const KARE = { primary: '#0D7A6E', primaryBgHover: '#E6F4F2', primaryPressed: '#085C53', gold: '#C9A84C', n900: '#111827', n700: '#374151', n500: '#6B7280', n300: '#D1D5DB', n100: '#F3F4F6', n000: '#FFFFFF', error: '#DC2626', errorBg: '#FEF2F2', warning: '#D97706', warningBg: '#FFFBEB', success: '#059669', successBg: '#ECFDF5', }; const KO = { partner: 'Partner', title: '파트너 로그인', subtitle: 'Sign in to manage your services', email: '이메일', emailPh: 'name@partner.com', password: '비밀번호', pwPh: '비밀번호 입력', show: '표시', hide: '숨김', signIn: '로그인', forgot: '비밀번호 찾기', invalid: '이메일 또는 비밀번호가 올바르지 않습니다', accountSuspended: '계정이 정지되었습니다. KARE 지원팀에 문의하세요', partnerSuspended: '파트너사가 정지 상태입니다. 조회 전용 모드로 진입합니다', copyright: '© KARE 2026 · Partner App', }; const EN = { partner: 'Partner', title: 'Partner Sign In', subtitle: 'Sign in to manage your services', email: 'Email', emailPh: 'name@partner.com', password: 'Password', pwPh: 'Enter password', show: 'Show', hide: 'Hide', signIn: 'Sign in', forgot: 'Forgot password', invalid: 'Incorrect email or password', accountSuspended: 'Your account is suspended. Please contact KARE support.', partnerSuspended: 'Partner is suspended. Entering view-only mode.', copyright: '© KARE 2026 · Partner App', }; const COPY = { ko: KO, en: EN }; // ───────────────────────────────────────────────────────────── // KARE logo — wordmark + accent square (no complex SVG) // ───────────────────────────────────────────────────────────── function KareLogo({ size = 80 }) { // size = total square footprint; render wordmark + small accent const fs = Math.round(size * 0.36); return (
KARE
); } // ───────────────────────────────────────────────────────────── // Lang toggle (top-right) // ───────────────────────────────────────────────────────────── function LangToggle({ lang, onChange }) { const opt = (v, label) => ( ); return (
{opt('ko', 'KO')}
{opt('en', 'EN')}
); } // ───────────────────────────────────────────────────────────── // Text input // ───────────────────────────────────────────────────────────── function TextField({ label, value, onChange, placeholder, type = 'text', error = false, trailing, autoComplete, onFocus, onBlur, focused, }) { const border = error ? KARE.error : (focused ? KARE.primary : KARE.n300); return (
onChange(e.target.value)} onFocus={onFocus} onBlur={onBlur} placeholder={placeholder} autoComplete={autoComplete} style={{ flex: 1, border: 'none', outline: 'none', background: 'transparent', fontFamily: 'inherit', fontSize: 16, color: KARE.n900, minWidth: 0, }} /> {trailing}
); } // ───────────────────────────────────────────────────────────── // Primary button — 56px glove-friendly // ───────────────────────────────────────────────────────────── function PrimaryBtn({ children, disabled, loading, onClick }) { const bg = disabled ? KARE.n300 : KARE.primary; return ( ); } function Spinner() { return (
); } // ───────────────────────────────────────────────────────────── // Toast — top-anchored, error or warning // ───────────────────────────────────────────────────────────── function Toast({ kind = 'error', children, top = 56 }) { const palette = kind === 'warning' ? { fg: KARE.warning, bg: KARE.warningBg, border: '#FBE5BE', icon: '!' } : { fg: KARE.error, bg: KARE.errorBg, border: '#FBD5D5', icon: '!' }; return (
{palette.icon}
{children}
); } // ───────────────────────────────────────────────────────────── // Inline error // ───────────────────────────────────────────────────────────── function InlineError({ children }) { return (
{children}
); } // ───────────────────────────────────────────────────────────── // Eye icon (show/hide password) // ───────────────────────────────────────────────────────────── function EyeBtn({ shown, onToggle }) { return ( ); } // ───────────────────────────────────────────────────────────── // LoginScreen — full screen, designed for 375 viewport // ───────────────────────────────────────────────────────────── // state: 'default' | 'filled' | 'loading' | 'invalid' // | 'accountSuspended' | 'partnerSuspended' // interactive: if true, manages its own state from a 'default' baseline // ───────────────────────────────────────────────────────────── function LoginScreen({ state: stateProp = 'default', interactive = false, initialLang = 'ko' }) { const [lang, setLang] = React.useState(initialLang); const t = COPY[lang]; // Interactive state const [email, setEmail] = React.useState(''); const [pw, setPw] = React.useState(''); const [showPw, setShowPw] = React.useState(false); const [focused, setFocused] = React.useState(null); const [loading, setLoading] = React.useState(false); const [invalid, setInvalid] = React.useState(false); const [toast, setToast] = React.useState(null); // 'accountSuspended' | 'partnerSuspended' | null // Static state overrides (for static showcase artboards) let _email = email, _pw = pw, _showPw = showPw, _loading = loading, _invalid = invalid, _toast = toast; if (!interactive) { if (stateProp === 'default') { _email = ''; _pw = ''; } else if (stateProp === 'filled') { _email = 'jiwon.kim@seoulmed.kr'; _pw = '••••••••••'; } else if (stateProp === 'loading') { _email = 'jiwon.kim@seoulmed.kr'; _pw = '••••••••••'; _loading = true; } else if (stateProp === 'invalid') { _email = 'jiwon.kim@seoulmed.kr'; _pw = '••••••••••'; _invalid = true; } else if (stateProp === 'accountSuspended') { _email = 'jiwon.kim@seoulmed.kr'; _pw = '••••••••••'; _toast = 'accountSuspended'; } else if (stateProp === 'partnerSuspended') { _email = 'jiwon.kim@seoulmed.kr'; _pw = '••••••••••'; _toast = 'partnerSuspended'; } } const canSubmit = _email.trim().length > 0 && _pw.length > 0 && !_loading; const handleSubmit = () => { if (!interactive || !canSubmit) return; setInvalid(false); setToast(null); setLoading(true); // Demo behavior: short timeout → invalid (unless special pwd) setTimeout(() => { setLoading(false); if (pw === 'kare2026') { setToast('partnerSuspended'); // success-but-suspended demo } else if (pw === 'suspended') { setToast('accountSuspended'); } else { setInvalid(true); } }, 1200); }; return (
{/* Lang toggle, top-right, below status bar */}
{}} />
{/* Toast layer */} {_toast === 'accountSuspended' && ( {t.accountSuspended} )} {_toast === 'partnerSuspended' && ( {t.partnerSuspended} )} {/* Main content — centered */}
{/* Logo + Partner caption */}
{t.partner}
{/* Card */}

{t.title}

{t.subtitle}

{}} placeholder={t.emailPh} type="email" autoComplete="email" error={_invalid} focused={focused === 'email'} onFocus={() => setFocused('email')} onBlur={() => setFocused(null)} /> {}} placeholder={t.pwPh} type={_showPw ? 'text' : 'password'} autoComplete="current-password" error={_invalid} focused={focused === 'password'} onFocus={() => setFocused('password')} onBlur={() => setFocused(null)} trailing={ setShowPw(!showPw) : () => {}} /> } /> {_invalid && {t.invalid}} {t.signIn}
{/* Footer copyright */}
{t.copyright}
); } Object.assign(window, { LoginScreen, KARE, KareLogo, LangToggle, TextField, PrimaryBtn, Spinner, Toast, InlineError, EyeBtn, COPY, });