feat(19-04): add LoginError, fetchAuthMode/login/logout + auth fetchers, BrandSlot, brand-seam tokens
- Add LoginError class (4 codes: invalid/rate-limit/locked/server) mirroring SessionExpiredError shape - Add hasLocalCredential to MeUser interface - Add fetchAuthMode, fetchLocalLogin, fetchLocalLogout (pre-auth endpoints) - Add fetchChangePassword, fetchCreateMember, fetchAdminResetPassword, fetchLinkOidc - Create BrandSlot component with Phase-17-ready placeholder (48px circle, FS initials, h1, tagline) - Add --brand-logo-* CSS custom properties to tokens.css (Phase 17 seam)
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* BrandSlot — Phase 17 seam component for the login page brand area.
|
||||
*
|
||||
* Phase 19 ships a minimal shippable placeholder: a 48px circle with "FS"
|
||||
* initials, the app name "FamilySync", and the tagline "Family calendar & lists".
|
||||
*
|
||||
* Phase 17 replaces the internals of this component (swap the placeholder div for
|
||||
* an <img> with a real logo) without touching LoginPage's layout. This isolates
|
||||
* the branding seam — see 19-UI-SPEC.md §Brand Slot section.
|
||||
*
|
||||
* CSS custom properties used (all set in tokens.css with placeholder defaults;
|
||||
* Phase 17 overrides these values):
|
||||
* --brand-logo-bg — logo circle background (default: var(--color-member-0))
|
||||
* --brand-logo-text — initials color (default: #ffffff)
|
||||
* --brand-logo-size — circle diameter (default: 48px)
|
||||
* --brand-logo-border-radius — circle shape (default: 50%)
|
||||
*
|
||||
* Accessibility:
|
||||
* <h1> contains the app name — screen readers read "FamilySync" as the page title.
|
||||
* The logo circle is aria-hidden (the text is the accessible label).
|
||||
* No <img> today → no broken image ref → no layout shift when Phase 17 replaces it.
|
||||
*
|
||||
* Security: all copy is plain-text JSX children — no dangerouslySetInnerHTML (T-05-24).
|
||||
*/
|
||||
|
||||
export function BrandSlot() {
|
||||
return (
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
{/* Phase 17 replaces this div with <img src="..." alt="" /> */}
|
||||
<div
|
||||
aria-hidden="true"
|
||||
style={{
|
||||
width: 'var(--brand-logo-size, 48px)',
|
||||
height: 'var(--brand-logo-size, 48px)',
|
||||
borderRadius: 'var(--brand-logo-border-radius, 50%)',
|
||||
background: 'var(--brand-logo-bg, var(--color-member-0, #4a90d9))',
|
||||
color: 'var(--brand-logo-text, #ffffff)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
margin: '0 auto var(--space-2, 8px)',
|
||||
fontSize: 'var(--text-display-size, 24px)',
|
||||
fontWeight: 600,
|
||||
fontFamily: 'var(--font-family-base)',
|
||||
flexShrink: 0,
|
||||
aspectRatio: '1 / 1',
|
||||
}}
|
||||
>
|
||||
FS
|
||||
</div>
|
||||
|
||||
{/* App name — <h1> so screen readers identify the page (UI-SPEC §Accessibility) */}
|
||||
<h1
|
||||
style={{
|
||||
margin: 0,
|
||||
marginTop: 'var(--space-2, 8px)',
|
||||
marginBottom: 'var(--space-1, 4px)',
|
||||
fontSize: 'var(--text-display-size, 24px)',
|
||||
fontWeight: 600,
|
||||
lineHeight: 'var(--text-display-line-height, 1.2)',
|
||||
color: 'var(--color-text-primary, #111318)',
|
||||
fontFamily: 'var(--font-family-base)',
|
||||
}}
|
||||
>
|
||||
FamilySync
|
||||
</h1>
|
||||
|
||||
{/* Tagline */}
|
||||
<p
|
||||
style={{
|
||||
margin: 0,
|
||||
marginBottom: 'var(--space-8, 32px)',
|
||||
fontSize: 'var(--text-body-size, 15px)',
|
||||
fontWeight: 400,
|
||||
lineHeight: 'var(--text-body-line-height, 1.5)',
|
||||
color: 'var(--color-text-secondary, #6b7280)',
|
||||
fontFamily: 'var(--font-family-base)',
|
||||
}}
|
||||
>
|
||||
Family calendar & lists
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user