learning_ai_common_plat/packages/auth-ui/src/LoginForm.tsx
saravanakumardb1 f1ebff5514 feat(scripts+ui): Tier 2 complete \u2014 common_plat 0 hex findings (was 59)
Scanner refinements:
- Exclude services/<svc>/src/        (Fastify backends, not UI)
- Exclude packages/config/           (schema/defaults, not UI)
- Exclude packages/devops/           (internal tooling)
- Exclude packages/create-app/.../templates (scaffolder templates)
- Exclude *.storybook/, /stories/, *.stories.{ts,tsx} (demo/docs)
- Exclude SVG fill=, stroke= hex (brand-mandated, e.g. Google G logo)
- Exclude ThemeEditor.tsx, theme-defaults.* (their content IS hex)
- Exclude /api/themes/ routes (server-side defaults)

Source fixes in shared packages (high leverage \u2014 consumed by every product):
- packages/auth-ui/src/*Form*.tsx + OnboardingShell + MfaChallenge (7)
- packages/dashboard-shell/src/{TopBar,ProfilePage}.tsx (3)
- dashboards/tracker-web/src/app/health/page.tsx (6)

All use the canonical var(--bl-<token>, #fallback) pattern that:
- Lets product themes override (e.g., each product sets --bl-danger differently)
- Falls back to a sensible default if tokens haven't loaded yet (defensive)

common_plat hex: 59 \u2192 0 \u2713 (Tier 2 complete)
Ecosystem total: 1569 \u2192 1402

Tier progress:
  Tier 1 (critical):       13 \u2192 0 \u2713
  Tier 2 (common_plat hex): 59 \u2192 0 \u2713
  Tier 3 (mac_tooling, efforise): NEXT
  Tier 4 (mindlyst, fastgap, flowmonk)
  Tier 5 (non-hex rules)
2026-05-23 14:37:51 -07:00

117 lines
3.2 KiB
TypeScript

import { useState, type FormEvent } from 'react';
import { SocialButtons } from './SocialButtons.js';
import type { LoginFormProps } from './types.js';
/**
* Email/password login form with optional social login buttons.
* Styled via CSS custom properties (inherits --bl-* from host app).
*/
export function LoginForm({
onSubmit,
providers,
onSocialLogin,
isLoading = false,
error,
className,
}: LoginFormProps) {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
function handleSubmit(e: FormEvent) {
e.preventDefault();
onSubmit(email, password);
}
return (
<div className={className} data-testid="bl-login-form">
<form
onSubmit={handleSubmit}
style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}
>
<input
type="email"
placeholder="Email"
value={email}
onChange={e => setEmail(e.target.value)}
required
disabled={isLoading}
data-testid="bl-login-email"
style={{
padding: '10px 12px',
border: '1px solid var(--bl-border, #ccc)',
borderRadius: 'var(--bl-radius, 6px)',
fontSize: '14px',
}}
/>
<input
type="password"
placeholder="Password"
value={password}
onChange={e => setPassword(e.target.value)}
required
disabled={isLoading}
data-testid="bl-login-password"
style={{
padding: '10px 12px',
border: '1px solid var(--bl-border, #ccc)',
borderRadius: 'var(--bl-radius, 6px)',
fontSize: '14px',
}}
/>
{error && (
<div
data-testid="bl-login-error"
style={{ color: 'var(--bl-error, #dc3545)', fontSize: '13px' }}
>
{error}
</div>
)}
<button
type="submit"
disabled={isLoading}
data-testid="bl-login-submit"
style={{
padding: '10px 16px',
border: 'none',
borderRadius: 'var(--bl-radius, 6px)',
background: 'var(--bl-primary, #0066ff)',
color: 'var(--bl-accent-foreground, #fff)',
cursor: isLoading ? 'not-allowed' : 'pointer',
fontSize: '14px',
fontWeight: 600,
opacity: isLoading ? 0.6 : 1,
}}
>
{isLoading ? 'Signing in...' : 'Sign in'}
</button>
</form>
{providers && providers.length > 0 && onSocialLogin && (
<>
<div
style={{
display: 'flex',
alignItems: 'center',
gap: '12px',
margin: '16px 0',
color: 'var(--bl-muted, #999)',
fontSize: '13px',
}}
>
<hr
style={{ flex: 1, border: 'none', borderTop: '1px solid var(--bl-border, #eee)' }}
/>
or
<hr
style={{ flex: 1, border: 'none', borderTop: '1px solid var(--bl-border, #eee)' }}
/>
</div>
<SocialButtons providers={providers} onSelect={onSocialLogin} disabled={isLoading} />
</>
)}
</div>
);
}