feat: add environment-based CSS theming with configurable colors and branding
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { Link, Outlet, useLocation } from "react-router-dom";
|
||||
import { SecuirdLogo as GatehouseLogo } from "@/components/branding/SecuirdLogo";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { config } from "@/config";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
Shield,
|
||||
@@ -35,7 +36,7 @@ return (
|
||||
{/* Logo */}
|
||||
<Link to="/" className="flex items-center gap-2.5">
|
||||
<GatehouseLogo size="md" />
|
||||
<span className="text-xl font-semibold text-foreground tracking-tight">Secuird</span>
|
||||
<span className="text-xl font-semibold text-foreground tracking-tight">{config.app.name}</span>
|
||||
</Link>
|
||||
|
||||
{/* Desktop Navigation */}
|
||||
@@ -130,7 +131,7 @@ return (
|
||||
<div className="col-span-2 lg:col-span-1">
|
||||
<Link to="/" className="flex items-center gap-2.5">
|
||||
<GatehouseLogo size="sm" />
|
||||
<span className="text-lg font-semibold text-foreground tracking-tight">Secuird</span>
|
||||
<span className="text-lg font-semibold text-foreground tracking-tight">{config.app.name}</span>
|
||||
</Link>
|
||||
<p className="mt-4 text-sm text-muted-foreground max-w-xs">
|
||||
Enterprise identity and access management. Secure by design, simple by choice.
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Shield, Smartphone, Fingerprint, AlertTriangle, CheckCircle, Loader2 }
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { useAuth } from '@/contexts/AuthContext';
|
||||
import { config } from '@/config';
|
||||
import { AddPasskeyWizard } from '@/components/security/AddPasskeyWizard';
|
||||
import { TotpEnrollmentWizard } from '@/components/security/TotpEnrollmentWizard';
|
||||
import { api } from '@/lib/api';
|
||||
@@ -98,7 +99,7 @@ export default function MfaEnforcementLayout() {
|
||||
<header className="h-14 border-b border-border bg-card flex items-center justify-between px-4 flex-shrink-0">
|
||||
<div className="flex items-center gap-3">
|
||||
<Shield className="w-5 h-5 text-primary" />
|
||||
<span className="font-semibold text-foreground">Secuird</span>
|
||||
<span className="font-semibold text-foreground">{config.app.name}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Outlet, Link } from "react-router-dom";
|
||||
import { SecuirdLogo } from "@/components/branding/SecuirdLogo";
|
||||
import { config } from "@/config";
|
||||
|
||||
export default function PublicLayout() {
|
||||
return (
|
||||
@@ -12,7 +13,7 @@ export default function PublicLayout() {
|
||||
<div className="max-w-md mx-auto">
|
||||
<Link to="/" className="flex items-center gap-2.5 justify-center">
|
||||
<SecuirdLogo size="md" />
|
||||
<span className="text-xl font-semibold text-foreground tracking-tight">Secuird</span>
|
||||
<span className="text-xl font-semibold text-foreground tracking-tight">{config.app.name}</span>
|
||||
</Link>
|
||||
</div>
|
||||
</header>
|
||||
@@ -28,7 +29,7 @@ export default function PublicLayout() {
|
||||
<footer className="relative z-10 py-6 px-4">
|
||||
<div className="max-w-md mx-auto text-center">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
© {new Date().getFullYear()} Secuird. Identity & Access.
|
||||
© {new Date().getFullYear()} {config.app.name}. Identity & Access.
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@@ -23,6 +23,7 @@ import { SecuirdLogo } from "@/components/branding/SecuirdLogo";
|
||||
import { NavLink } from "@/components/NavLink";
|
||||
import { useAuth } from "@/contexts/AuthContext";
|
||||
import { useOrg } from "@/contexts/OrgContext";
|
||||
import { config } from "@/config";
|
||||
import {
|
||||
Sidebar,
|
||||
SidebarContent,
|
||||
@@ -104,7 +105,7 @@ export function AppSidebar() {
|
||||
<SecuirdLogo size="sm" variant="light" />
|
||||
{!collapsed && (
|
||||
<span className="text-lg font-semibold text-sidebar-foreground tracking-tight">
|
||||
Secuird
|
||||
{config.app.name}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
@@ -221,7 +222,7 @@ export function AppSidebar() {
|
||||
<SidebarFooter className="p-4 border-t border-sidebar-border">
|
||||
{!collapsed && (
|
||||
<div className="text-xs text-sidebar-muted">
|
||||
{import.meta.env.VITE_APP_VERSION ?? 'Secuird'}
|
||||
{import.meta.env.VITE_APP_VERSION ?? config.app.name}
|
||||
</div>
|
||||
)}
|
||||
</SidebarFooter>
|
||||
|
||||
+63
-2
@@ -4,6 +4,60 @@
|
||||
// Base URL without /api/v1 suffix - used for CLI sign URL
|
||||
const BASE_URL = import.meta.env.VITE_API_BASE_URL || "http://192.168.64.7:8888";
|
||||
|
||||
// Theme configuration from environment
|
||||
export type ThemeName = "default" | "dev" | "preprod";
|
||||
|
||||
interface ThemeColors {
|
||||
primary: string;
|
||||
primaryForeground: string;
|
||||
sidebarPrimary: string;
|
||||
sidebarPrimaryForeground: string;
|
||||
ring: string;
|
||||
}
|
||||
|
||||
const THEME_COLORS: Record<ThemeName, ThemeColors> = {
|
||||
default: {
|
||||
primary: "173 65% 36%", // teal
|
||||
primaryForeground: "0 0% 100%",
|
||||
sidebarPrimary: "173 65% 36%",
|
||||
sidebarPrimaryForeground: "0 0% 100%",
|
||||
ring: "173 65% 36%",
|
||||
},
|
||||
dev: {
|
||||
primary: "0 72% 51%", // red
|
||||
primaryForeground: "0 0% 100%",
|
||||
sidebarPrimary: "0 72% 51%",
|
||||
sidebarPrimaryForeground: "0 0% 100%",
|
||||
ring: "0 72% 51%",
|
||||
},
|
||||
preprod: {
|
||||
primary: "270 70% 55%", // purple
|
||||
primaryForeground: "0 0% 100%",
|
||||
sidebarPrimary: "270 70% 55%",
|
||||
sidebarPrimaryForeground: "0 0% 100%",
|
||||
ring: "270 70% 55%",
|
||||
},
|
||||
};
|
||||
|
||||
const THEME_DEFAULTS: Record<ThemeName, { appName: string; favicon: string }> = {
|
||||
default: {
|
||||
appName: "Secuird",
|
||||
favicon: "/favicon.svg",
|
||||
},
|
||||
dev: {
|
||||
appName: "Secuird Dev",
|
||||
favicon: "/favicon-dev.svg",
|
||||
},
|
||||
preprod: {
|
||||
appName: "Secuird Staging",
|
||||
favicon: "/favicon-staging.svg",
|
||||
},
|
||||
};
|
||||
|
||||
const themeName = (import.meta.env.VITE_THEME || "default") as ThemeName;
|
||||
const themeColors = THEME_COLORS[themeName] || THEME_COLORS.default;
|
||||
const themeDefaults = THEME_DEFAULTS[themeName] || THEME_DEFAULTS.default;
|
||||
|
||||
export const config = {
|
||||
// API Configuration
|
||||
api: {
|
||||
@@ -13,10 +67,17 @@ export const config = {
|
||||
// Sign URL for CLI (same as base URL, no /api/v1)
|
||||
signUrl: BASE_URL,
|
||||
|
||||
// App metadata
|
||||
// App metadata - can be overridden per-theme or via env
|
||||
app: {
|
||||
name: "Secuird",
|
||||
name: import.meta.env.VITE_APP_NAME || themeDefaults.appName,
|
||||
description: "Identity & Access Platform",
|
||||
favicon: import.meta.env.VITE_FAVICON || themeDefaults.favicon,
|
||||
},
|
||||
|
||||
// Theme configuration for CSS variable injection
|
||||
theme: {
|
||||
name: themeName,
|
||||
colors: themeColors,
|
||||
},
|
||||
|
||||
// Feature flags
|
||||
|
||||
+15
-13
@@ -1,5 +1,7 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
|
||||
|
||||
@import './styles/theme-vars.css';
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@@ -7,7 +9,7 @@
|
||||
/* Secuird Design System - Enterprise Identity & Access Platform
|
||||
Authoritative, infrastructure-grade aesthetic with slate/charcoal/muted blue palette
|
||||
Colors are HSL for theming flexibility
|
||||
*/
|
||||
*/
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
@@ -21,9 +23,9 @@
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 222 47% 9%;
|
||||
|
||||
/* Primary — teal, fully saturated, dark enough to read on white */
|
||||
--primary: 173 65% 36%;
|
||||
--primary-foreground: 0 0% 100%;
|
||||
/* Primary — theme-configurable (teal default, red for dev, purple for staging) */
|
||||
--primary: var(--theme-primary);
|
||||
--primary-foreground: var(--theme-primary-foreground);
|
||||
|
||||
/* Secondary — cool blue-gray, clearly darker than bg */
|
||||
--secondary: 216 20% 91%;
|
||||
@@ -33,9 +35,9 @@
|
||||
--muted: 216 18% 88%;
|
||||
--muted-foreground: 222 18% 42%;
|
||||
|
||||
/* Accent — same teal as primary */
|
||||
--accent: 173 65% 36%;
|
||||
--accent-foreground: 0 0% 100%;
|
||||
/* Accent — same as primary */
|
||||
--accent: var(--theme-primary);
|
||||
--accent-foreground: var(--theme-primary-foreground);
|
||||
|
||||
/* Semantic */
|
||||
--destructive: 0 72% 48%;
|
||||
@@ -53,24 +55,24 @@
|
||||
/* UI chrome */
|
||||
--border: 216 18% 84%; /* clearly visible on white card */
|
||||
--input: 216 18% 92%;
|
||||
--ring: 173 65% 36%;
|
||||
--ring: var(--theme-ring);
|
||||
|
||||
--radius: 0.5rem;
|
||||
|
||||
/* Sidebar */
|
||||
--sidebar-background: 222 30% 95%;
|
||||
--sidebar-foreground: 222 47% 18%;
|
||||
--sidebar-primary: 173 65% 36%;
|
||||
--sidebar-primary-foreground: 0 0% 100%;
|
||||
--sidebar-primary: var(--theme-sidebar-primary);
|
||||
--sidebar-primary-foreground: var(--theme-sidebar-primary-foreground);
|
||||
--sidebar-accent: 216 20% 88%;
|
||||
--sidebar-accent-foreground: 222 47% 9%;
|
||||
--sidebar-border: 216 18% 84%;
|
||||
--sidebar-ring: 173 65% 36%;
|
||||
--sidebar-ring: var(--theme-ring);
|
||||
--sidebar-muted: 222 20% 48%;
|
||||
|
||||
/* Gradients */
|
||||
--gradient-brand: linear-gradient(135deg, hsl(173 65% 36%), hsl(173 65% 28%));
|
||||
--gradient-accent: linear-gradient(135deg, hsl(173 65% 36%), hsl(173 65% 28%));
|
||||
--gradient-brand: linear-gradient(135deg, hsl(var(--theme-primary)), hsl(var(--theme-primary) / 0.8));
|
||||
--gradient-accent: linear-gradient(135deg, hsl(var(--theme-primary)), hsl(var(--theme-primary) / 0.8));
|
||||
--gradient-subtle: linear-gradient(135deg, hsl(216 28% 97%), hsl(216 18% 93%));
|
||||
|
||||
/* Shadows — stronger alpha so cards lift off the bg */
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
/* Default theme - Secuird (Teal) */
|
||||
@theme {
|
||||
--color-primary: hsl(173 65% 36%);
|
||||
--color-primary-foreground: hsl(0 0% 100%);
|
||||
--color-sidebar-primary: hsl(173 65% 36%);
|
||||
--color-sidebar-primary-foreground: hsl(0 0% 100%);
|
||||
--color-ring: hsl(173 65% 36%);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
/* Dev theme - Red */
|
||||
@theme {
|
||||
--color-primary: hsl(0 72% 51%);
|
||||
--color-primary-foreground: hsl(0 0% 100%);
|
||||
--color-sidebar-primary: hsl(0 72% 51%);
|
||||
--color-sidebar-primary-foreground: hsl(0 0% 100%);
|
||||
--color-ring: hsl(0 72% 51%);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
/* Preprod theme - Purple */
|
||||
@theme {
|
||||
--color-primary: hsl(270 70% 55%);
|
||||
--color-primary-foreground: hsl(0 0% 100%);
|
||||
--color-sidebar-primary: hsl(270 70% 55%);
|
||||
--color-sidebar-primary-foreground: hsl(0 0% 100%);
|
||||
--color-ring: hsl(270 70% 55%);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
/* Theme variables - values injected by Vite at build time */
|
||||
:root {
|
||||
--theme-primary: %%VITE_THEME_PRIMARY%%;
|
||||
--theme-primary-foreground: 0 0% 100%;
|
||||
--theme-sidebar-primary: %%VITE_THEME_SIDEBAR_PRIMARY%%;
|
||||
--theme-sidebar-primary-foreground: 0 0% 100%;
|
||||
--theme-ring: %%VITE_THEME_RING%%;
|
||||
--theme-color: %%VITE_THEME_COLOR%%;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/* Theme configuration - injected at runtime from config */
|
||||
|
||||
const themeConfig = {
|
||||
default: {
|
||||
primary: "173 65% 36%",
|
||||
sidebarPrimary: "173 65% 36%",
|
||||
ring: "173 65% 36%",
|
||||
},
|
||||
dev: {
|
||||
primary: "0 72% 51%",
|
||||
sidebarPrimary: "0 72% 51%",
|
||||
ring: "0 72% 51%",
|
||||
},
|
||||
preprod: {
|
||||
primary: "270 70% 55%",
|
||||
sidebarPrimary: "270 70% 55%",
|
||||
ring: "270 70% 55%",
|
||||
},
|
||||
};
|
||||
|
||||
const themeName = import.meta.env.VITE_THEME || "default";
|
||||
const theme = themeConfig[themeName as keyof typeof themeConfig] || themeConfig.default;
|
||||
|
||||
const root = document.documentElement;
|
||||
root.style.setProperty("--primary", theme.primary);
|
||||
root.style.setProperty("--primary-foreground", "0 0% 100%");
|
||||
root.style.setProperty("--sidebar-primary", theme.sidebarPrimary);
|
||||
root.style.setProperty("--sidebar-primary-foreground", "0 0% 100%");
|
||||
root.style.setProperty("--ring", theme.ring);
|
||||
Reference in New Issue
Block a user