Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions apps/web/src/app/dashboard/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
import { UserButton } from "@clerk/nextjs";
import Link from "next/link";
import { ThemeToggle } from "@/components/theme-toggle";

export default function DashboardLayout({ children }: { children: React.ReactNode }) {
return (
<div className="min-h-screen bg-gray-50 flex flex-col">
<header className="bg-white border-b border-gray-200 h-16 flex items-center px-8">
<h1 className="text-xl font-bold tracking-tight text-gray-900 mr-12">ForgeLens</h1>
<div className="min-h-screen bg-[var(--background)] flex flex-col">
<header className="bg-[var(--card-bg)] border-b border-[var(--card-border)] h-16 flex items-center px-8">
<h1 className="text-xl font-bold tracking-tight text-[var(--text-primary)] mr-12">ForgeLens</h1>

<nav className="flex-1 flex gap-6 text-sm font-medium">
<Link href="/dashboard" className="text-gray-600 hover:text-black transition-colors">Overview</Link>
<Link href="/dashboard" className="text-[var(--text-secondary)] hover:text-[var(--text-primary)] transition-colors">Overview</Link>
<Link href="/dashboard/insights" className="text-indigo-600 hover:text-indigo-900 transition-colors flex items-center gap-2">
✨ AI Insights
</Link>
</nav>

<UserButton />
<div className="flex items-center gap-3">
<ThemeToggle />
<UserButton />
</div>
</header>
<main className="flex-1 max-w-7xl w-full mx-auto p-8">
{children}
Expand Down
23 changes: 18 additions & 5 deletions apps/web/src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@
:root {
--background: #ffffff;
--foreground: #171717;
--card-bg: #ffffff;
--card-border: #e5e7eb;
--text-primary: #111827;
--text-secondary: #6b7280;
--text-muted: #9ca3af;
--surface: #f9fafb;
--surface-hover: #f3f4f6;
}

@theme inline {
Expand All @@ -12,15 +19,21 @@
--font-mono: var(--font-geist-mono);
}

@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
}
.dark {
--background: #0a0a0a;
--foreground: #ededed;
--card-bg: #1a1a2e;
--card-border: #2d2d44;
--text-primary: #f9fafb;
--text-secondary: #d1d5db;
--text-muted: #9ca3af;
--surface: #111827;
--surface-hover: #1f2937;
}

body {
background: var(--background);
color: var(--foreground);
font-family: Arial, Helvetica, sans-serif;
transition: background-color 0.2s ease, color 0.2s ease;
}
101 changes: 101 additions & 0 deletions apps/web/src/components/theme-toggle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
"use client";

import { useState, useEffect } from "react";

type Theme = "light" | "dark" | "system";

function getSystemPreference(): "light" | "dark" {
if (typeof window === "undefined") return "light";
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
}

function applyTheme(theme: Theme) {
const resolved = theme === "system" ? getSystemPreference() : theme;
const root = document.documentElement;
if (resolved === "dark") {
root.classList.add("dark");
} else {
root.classList.remove("dark");
}
}

export function ThemeToggle() {
const [theme, setTheme] = useState<Theme>("system");
const [mounted, setMounted] = useState(false);

useEffect(() => {
const stored = localStorage.getItem("forgelens-theme") as Theme | null;
if (stored && ["light", "dark", "system"].includes(stored)) {
setTheme(stored);
applyTheme(stored);
} else {
setTheme("system");
applyTheme("system");
}
setMounted(true);
}, []);

useEffect(() => {
if (!mounted) return;

if (theme === "system") {
const mq = window.matchMedia("(prefers-color-scheme: dark)");
const handler = () => applyTheme("system");
mq.addEventListener("change", handler);
applyTheme("system");
return () => mq.removeEventListener("change", handler);
} else {
applyTheme(theme);
}
}, [theme, mounted]);

const cycleTheme = () => {
const themes: Theme[] = ["light", "dark", "system"];
const next = themes[(themes.indexOf(theme) + 1) % themes.length];
setTheme(next);
localStorage.setItem("forgelens-theme", next);
};

if (!mounted) {
return (
<button className="p-2 rounded-lg border border-gray-200 opacity-50" type="button">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<circle cx="12" cy="12" r="5" />
</svg>
</button>
);
}

return (
<button
onClick={cycleTheme}
className="p-2 rounded-lg border border-[var(--card-border)] hover:bg-[var(--surface-hover)] transition-colors"
title={`Theme: ${theme}. Click to switch.`}
type="button"
>
{theme === "system" ? (
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<rect x="2" y="3" width="20" height="14" rx="2" ry="2" />
<line x1="8" y1="21" x2="16" y2="21" />
<line x1="12" y1="17" x2="12" y2="21" />
</svg>
) : theme === "dark" ? (
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
</svg>
) : (
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<circle cx="12" cy="12" r="5" />
<line x1="12" y1="1" x2="12" y2="3" />
<line x1="12" y1="21" x2="12" y2="23" />
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
<line x1="1" y1="12" x2="3" y2="12" />
<line x1="21" y1="12" x2="23" y2="12" />
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
</svg>
)}
</button>
);
}
Loading