diff --git a/.env.example b/.env.example index 54c522f..292d6dd 100644 --- a/.env.example +++ b/.env.example @@ -1 +1,19 @@ -VITE_API_BASE_URL=http://localhost:5000 +# =========================================== +# Secuird UI Configuration +# =========================================== +# Copy this file to .env.local for local development +# or use mode-specific env files (.env.development, .env.staging, .env.production) + +# API Configuration +VITE_API_BASE_URL=https://api.gatehouse.local/api/v1 + +# Theme Configuration +# Options: default (teal), dev (red), preprod (purple) +VITE_THEME=default + +# Optional: Override app name and favicon +# VITE_APP_NAME=Secuird +# VITE_FAVICON=/favicon.svg + +# Host Configuration +VITE_ALLOWED_HOSTS=ui.gatehouse.local,localhost \ No newline at end of file diff --git a/index.html b/index.html index 76ab5db..c3f389b 100644 --- a/index.html +++ b/index.html @@ -5,37 +5,37 @@ - Secuird — Enterprise Identity & Access Management - - + %VITE_APP_NAME% — Enterprise Identity & Access Management + + - + - - + + - - + + - + - - + + - - + + @@ -45,7 +45,7 @@ { "@context": "https://schema.org", "@type": "SoftwareApplication", - "name": "Secuird", + "name": "%VITE_APP_NAME%", "applicationCategory": "SecurityApplication", "operatingSystem": "Web", "description": "Enterprise identity and access management platform providing secure authentication, organization-level security policy, SSH certificate management, and OIDC-based Single Sign-On.", @@ -74,7 +74,7 @@ { "@context": "https://schema.org", "@type": "Organization", - "name": "Secuird", + "name": "%VITE_APP_NAME%", "url": "https://securd.com", "logo": "https://securd.com/gatehouse-logo.svg", "sameAs": [ diff --git a/public/favicon-dev.svg b/public/favicon-dev.svg new file mode 100644 index 0000000..ebf4f5d --- /dev/null +++ b/public/favicon-dev.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/favicon-staging.svg b/public/favicon-staging.svg new file mode 100644 index 0000000..2018bed --- /dev/null +++ b/public/favicon-staging.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/components/layouts/MarketingLayout.tsx b/src/components/layouts/MarketingLayout.tsx index 92b5056..e5998f2 100644 --- a/src/components/layouts/MarketingLayout.tsx +++ b/src/components/layouts/MarketingLayout.tsx @@ -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 */} - Secuird + {config.app.name} {/* Desktop Navigation */} @@ -130,7 +131,7 @@ return (
- Secuird + {config.app.name}

Enterprise identity and access management. Secure by design, simple by choice. diff --git a/src/components/layouts/MfaEnforcementLayout.tsx b/src/components/layouts/MfaEnforcementLayout.tsx index 6b4d260..3eb62d5 100644 --- a/src/components/layouts/MfaEnforcementLayout.tsx +++ b/src/components/layouts/MfaEnforcementLayout.tsx @@ -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() {

- Secuird + {config.app.name}
diff --git a/src/components/layouts/PublicLayout.tsx b/src/components/layouts/PublicLayout.tsx index 507e530..2966d3c 100644 --- a/src/components/layouts/PublicLayout.tsx +++ b/src/components/layouts/PublicLayout.tsx @@ -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() {
- Secuird + {config.app.name}
@@ -28,7 +29,7 @@ export default function PublicLayout() {

- © {new Date().getFullYear()} Secuird. Identity & Access. + © {new Date().getFullYear()} {config.app.name}. Identity & Access.

diff --git a/src/components/navigation/AppSidebar.tsx b/src/components/navigation/AppSidebar.tsx index 533fd46..cd88f9a 100644 --- a/src/components/navigation/AppSidebar.tsx +++ b/src/components/navigation/AppSidebar.tsx @@ -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() { {!collapsed && ( - Secuird + {config.app.name} )}
@@ -221,7 +222,7 @@ export function AppSidebar() { {!collapsed && (
- {import.meta.env.VITE_APP_VERSION ?? 'Secuird'} + {import.meta.env.VITE_APP_VERSION ?? config.app.name}
)}
diff --git a/src/config.ts b/src/config.ts index f01ff66..bfc6a5d 100644 --- a/src/config.ts +++ b/src/config.ts @@ -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 = { + 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 = { + 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 diff --git a/src/index.css b/src/index.css index b9fbffd..adc4663 100644 --- a/src/index.css +++ b/src/index.css @@ -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 */ diff --git a/src/styles/theme-default.css b/src/styles/theme-default.css new file mode 100644 index 0000000..9e00ac6 --- /dev/null +++ b/src/styles/theme-default.css @@ -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%); +} \ No newline at end of file diff --git a/src/styles/theme-dev.css b/src/styles/theme-dev.css new file mode 100644 index 0000000..540938d --- /dev/null +++ b/src/styles/theme-dev.css @@ -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%); +} \ No newline at end of file diff --git a/src/styles/theme-preprod.css b/src/styles/theme-preprod.css new file mode 100644 index 0000000..0905cad --- /dev/null +++ b/src/styles/theme-preprod.css @@ -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%); +} \ No newline at end of file diff --git a/src/styles/theme-vars.css b/src/styles/theme-vars.css new file mode 100644 index 0000000..d6ece0a --- /dev/null +++ b/src/styles/theme-vars.css @@ -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%%; +} \ No newline at end of file diff --git a/src/styles/theme.js b/src/styles/theme.js new file mode 100644 index 0000000..93250c9 --- /dev/null +++ b/src/styles/theme.js @@ -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); \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts index 9db64c3..475894a 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -6,6 +6,19 @@ import { componentTagger } from "lovable-tagger"; export default defineConfig(({ mode }) => { const env = loadEnv(mode, process.cwd(), ""); + const themeName = env.VITE_THEME || "default"; + const themeColors: Record = { + 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 colors = themeColors[themeName] || themeColors.default; + + const appName = env.VITE_APP_NAME || (themeName === "dev" ? "Secuird Dev" : themeName === "preprod" ? "Secuird Staging" : "Secuird"); + const favicon = env.VITE_FAVICON || (themeName === "dev" ? "/favicon-dev.svg" : themeName === "preprod" ? "/favicon-staging.svg" : "/favicon.svg"); + + const themeColor = `hsl(${colors.primary})`; + return { server: { host: "::", @@ -15,7 +28,38 @@ export default defineConfig(({ mode }) => { "gatehouse-ui.hawkvelt.tech", ], }, - plugins: [react(), mode === "development" && componentTagger()].filter(Boolean), + plugins: [ + react(), + mode === "development" && componentTagger(), + { + name: "vite-plugin-theme-injection", + transformIndexHtml: { + order: "pre", + handler: (html) => { + return html + .replace(/%VITE_APP_NAME%/g, appName) + .replace(/%VITE_FAVICON%/g, favicon) + .replace(/%VITE_THEME_COLOR%/g, themeColor); + }, + }, + }, + ], + css: { + preprocessorOptions: { + css: { + additionalData: ` + :root { + --theme-primary: ${colors.primary}; + --theme-primary-foreground: 0 0% 100%; + --theme-sidebar-primary: ${colors.sidebarPrimary}; + --theme-sidebar-primary-foreground: 0 0% 100%; + --theme-ring: ${colors.ring}; + --theme-color: hsl(${colors.primary}); + } + `, + }, + }, + }, resolve: { alias: { "@": path.resolve(__dirname, "./src"),