Some checks are pending
Build / build (push) Waiting to run
Deploy / deploy (push) Waiting to run
Lint & Type Check / lint (push) Waiting to run
Security Scan / dependency-check (push) Waiting to run
Security Scan / semgrep (push) Waiting to run
Test / test (18.x) (push) Waiting to run
Test / test (20.x) (push) Waiting to run
Auth & SSO - Wire Authentik (auth.aethex.tech) as OIDC PKCE SSO provider - Server-side only flow with HMAC-signed stateless state token - Account linking via authentik_sub in user metadata - AeThex ID connection card in Dashboard connections tab - Unlink endpoint POST /api/auth/authentik/unlink - Fix node:https helper to bypass undici DNS bug on Node 18 - Fix resolv.conf to use 1.1.1.1/8.8.8.8 in container Schema & types - Regenerate database.types.ts from live Supabase schema (23k lines) - Fix 511 TypeScript errors caused by stale 582-line types file - Fix UserProfile import in aethex-database-adapter.ts - Add notifications migration (title, message, read columns) Server fixes - Remove badge_color from achievements seed/upsert (column doesn't exist) - Rename name→title, add slug field in achievements seed - Remove email from all user_profiles select queries (column doesn't exist) - Fix email-based achievement target lookup via auth.admin.listUsers - Add GET /api/projects/:projectId endpoint - Fix import.meta.dirname → fileURLToPath for Node 18 compatibility - Expose VITE_APP_VERSION from package.json at build time Navigation systems - DevPlatformNav: reorganize into Learn/Build grouped dropdowns with descriptions - Migrate all 11 dev-platform pages from main Layout to DevPlatformLayout - Remove dead isDevMode context nav swap from main Layout - EthosLayout: purple-accented tab bar (Library, Artists, Licensing, Settings) with member-only gating and guest CTA — migrate 4 Ethos pages - GameForgeLayout: orange-branded sidebar with Studio section and lock icons for unauthenticated users — migrate GameForge + GameForgeDashboard - SysBar: live latency ping, status dot (green/yellow/red), real version Layout dropdown - Role-gate Admin (owner/admin/founder only) and Internal Docs (+ staff) - Add Internal section label with separator - Fix settings link from /dashboard?tab=profile#settings to /dashboard?tab=settings Project pages - Add ProjectDetail page at /projects/:projectId - Fix ProfilePassport "View mission" link from /projects/new to /projects/:id Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
166 lines
5.7 KiB
TypeScript
166 lines
5.7 KiB
TypeScript
import { Link, useLocation } from "react-router-dom";
|
|
import { cn } from "@/lib/utils";
|
|
import { useAuth } from "@/contexts/AuthContext";
|
|
import {
|
|
Music2,
|
|
Users,
|
|
FileText,
|
|
Settings,
|
|
ChevronLeft,
|
|
Headphones,
|
|
} from "lucide-react";
|
|
|
|
interface EthosLayoutProps {
|
|
children: React.ReactNode;
|
|
}
|
|
|
|
interface NavItem {
|
|
name: string;
|
|
href: string;
|
|
icon: React.ElementType;
|
|
memberOnly?: boolean;
|
|
}
|
|
|
|
const NAV_ITEMS: NavItem[] = [
|
|
{ name: "Library", href: "/ethos/library", icon: Headphones },
|
|
{ name: "Artists", href: "/ethos/artists", icon: Users },
|
|
{ name: "Licensing", href: "/ethos/licensing", icon: FileText, memberOnly: true },
|
|
{ name: "Settings", href: "/ethos/settings", icon: Settings, memberOnly: true },
|
|
];
|
|
|
|
export default function EthosLayout({ children }: EthosLayoutProps) {
|
|
const location = useLocation();
|
|
const { user } = useAuth();
|
|
|
|
const isActive = (href: string) => location.pathname.startsWith(href);
|
|
|
|
return (
|
|
<div style={{ minHeight: "100vh", background: "#050505", color: "#e0e0e0" }}>
|
|
{/* Top bar */}
|
|
<header style={{
|
|
position: "sticky", top: 0, zIndex: 50,
|
|
background: "rgba(5,5,5,0.97)",
|
|
borderBottom: "1px solid rgba(168,85,247,0.15)",
|
|
backdropFilter: "blur(12px)",
|
|
}}>
|
|
{/* Purple accent stripe */}
|
|
<div style={{ height: 2, background: "linear-gradient(90deg, #7c3aed 0%, #a855f7 50%, #7c3aed 100%)", opacity: 0.6 }} />
|
|
|
|
<div style={{
|
|
maxWidth: 1200, margin: "0 auto",
|
|
padding: "0 24px",
|
|
display: "flex", alignItems: "center",
|
|
height: 52, gap: 0,
|
|
}}>
|
|
{/* Back to main site */}
|
|
<Link
|
|
to="/"
|
|
style={{
|
|
display: "flex", alignItems: "center", gap: 6,
|
|
color: "rgba(168,85,247,0.5)", textDecoration: "none",
|
|
fontSize: 11, fontFamily: "monospace", letterSpacing: 1,
|
|
marginRight: 24, flexShrink: 0,
|
|
transition: "color 0.2s",
|
|
}}
|
|
onMouseEnter={e => (e.currentTarget.style.color = "rgba(168,85,247,0.9)")}
|
|
onMouseLeave={e => (e.currentTarget.style.color = "rgba(168,85,247,0.5)")}
|
|
>
|
|
<ChevronLeft className="h-3.5 w-3.5" />
|
|
aethex.dev
|
|
</Link>
|
|
|
|
{/* Brand */}
|
|
<Link
|
|
to="/ethos/library"
|
|
style={{
|
|
display: "flex", alignItems: "center", gap: 8,
|
|
textDecoration: "none", marginRight: 40, flexShrink: 0,
|
|
}}
|
|
>
|
|
<div style={{
|
|
width: 28, height: 28,
|
|
background: "linear-gradient(135deg, #7c3aed, #a855f7)",
|
|
borderRadius: "50%",
|
|
display: "flex", alignItems: "center", justifyContent: "center",
|
|
}}>
|
|
<Music2 className="h-3.5 w-3.5 text-white" />
|
|
</div>
|
|
<span style={{
|
|
fontFamily: "monospace", fontWeight: 700, fontSize: 13,
|
|
letterSpacing: 3, color: "#a855f7", textTransform: "uppercase",
|
|
}}>
|
|
Ethos Guild
|
|
</span>
|
|
</Link>
|
|
|
|
{/* Nav tabs */}
|
|
<nav style={{ display: "flex", alignItems: "stretch", gap: 2, flex: 1, height: "100%" }}>
|
|
{NAV_ITEMS.filter(item => !item.memberOnly || user).map(item => (
|
|
<Link
|
|
key={item.href}
|
|
to={item.href}
|
|
style={{
|
|
display: "flex", alignItems: "center", gap: 6,
|
|
padding: "0 16px",
|
|
textDecoration: "none",
|
|
fontFamily: "monospace", fontSize: 11, letterSpacing: 1,
|
|
textTransform: "uppercase",
|
|
color: isActive(item.href) ? "#a855f7" : "rgba(255,255,255,0.4)",
|
|
borderBottom: isActive(item.href) ? "2px solid #a855f7" : "2px solid transparent",
|
|
transition: "color 0.2s, border-color 0.2s",
|
|
marginBottom: -1,
|
|
}}
|
|
onMouseEnter={e => {
|
|
if (!isActive(item.href)) e.currentTarget.style.color = "rgba(168,85,247,0.8)";
|
|
}}
|
|
onMouseLeave={e => {
|
|
if (!isActive(item.href)) e.currentTarget.style.color = "rgba(255,255,255,0.4)";
|
|
}}
|
|
>
|
|
<item.icon className="h-3.5 w-3.5" />
|
|
{item.name}
|
|
{item.memberOnly && (
|
|
<span style={{
|
|
fontSize: 8, padding: "1px 4px",
|
|
background: "rgba(168,85,247,0.15)",
|
|
color: "#a855f7", borderRadius: 2,
|
|
letterSpacing: 1,
|
|
}}>
|
|
MEMBER
|
|
</span>
|
|
)}
|
|
</Link>
|
|
))}
|
|
</nav>
|
|
|
|
{/* Sign in prompt for guests */}
|
|
{!user && (
|
|
<Link
|
|
to="/login"
|
|
style={{
|
|
fontFamily: "monospace", fontSize: 10, letterSpacing: 2,
|
|
color: "#a855f7", textDecoration: "none",
|
|
border: "1px solid rgba(168,85,247,0.4)",
|
|
padding: "5px 12px",
|
|
transition: "all 0.2s",
|
|
}}
|
|
onMouseEnter={e => {
|
|
e.currentTarget.style.background = "rgba(168,85,247,0.1)";
|
|
e.currentTarget.style.borderColor = "rgba(168,85,247,0.7)";
|
|
}}
|
|
onMouseLeave={e => {
|
|
e.currentTarget.style.background = "transparent";
|
|
e.currentTarget.style.borderColor = "rgba(168,85,247,0.4)";
|
|
}}
|
|
>
|
|
JOIN GUILD
|
|
</Link>
|
|
)}
|
|
</div>
|
|
</header>
|
|
|
|
{/* Page content */}
|
|
<main>{children}</main>
|
|
</div>
|
|
);
|
|
}
|