mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage
synced 2026-04-11 18:28:15 +00:00
feat: design tokens — multi-hospital theming system
Backend (sidecar): - ThemeService: read/write/backup/reset theme.json with versioning - ThemeController: GET/PUT/POST /api/config/theme endpoints - ConfigThemeModule registered in app Frontend: - ThemeTokenProvider: fetches theme, injects CSS variables on <html> - Login page: logo, title, subtitle, Google/forgot toggles from tokens - Sidebar: title, subtitle, active highlight from brand color scale - AI chat: quick actions from tokens - Branding settings page: 2-column layout, file upload for logo/favicon, single color picker with palette generation, font dropdowns, presets, pinned footer, versioning Theme CSS: - Sidebar active/hover text now references --color-brand-400 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -10,12 +10,14 @@ import { Checkbox } from '@/components/base/checkbox/checkbox';
|
||||
import { Input } from '@/components/base/input/input';
|
||||
import { MaintOtpModal } from '@/components/modals/maint-otp-modal';
|
||||
import { useMaintShortcuts } from '@/hooks/use-maint-shortcuts';
|
||||
import { useThemeTokens } from '@/providers/theme-token-provider';
|
||||
|
||||
export const LoginPage = () => {
|
||||
const { loginWithUser } = useAuth();
|
||||
const { refresh } = useData();
|
||||
const navigate = useNavigate();
|
||||
const { isOpen, activeAction, close } = useMaintShortcuts();
|
||||
const { tokens } = useThemeTokens();
|
||||
|
||||
const saved = localStorage.getItem('helix_remember');
|
||||
const savedCreds = saved ? JSON.parse(saved) : null;
|
||||
@@ -89,13 +91,13 @@ export const LoginPage = () => {
|
||||
<div className="w-full max-w-[420px] bg-primary rounded-xl shadow-xl p-8">
|
||||
{/* Logo */}
|
||||
<div className="flex flex-col items-center mb-8">
|
||||
<img src="/helix-logo.png" alt="Helix Engage" className="size-12 rounded-xl mb-3" />
|
||||
<h1 className="text-display-xs font-bold text-primary font-display">Sign in to Helix Engage</h1>
|
||||
<p className="text-sm text-tertiary mt-1">Global Hospital</p>
|
||||
<img src={tokens.brand.logo} alt={tokens.brand.name} className="size-12 rounded-xl mb-3" />
|
||||
<h1 className="text-display-xs font-bold text-primary font-display">{tokens.login.title}</h1>
|
||||
<p className="text-sm text-tertiary mt-1">{tokens.login.subtitle}</p>
|
||||
</div>
|
||||
|
||||
{/* Google sign-in */}
|
||||
<SocialButton
|
||||
{tokens.login.showGoogleSignIn && <SocialButton
|
||||
social="google"
|
||||
size="lg"
|
||||
theme="gray"
|
||||
@@ -104,14 +106,14 @@ export const LoginPage = () => {
|
||||
className="w-full rounded-xl py-3 border-2 border-secondary font-semibold hover:bg-secondary transition duration-300 ease-[cubic-bezier(0.4,0,0.2,1)]"
|
||||
>
|
||||
Sign in with Google
|
||||
</SocialButton>
|
||||
</SocialButton>}
|
||||
|
||||
{/* Divider */}
|
||||
<div className="mt-5 mb-5 flex items-center gap-3">
|
||||
{tokens.login.showGoogleSignIn && <div className="mt-5 mb-5 flex items-center gap-3">
|
||||
<div className="flex-1 h-px bg-secondary" />
|
||||
<span className="text-xs font-semibold text-quaternary tracking-wider uppercase">or continue with</span>
|
||||
<div className="flex-1 h-px bg-secondary" />
|
||||
</div>
|
||||
</div>}
|
||||
|
||||
{/* Form */}
|
||||
<form onSubmit={handleSubmit} className="flex flex-col gap-4" noValidate>
|
||||
@@ -156,13 +158,13 @@ export const LoginPage = () => {
|
||||
isSelected={rememberMe}
|
||||
onChange={setRememberMe}
|
||||
/>
|
||||
<button
|
||||
{tokens.login.showForgotPassword && <button
|
||||
type="button"
|
||||
className="text-sm font-semibold text-brand-secondary hover:text-brand-secondary_hover transition duration-100 ease-linear"
|
||||
onClick={() => setError('Password reset is not yet configured. Contact your administrator.')}
|
||||
>
|
||||
Forgot password?
|
||||
</button>
|
||||
</button>}
|
||||
</div>
|
||||
|
||||
<Button
|
||||
@@ -178,7 +180,7 @@ export const LoginPage = () => {
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<a href="https://f0rty2.ai" target="_blank" rel="noopener noreferrer" className="mt-6 text-xs text-primary_on-brand opacity-60 hover:opacity-90 transition duration-100 ease-linear">Powered by F0rty2.ai</a>
|
||||
<a href={tokens.login.poweredBy.url} target="_blank" rel="noopener noreferrer" className="mt-6 text-xs text-primary_on-brand opacity-60 hover:opacity-90 transition duration-100 ease-linear">{tokens.login.poweredBy.label}</a>
|
||||
|
||||
<MaintOtpModal isOpen={isOpen} onOpenChange={(open) => !open && close()} action={activeAction} />
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user