feat: add visual theme indicators and fix logo colors for dev/staging environments

This commit is contained in:
2026-04-26 17:06:45 +09:30
parent d8828d64f2
commit 9cba8b4829
6 changed files with 139 additions and 22 deletions
+19 -9
View File
@@ -10,6 +10,9 @@ interface SecuirdLogoProps {
* Secuird Logo - Abstract gate/doorway mark
* Represents controlled entry and policy enforcement
* Two vertical pillars forming a gateway with negative space
*
* Uses inline styles for theme colors to ensure they work correctly
* across all theme configurations (default, dev, preprod)
*/
export function SecuirdLogo({
size = "md",
@@ -22,31 +25,38 @@ export function SecuirdLogo({
lg: "w-12 h-12",
};
const bgClasses = {
default: "bg-primary",
light: "bg-sidebar-primary",
// Use inline styles for theme colors to ensure they work at runtime
const getBgStyle = () => {
if (variant === "light") {
return {
backgroundColor: "hsl(var(--theme-sidebar-primary, 173 65% 36%))",
color: "hsl(var(--theme-sidebar-primary-foreground, 0 0% 100%))"
};
}
return {
backgroundColor: "hsl(var(--theme-primary, 173 65% 36%))",
color: "hsl(var(--theme-primary-foreground, 0 0% 100%))"
};
};
const iconColor = variant === "light"
? "text-sidebar-primary-foreground"
: "text-primary-foreground";
const bgStyle = getBgStyle();
return (
<div
className={cn(
"rounded-lg flex items-center justify-center flex-shrink-0",
sizeClasses[size],
bgClasses[variant],
className
)}
style={bgStyle}
>
<svg
viewBox="0 0 24 24"
fill="none"
className={cn(
iconColor,
size === "sm" ? "w-4 h-4" : size === "md" ? "w-5 h-5" : "w-6 h-6"
)}
style={{ color: bgStyle.color }}
>
{/* Abstract gate - two pillars with archway */}
<path
@@ -0,0 +1,42 @@
import { config } from "@/config";
interface ThemeIndicatorProps {
showBanner?: boolean;
className?: string;
}
/**
* Visual theme indicator - shows environment name and color
* Appears as a banner in dev/preprod modes
*/
export function ThemeIndicator({ showBanner = true, className }: ThemeIndicatorProps) {
const theme = config.theme.name;
const appName = config.app.name;
// Only show for non-default themes
if (theme === "default") {
return null;
}
const themeLabels: Record<string, string> = {
dev: "Development Environment",
preprod: "Staging / Pre-Production",
};
const themeLabel = themeLabels[theme] || theme;
if (showBanner) {
return (
<div className="env-banner">
{themeLabel} {appName}
</div>
);
}
return (
<span className={`theme-badge ${className || ""}`}>
<span className="env-indicator" />
{themeLabel}
</span>
);
}
@@ -3,11 +3,13 @@ import { SidebarProvider } from "@/components/ui/sidebar";
import { AppSidebar } from "@/components/navigation/AppSidebar";
import { TopBar } from "@/components/navigation/TopBar";
import ApiDevTools from "@/components/dev/ApiDevTools";
import { ThemeIndicator } from "@/components/branding/ThemeIndicator";
export default function AuthenticatedLayout() {
return (
<SidebarProvider>
<div className="min-h-screen flex w-full bg-background">
<ThemeIndicator />
<div className="min-h-screen flex w-full bg-background pt-8">
<AppSidebar />
<div className="flex-1 flex flex-col min-w-0">
<TopBar />
+12 -8
View File
@@ -1,16 +1,17 @@
import { Link, Outlet, useLocation } from "react-router-dom";
import { SecuirdLogo as GatehouseLogo } from "@/components/branding/SecuirdLogo";
import { ThemeIndicator } from "@/components/branding/ThemeIndicator";
import { Button } from "@/components/ui/button";
import { config } from "@/config";
import { cn } from "@/lib/utils";
import {
Shield,
Key,
CreditCard,
Play,
Lock,
Menu,
X
Shield,
Key,
CreditCard,
Play,
Lock,
Menu,
X
} from "lucide-react";
import { useState } from "react";
@@ -29,8 +30,11 @@ const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
return (
<div className="min-h-screen bg-background flex flex-col">
{/* Theme indicator banner for dev/staging */}
<ThemeIndicator />
{/* Header */}
<header className="sticky top-0 z-50 w-full border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
<header className="sticky top-8 z-50 w-full border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60 marketing-header">
<nav className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="flex h-16 items-center justify-between">
{/* Logo */}
+6 -2
View File
@@ -1,15 +1,19 @@
import { Outlet, Link } from "react-router-dom";
import { SecuirdLogo } from "@/components/branding/SecuirdLogo";
import { ThemeIndicator } from "@/components/branding/ThemeIndicator";
import { config } from "@/config";
export default function PublicLayout() {
return (
<div className="min-h-screen bg-background flex flex-col">
{/* Theme indicator banner for dev/staging */}
<ThemeIndicator />
{/* Subtle gradient background */}
<div className="fixed inset-0 bg-gradient-to-br from-background via-background to-secondary/30 pointer-events-none" />
{/* Header */}
<header className="relative z-10 w-full py-6 px-4">
{/* Header with theme-colored border */}
<header className="relative z-10 w-full py-6 px-4 marketing-header">
<div className="max-w-md mx-auto">
<Link to="/" className="flex items-center gap-2.5 justify-center">
<SecuirdLogo size="md" />
+57 -2
View File
@@ -1,7 +1,5 @@
@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;
@@ -153,6 +151,40 @@
}
@layer components {
/* Theme-specific branding enhancements */
/* Marketing header - theme colored */
.marketing-header {
@apply border-b transition-colors duration-300;
background-color: hsl(var(--theme-primary) / 0.03);
border-color: hsl(var(--theme-primary) / 0.2);
}
/* Theme indicator badge */
.theme-badge {
@apply inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium;
background-color: hsl(var(--theme-primary) / 0.15);
color: hsl(var(--theme-primary));
}
/* Theme-colored sidebar header */
.sidebar-header-theme {
background: linear-gradient(135deg,
hsl(var(--theme-sidebar-primary)) 0%,
hsl(var(--theme-sidebar-primary) / 0.85) 100%
);
}
/* Theme-colored buttons */
.btn-theme {
@apply bg-primary text-primary-foreground hover:opacity-90 transition-opacity;
}
/* Theme ring focus */
.focus-ring:focus-visible {
@apply outline-none ring-2 ring-ring ring-offset-2 ring-offset-background;
}
/* Auth card for public pages */
.auth-card {
@apply bg-card rounded-xl p-8 shadow-card border border-border;
@@ -204,6 +236,29 @@
.page-description {
@apply text-muted-foreground mt-1;
}
/* Dev/Staging environment banner */
.env-banner {
@apply fixed top-0 left-0 right-0 z-50 px-3 py-1 text-center text-xs font-bold uppercase tracking-wider;
background: linear-gradient(90deg,
hsl(var(--theme-primary)) 0%,
hsl(var(--theme-primary) / 0.8) 50%,
hsl(var(--theme-primary)) 100%
);
color: hsl(var(--theme-primary-foreground));
}
/* Additional env indicator on page */
.env-indicator {
@apply inline-block w-2 h-2 rounded-full ml-2;
background-color: hsl(var(--theme-primary));
animation: pulse 2s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
}
@layer utilities {