// LoginPage.jsx const LoginPage = ({ onLogin }) => { const { useState, useEffect, useRef } = React; const [hasPassword, setHasPassword] = useState(!!localStorage.getItem('tj_pwd_hash')); const [password, setPassword] = useState(''); const [confirm, setConfirm] = useState(''); const [error, setError] = useState(''); const [loading, setLoading] = useState(false); const [showReset, setShowReset] = useState(false); const inputRef = useRef(); useEffect(() => { inputRef.current?.focus(); }, []); const hashPwd = async (pwd) => { const enc = new TextEncoder().encode(pwd + 'tj_salt_8x72'); const buf = await crypto.subtle.digest('SHA-256', enc); return Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2,'0')).join(''); }; const handleSubmit = async () => { if (loading || !password) return; setLoading(true); setError(''); try { const hash = await hashPwd(password); if (hasPassword) { const stored = localStorage.getItem('tj_pwd_hash'); if (hash === stored) { sessionStorage.setItem('tj_session', '1'); onLogin(); } else { setError('Incorrect password'); setLoading(false); } } else { if (password.length < 4) { setError('Minimum 4 characters'); setLoading(false); return; } if (password !== confirm) { setError("Passwords don't match"); setLoading(false); return; } localStorage.setItem('tj_pwd_hash', hash); sessionStorage.setItem('tj_session', '1'); onLogin(); } } catch { setError('Error — please retry'); setLoading(false); } }; const handleReset = () => { if (!confirm('Reset your password? Your trading data will be kept.')) return; localStorage.removeItem('tj_pwd_hash'); setHasPassword(false); setPassword(''); setConfirm(''); setError(''); setShowReset(false); }; const inp = (extra = {}) => ({ background: 'var(--surface3)', border: `1px solid ${error ? '#f04060' : 'var(--border)'}`, borderRadius: '8px', color: 'var(--text)', padding: '12px 14px', fontSize: '14px', outline: 'none', fontFamily: 'Space Grotesk, sans-serif', width: '100%', boxSizing: 'border-box', transition: 'border-color 0.15s', ...extra }); return React.createElement('div', { style: { minHeight: '100vh', width: '100%', background: 'var(--bg)', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: '28px', padding: '20px', boxSizing: 'border-box' } }, [ // Brand React.createElement('div', { key: 'brand', style: { display: 'flex', alignItems: 'center', gap: '14px' } }, [ React.createElement('div', { key: 'icon', style: { width: '44px', height: '44px', background: 'linear-gradient(135deg,#5588ff,#a064ff)', borderRadius: '12px', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '18px', fontWeight: 800, color: '#fff', letterSpacing: '-0.5px' } }, 'TJ'), React.createElement('div', { key: 'txt' }, [ React.createElement('div', { key: 'n', style: { fontSize: '22px', fontWeight: 700, color: 'var(--text)', lineHeight: 1 } }, 'TradeJournal'), React.createElement('div', { key: 's', style: { fontSize: '12px', color: 'var(--muted)', marginTop: '3px' } }, 'Personal Trading Intelligence') ]) ]), // Card React.createElement('div', { key: 'card', style: { background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: '16px', padding: '32px', width: '100%', maxWidth: '360px', boxShadow: '0 24px 64px rgba(0,0,0,0.6)' } }, [ React.createElement('div', { key: 'hdr', style: { marginBottom: '22px' } }, [ React.createElement('div', { key: 'h', style: { fontSize: '17px', fontWeight: 700, color: 'var(--text)', marginBottom: '4px' } }, hasPassword ? 'Sign in' : 'Create your password'), React.createElement('div', { key: 's', style: { fontSize: '13px', color: 'var(--muted)' } }, hasPassword ? 'Enter your password to access your journal' : 'Set a password to protect your data'), ]), React.createElement('div', { key: 'form', style: { display: 'flex', flexDirection: 'column', gap: '10px' } }, [ React.createElement('div', { key: 'pwdwrap' }, [ React.createElement('label', { key: 'l', style: { fontSize: '11px', color: 'var(--muted)', fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.07em', display: 'block', marginBottom: '6px' } }, 'Password'), React.createElement('input', { ref: inputRef, key: 'pwd', type: 'password', value: password, placeholder: '••••••••', onChange: e => { setPassword(e.target.value); setError(''); }, onKeyDown: e => e.key === 'Enter' && handleSubmit(), style: inp({ border: `1px solid ${error ? '#f04060' : password.length > 0 ? '#5588ff' : 'var(--border)'}` }) }) ]), !hasPassword && React.createElement('div', { key: 'confwrap' }, [ React.createElement('label', { key: 'l', style: { fontSize: '11px', color: 'var(--muted)', fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.07em', display: 'block', marginBottom: '6px' } }, 'Confirm Password'), React.createElement('input', { key: 'conf', type: 'password', value: confirm, placeholder: '••••••••', onChange: e => { setConfirm(e.target.value); setError(''); }, onKeyDown: e => e.key === 'Enter' && handleSubmit(), style: inp({ border: `1px solid ${error ? '#f04060' : confirm.length > 0 && confirm === password ? '#00d27a' : 'var(--border)'}` }) }) ]), error && React.createElement('div', { key: 'err', style: { fontSize: '12px', color: '#f04060', display: 'flex', alignItems: 'center', gap: '6px' } }, [ React.createElement('span', { key: 'i' }, '⚠'), error ]), React.createElement('button', { key: 'btn', onClick: handleSubmit, disabled: loading || !password, style: { padding: '12px', background: loading ? '#3a5acc' : '#5588ff', border: 'none', borderRadius: '8px', color: '#fff', fontSize: '14px', fontWeight: 600, cursor: loading ? 'wait' : 'pointer', fontFamily: 'Space Grotesk, sans-serif', marginTop: '4px', transition: 'all 0.15s', opacity: !password ? 0.5 : 1 } }, loading ? 'Verifying…' : hasPassword ? 'Sign In →' : 'Create Password →'), hasPassword && React.createElement('button', { key: 'forgot', onClick: handleReset, style: { background: 'none', border: 'none', color: 'var(--muted)', fontSize: '12px', cursor: 'pointer', fontFamily: 'Space Grotesk, sans-serif', padding: '4px', textDecoration: 'underline', marginTop: '2px' } }, 'Forgot password? Reset (data is kept)') ]) ]), // Security note React.createElement('div', { key: 'note', style: { fontSize: '11px', color: 'var(--dim)', textAlign: 'center', maxWidth: '340px', lineHeight: 1.5 } }, [ React.createElement('span', { key: 'icon', style: { marginRight: '4px' } }, '🔒'), 'Client-side password protection. ', React.createElement('strong', { key: 'b', style: { color: '#3a4060' } }, 'For VPS deployment, add server-side authentication (Node.js / Nginx basic auth / OAuth).') ]) ]); }; Object.assign(window, { LoginPage });