feat: migrate login page to design system styling
All checks were successful
CI / build (push) Successful in 1m26s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 57s
CI / deploy (push) Successful in 38s
CI / deploy-feature (push) Has been skipped

Replace inline styles with CSS module matching the design system's
LoginForm visual patterns. Uses proper DS class structure (divider,
social section, form fields) while keeping username-based auth
instead of the DS component's email validation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-03-24 16:44:52 +01:00
parent 81f85aa82d
commit e54d20bcb7
2 changed files with 146 additions and 38 deletions

View File

@@ -0,0 +1,85 @@
.page {
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
background: var(--bg-base);
}
.card {
width: 100%;
max-width: 400px;
padding: 32px;
}
.loginForm {
display: flex;
flex-direction: column;
align-items: center;
font-family: var(--font-body);
width: 100%;
}
.logo {
margin-bottom: 8px;
font-size: 24px;
font-weight: 700;
color: var(--text-primary);
}
.subtitle {
font-size: 13px;
color: var(--text-muted);
margin: 0 0 24px;
}
.error {
width: 100%;
margin-bottom: 16px;
}
.socialSection {
display: flex;
flex-direction: column;
gap: 8px;
width: 100%;
margin-bottom: 20px;
}
.divider {
display: flex;
align-items: center;
gap: 12px;
width: 100%;
margin-bottom: 20px;
}
.dividerLine {
flex: 1;
height: 1px;
background: var(--border);
}
.dividerText {
color: var(--text-muted);
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.5px;
font-weight: 500;
}
.fields {
display: flex;
flex-direction: column;
gap: 14px;
width: 100%;
}
.submitButton {
width: 100%;
}
.ssoButton {
width: 100%;
justify-content: center;
}

View File

@@ -3,6 +3,7 @@ import { Navigate } from 'react-router';
import { useAuthStore } from './auth-store';
import { api } from '../api/client';
import { Card, Input, Button, Alert, FormField } from '@cameleer/design-system';
import styles from './LoginPage.module.css';
interface OidcInfo {
clientId: string;
@@ -50,53 +51,75 @@ export function LoginPage() {
};
return (
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: '100vh', background: 'var(--surface-ground)' }}>
<Card>
<form onSubmit={handleSubmit} style={{ padding: '2rem', minWidth: 360 }}>
<div style={{ textAlign: 'center', marginBottom: '1.5rem' }}>
<h1 style={{ fontSize: '1.5rem', fontWeight: 600 }}>cameleer3</h1>
<p style={{ color: 'var(--text-secondary)', marginTop: '0.25rem', fontSize: '0.875rem' }}>
Sign in to access the observability dashboard
</p>
</div>
<div className={styles.page}>
<Card className={styles.card}>
<div className={styles.loginForm}>
<div className={styles.logo}>cameleer3</div>
<p className={styles.subtitle}>Sign in to access the observability dashboard</p>
{error && (
<div className={styles.error}>
<Alert variant="error">{error}</Alert>
</div>
)}
{oidc && (
<>
<Button variant="secondary" onClick={handleOidcLogin} disabled={oidcLoading} style={{ width: '100%', marginBottom: '1rem' }}>
{oidcLoading ? 'Redirecting...' : 'Sign in with SSO'}
</Button>
<div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem', margin: '1rem 0' }}>
<hr style={{ flex: 1, border: 'none', borderTop: '1px solid var(--border)' }} />
<span style={{ color: 'var(--text-tertiary)', fontSize: '0.75rem' }}>or</span>
<hr style={{ flex: 1, border: 'none', borderTop: '1px solid var(--border)' }} />
<div className={styles.socialSection}>
<Button
variant="secondary"
className={styles.ssoButton}
onClick={handleOidcLogin}
disabled={oidcLoading}
type="button"
>
{oidcLoading ? 'Redirecting...' : 'Sign in with SSO'}
</Button>
</div>
<div className={styles.divider}>
<div className={styles.dividerLine} />
<span className={styles.dividerText}>or</span>
<div className={styles.dividerLine} />
</div>
</>
)}
<FormField label="Username">
<Input
value={username}
onChange={(e) => setUsername(e.target.value)}
autoFocus
autoComplete="username"
/>
</FormField>
<form className={styles.fields} onSubmit={handleSubmit} aria-label="Sign in" noValidate>
<FormField label="Username" htmlFor="login-username">
<Input
id="login-username"
value={username}
onChange={(e) => setUsername(e.target.value)}
placeholder="Enter your username"
autoFocus
autoComplete="username"
disabled={loading}
/>
</FormField>
<FormField label="Password">
<Input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
autoComplete="current-password"
/>
</FormField>
<FormField label="Password" htmlFor="login-password">
<Input
id="login-password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="••••••••"
autoComplete="current-password"
disabled={loading}
/>
</FormField>
<Button variant="primary" disabled={loading || !username || !password} style={{ width: '100%', marginTop: '0.5rem' }}>
{loading ? 'Signing in...' : 'Sign In'}
</Button>
{error && <div style={{ marginTop: '1rem' }}><Alert variant="error">{error}</Alert></div>}
</form>
<Button
variant="primary"
type="submit"
loading={loading}
disabled={loading || !username || !password}
className={styles.submitButton}
>
Sign in
</Button>
</form>
</div>
</Card>
</div>
);