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:
2026-04-02 15:50:36 +05:30
parent c5d5e9c4f9
commit afd0829dc6
10 changed files with 1358 additions and 28 deletions

View File

@@ -1,5 +1,6 @@
import type { ReactNode } from 'react';
import { useRef, useEffect } from 'react';
import { useThemeTokens } from '@/providers/theme-token-provider';
import { useChat } from '@ai-sdk/react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faPaperPlaneTop, faSparkles, faUserHeadset } from '@fortawesome/pro-duotone-svg-icons';
@@ -18,14 +19,9 @@ interface AiChatPanelProps {
onChatStart?: () => void;
}
const QUICK_ACTIONS = [
{ label: 'Doctor availability', prompt: 'What doctors are available and what are their visiting hours?' },
{ label: 'Clinic timings', prompt: 'What are the clinic locations and timings?' },
{ label: 'Patient history', prompt: 'Can you summarize this patient\'s history?' },
{ label: 'Treatment packages', prompt: 'What treatment packages are available?' },
];
export const AiChatPanel = ({ callerContext, onChatStart }: AiChatPanelProps) => {
const { tokens } = useThemeTokens();
const quickActions = tokens.ai.quickActions;
const messagesEndRef = useRef<HTMLDivElement>(null);
const chatStartedRef = useRef(false);
@@ -67,7 +63,7 @@ export const AiChatPanel = ({ callerContext, onChatStart }: AiChatPanelProps) =>
Ask me about doctors, clinics, packages, or patient info.
</p>
<div className="mt-3 flex flex-wrap justify-center gap-1.5">
{QUICK_ACTIONS.map((action) => (
{quickActions.map((action) => (
<button
key={action.label}
onClick={() => handleQuickAction(action.prompt)}