+9
-6
@@ -1,6 +1,7 @@
|
||||
import type { Metadata } from "next";
|
||||
import "./globals.css";
|
||||
import { Providers } from "@/components/Providers";
|
||||
import { AuthProvider } from "@/components/AuthProvider";
|
||||
import { Sidebar } from "@/components/Sidebar";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
@@ -17,12 +18,14 @@ export default function RootLayout({
|
||||
<html lang="en" className="dark">
|
||||
<body className="bg-background text-text-primary">
|
||||
<Providers>
|
||||
<div className="flex h-screen overflow-hidden">
|
||||
<Sidebar />
|
||||
<main className="flex-1 overflow-y-auto">
|
||||
{children}
|
||||
</main>
|
||||
</div>
|
||||
<AuthProvider>
|
||||
<div className="flex h-screen overflow-hidden">
|
||||
<Sidebar />
|
||||
<main className="flex-1 overflow-y-auto">
|
||||
{children}
|
||||
</main>
|
||||
</div>
|
||||
</AuthProvider>
|
||||
</Providers>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
"use client";
|
||||
|
||||
import { createContext, useContext, useEffect, useState, ReactNode } from "react";
|
||||
|
||||
export interface User {
|
||||
user_id: string;
|
||||
email: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface AuthContextType {
|
||||
user: User | null;
|
||||
authEnabled: boolean;
|
||||
}
|
||||
|
||||
const AuthContext = createContext<AuthContextType>({ user: null, authEnabled: false });
|
||||
|
||||
export function useAuth() {
|
||||
return useContext(AuthContext);
|
||||
}
|
||||
|
||||
export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
const [user, setUser] = useState<User | null>(null);
|
||||
const [authEnabled, setAuthEnabled] = useState(false);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
fetch("/auth/me", { credentials: "include" })
|
||||
.then(async (res) => {
|
||||
if (res.status === 401) {
|
||||
window.location.href = "/auth/login";
|
||||
return;
|
||||
}
|
||||
const data = await res.json();
|
||||
if (data.auth_enabled === false) {
|
||||
setAuthEnabled(false);
|
||||
} else {
|
||||
setAuthEnabled(true);
|
||||
setUser(data as User);
|
||||
}
|
||||
setLoading(false);
|
||||
})
|
||||
.catch(() => {
|
||||
// Backend unreachable — don't block the UI
|
||||
setLoading(false);
|
||||
});
|
||||
}, []);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex h-screen items-center justify-center bg-background">
|
||||
<div className="h-8 w-8 animate-spin rounded-full border-2 border-border border-t-accent" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={{ user, authEnabled }}>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { clsx } from "clsx";
|
||||
import { useAuth } from "@/components/AuthProvider";
|
||||
|
||||
interface NavItem {
|
||||
href: string;
|
||||
@@ -33,6 +34,7 @@ const navItems: NavItem[] = [
|
||||
|
||||
export function Sidebar() {
|
||||
const pathname = usePathname();
|
||||
const { user, authEnabled } = useAuth();
|
||||
|
||||
return (
|
||||
<aside className="flex h-screen w-60 flex-col border-r border-border bg-surface">
|
||||
@@ -71,7 +73,23 @@ export function Sidebar() {
|
||||
</nav>
|
||||
|
||||
<div className="border-t border-border px-4 py-3">
|
||||
<p className="text-xs text-text-secondary">KeyManager v1.0</p>
|
||||
{authEnabled && user && (
|
||||
<div className="mb-3">
|
||||
<p className="truncate text-sm font-medium text-text-primary">{user.name || user.email}</p>
|
||||
<p className="truncate text-xs text-text-secondary">{user.email}</p>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-xs text-text-secondary">KeyManager v1.0</p>
|
||||
{authEnabled && user && (
|
||||
<a
|
||||
href="/auth/logout"
|
||||
className="text-xs text-text-secondary transition-colors hover:text-danger"
|
||||
>
|
||||
Logout
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
|
||||
@@ -58,6 +58,7 @@ class ApiError extends Error {
|
||||
|
||||
async function request<T>(path: string, options?: RequestInit): Promise<T> {
|
||||
const res = await fetch(`/api${path}`, {
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...options?.headers,
|
||||
|
||||
@@ -10,6 +10,10 @@ const nextConfig: NextConfig = {
|
||||
source: "/api/:path*",
|
||||
destination: `${apiUrl}/api/:path*`,
|
||||
},
|
||||
{
|
||||
source: "/auth/:path*",
|
||||
destination: `${apiUrl}/auth/:path*`,
|
||||
},
|
||||
{
|
||||
source: "/install",
|
||||
destination: `${apiUrl}/install`,
|
||||
|
||||
Reference in New Issue
Block a user