- 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)
85 lines
3.0 KiB
TypeScript
85 lines
3.0 KiB
TypeScript
/**
|
|
* 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>
|
|
);
|
|
}
|