Compare commits
4 Commits
c651c73fe3
...
6a74cd55a0
| Author | SHA1 | Date | |
|---|---|---|---|
| 6a74cd55a0 | |||
|
|
f8154e22ae | ||
|
|
bd8c9fde8b | ||
|
|
7cbbc86ee4 |
@@ -1,3 +1,4 @@
|
||||
/* globals.css */
|
||||
@import "tailwindcss";
|
||||
@import "tw-animate-css";
|
||||
|
||||
@@ -45,7 +46,7 @@
|
||||
|
||||
:root {
|
||||
--radius: 0.625rem;
|
||||
/* Light Mode Palette */
|
||||
/* Light Mode Palette - Retained similar modern, soft feel */
|
||||
--background: oklch(0.99 0 0); /* Very light off-white */
|
||||
--foreground: oklch(0.1 0 0); /* Very dark gray */
|
||||
--card: oklch(0.99 0 0);
|
||||
@@ -84,42 +85,42 @@
|
||||
}
|
||||
|
||||
.dark {
|
||||
/* Dark Mode Palette */
|
||||
--background: oklch(0.12 0 0); /* Very dark charcoal */
|
||||
--foreground: oklch(0.92 0 0); /* Light gray */
|
||||
--card: oklch(0.18 0 0); /* Slightly lighter dark for cards */
|
||||
--card-foreground: oklch(0.92 0 0);
|
||||
--popover: oklch(0.18 0 0);
|
||||
--popover-foreground: oklch(0.92 0 0);
|
||||
--primary: oklch(0.7 0.15 260); /* Brighter, more vibrant indigo for dark mode */
|
||||
--primary-foreground: oklch(0.15 0 0); /* Dark gray for text on primary */
|
||||
--secondary: oklch(0.22 0 0); /* Darker gray */
|
||||
--secondary-foreground: oklch(0.85 0 0); /* Lighter gray */
|
||||
--muted: oklch(0.22 0 0);
|
||||
--muted-foreground: oklch(0.6 0 0); /* Mid-gray */
|
||||
--accent: oklch(0.22 0.03 260); /* Subtle dark hint of primary */
|
||||
--accent-foreground: oklch(0.8 0.05 260); /* Lighter desaturated primary */
|
||||
--destructive: oklch(0.7 0.15 20); /* Brighter red for dark mode */
|
||||
--border: oklch(0.25 0 0); /* Medium dark gray */
|
||||
--input: oklch(0.2 0 0); /* Darker than border */
|
||||
--ring: oklch(0.4 0 0); /* Mid-dark gray */
|
||||
/* Dark Mode Palette - Modernized */
|
||||
--background: oklch(0.18 0 0); /* Slightly lighter dark charcoal */
|
||||
--foreground: oklch(0.88 0 0); /* Soft white/light gray */
|
||||
--card: oklch(0.22 0 0); /* Slightly lighter for cards than background */
|
||||
--card-foreground: oklch(0.88 0 0);
|
||||
--popover: oklch(0.22 0 0);
|
||||
--popover-foreground: oklch(0.88 0 0);
|
||||
--primary: oklch(0.65 0.18 260); /* Brighter, more vibrant indigo */
|
||||
--primary-foreground: oklch(0.18 0 0); /* Darker text on primary */
|
||||
--secondary: oklch(0.28 0 0); /* Medium dark gray */
|
||||
--secondary-foreground: oklch(0.75 0 0); /* Lighter gray */
|
||||
--muted: oklch(0.28 0 0);
|
||||
--muted-foreground: oklch(0.55 0 0); /* Mid-gray */
|
||||
--accent: oklch(0.28 0.04 260); /* Subtle dark hint of primary */
|
||||
--accent-foreground: oklch(0.7 0.06 260); /* Lighter desaturated primary */
|
||||
--destructive: oklch(0.65 0.16 20); /* Brighter red */
|
||||
--border: oklch(0.32 0 0); /* Medium dark gray */
|
||||
--input: oklch(0.28 0 0); /* Darker than border */
|
||||
--ring: oklch(0.5 0 0); /* Mid-dark gray */
|
||||
|
||||
/* Chart Colors (Dark Mode) - Brighter and distinct */
|
||||
--chart-1: oklch(0.7 0.18 270); /* Brighter purple-blue */
|
||||
--chart-2: oklch(0.75 0.15 180); /* Brighter teal */
|
||||
--chart-3: oklch(0.8 0.12 90); /* Brighter muted green-yellow */
|
||||
--chart-4: oklch(0.75 0.16 30); /* Brighter orange-brown */
|
||||
--chart-5: oklch(0.85 0.1 330); /* Brighter muted pink */
|
||||
/* Chart Colors (Dark Mode) - Brighter and distinct for visibility */
|
||||
--chart-1: oklch(0.75 0.2 270); /* Brighter purple-blue */
|
||||
--chart-2: oklch(0.8 0.18 180); /* Brighter teal */
|
||||
--chart-3: oklch(0.85 0.15 90); /* Brighter muted green-yellow */
|
||||
--chart-4: oklch(0.8 0.18 30); /* Brighter orange-brown */
|
||||
--chart-5: oklch(0.9 0.12 330); /* Brighter muted pink */
|
||||
|
||||
/* Sidebar Colors (Dark Mode) */
|
||||
--sidebar: oklch(0.18 0 0); /* Slightly lighter dark for sidebar */
|
||||
--sidebar-foreground: oklch(0.92 0 0);
|
||||
--sidebar-primary: oklch(0.7 0.15 260);
|
||||
--sidebar-primary-foreground: oklch(0.15 0 0);
|
||||
--sidebar-accent: oklch(0.22 0.03 260);
|
||||
--sidebar-accent-foreground: oklch(0.8 0.05 260);
|
||||
--sidebar-border: oklch(0.25 0 0);
|
||||
--sidebar-ring: oklch(0.4 0 0);
|
||||
--sidebar: oklch(0.22 0 0); /* Slightly lighter dark for sidebar */
|
||||
--sidebar-foreground: oklch(0.88 0 0);
|
||||
--sidebar-primary: oklch(0.65 0.18 260);
|
||||
--sidebar-primary-foreground: oklch(0.18 0 0);
|
||||
--sidebar-accent: oklch(0.28 0.04 260);
|
||||
--sidebar-accent-foreground: oklch(0.7 0.06 260);
|
||||
--sidebar-border: oklch(0.32 0 0);
|
||||
--sidebar-ring: oklch(0.5 0 0);
|
||||
}
|
||||
|
||||
@layer base {
|
||||
|
||||
@@ -4,7 +4,8 @@ import { Geist, Geist_Mono } from "next/font/google";
|
||||
import { Toaster } from '@/components/ui/sonner';
|
||||
import { ThemeProvider } from '@/components/ThemeProvider';
|
||||
import { AuthProvider } from '@/context/AuthContext';
|
||||
import { ThemeToggle } from '@/components/ThemeToggle'; // Import ThemeToggle
|
||||
import { HeaderProvider } from '@/context/HeaderContext'; // Import HeaderProvider
|
||||
import Header from '@/components/Header';
|
||||
import "./globals.css";
|
||||
|
||||
const geistSans = Geist({
|
||||
@@ -39,22 +40,20 @@ export default function RootLayout({
|
||||
disableTransitionOnChange
|
||||
>
|
||||
<AuthProvider>
|
||||
{/* ThemeToggle placed in a fixed position */}
|
||||
<div className="top-4 right-4 z-50"> {/* Add these classes for positioning */}
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
|
||||
{children}
|
||||
<footer>
|
||||
<div className="max-w-7xl mx-auto py-8 px-4 sm:px-6 lg:px-8 flex flex-col sm:flex-row justify-between items-center text-sm text-gray-500">
|
||||
<div className="mb-4 sm:mb-0 text-center sm:text-left">
|
||||
© 2025 Logan Cusano. All rights reserved.
|
||||
<HeaderProvider> {/* Wrap with HeaderProvider */}
|
||||
<Header /> {/* Header component now consumes HeaderContext */}
|
||||
{children}
|
||||
<footer>
|
||||
<div className="max-w-7xl mx-auto py-8 px-4 sm:px-6 lg:px-8 flex flex-col sm:flex-row justify-between items-center text-sm text-gray-500">
|
||||
<div className="mb-4 sm:mb-0 text-center sm:text-left">
|
||||
© 2025 Logan Cusano. All rights reserved.
|
||||
</div>
|
||||
<div className="flex space-x-6">
|
||||
<a href="https://git.vpn.cusano.net/logan/drb-frontend/issues" target="_blank" rel="noopener noreferrer" className="hover:text-gray-700">Submit Issues</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex space-x-6">
|
||||
<a href="https://git.vpn.cusano.net/logan/drb-frontend/issues" target="_blank" rel="noopener noreferrer" className="hover:text-gray-700">Submit Issues</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</footer>
|
||||
</HeaderProvider>
|
||||
</AuthProvider>
|
||||
<Toaster />
|
||||
</ThemeProvider>
|
||||
|
||||
@@ -1,30 +1,37 @@
|
||||
// app/nodes/[clientId]/page.tsx
|
||||
"use client";
|
||||
|
||||
import React from 'react';
|
||||
import React, { useEffect } from 'react'; // Import useEffect
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useAuth } from '@/context/AuthContext';
|
||||
import { useHeader } from '@/context/HeaderContext'; // Import useHeader
|
||||
import IndividualClientPage from '@/components/IndividualClientPage';
|
||||
import LoginPage from '@/components/LoginPage';
|
||||
import { UserRoles } from '@/types';
|
||||
import { Button } from '@/components/ui/button';
|
||||
|
||||
const ClientDetailPage: React.FC = () => {
|
||||
const router = useRouter();
|
||||
const params = useParams(); // params can be null or an object with string | string[] values
|
||||
|
||||
const params = useParams();
|
||||
const { user, loading, token, hasPermission, logout } = useAuth();
|
||||
const { setHeaderConfig } = useHeader();
|
||||
|
||||
// Use useEffect to set header configuration on mount and clean up on unmount
|
||||
useEffect(() => {
|
||||
setHeaderConfig({ showBackButton: true });
|
||||
|
||||
return () => {
|
||||
setHeaderConfig({ showBackButton: false });
|
||||
};
|
||||
}, [setHeaderConfig]); // Dependency array: ensure effect runs if setHeaderConfig changes (unlikely)
|
||||
|
||||
|
||||
if (loading) {
|
||||
return <div className="flex items-center justify-center min-h-screen bg-background text-foreground">Loading Authentication...</div>;
|
||||
}
|
||||
|
||||
// Safely extract clientId, handling the case where params might be null or clientId might be undefined
|
||||
const clientId = params?.clientId; // Use optional chaining
|
||||
const clientId = params?.clientId;
|
||||
|
||||
if (!clientId) {
|
||||
// This covers cases where params is null, or clientId property is missing/undefined
|
||||
// or if the URL param isn't properly captured.
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-screen bg-background text-foreground">
|
||||
Client ID not found in URL.
|
||||
@@ -32,15 +39,11 @@ const ClientDetailPage: React.FC = () => {
|
||||
);
|
||||
}
|
||||
|
||||
// Ensure clientIdentifier is a string (use the first element if it's an array)
|
||||
const clientIdentifier = Array.isArray(clientId) ? clientId[0] : clientId;
|
||||
|
||||
if (!user || !token || !hasPermission(UserRoles.MOD)) {
|
||||
return (
|
||||
<div className="min-h-screen bg-background text-foreground font-sans">
|
||||
<header className="flex justify-between items-center p-4 bg-card text-card-foreground shadow-md">
|
||||
<h1 className="text-xl font-bold">Radio App Admin</h1>
|
||||
</header>
|
||||
<main className="p-6">
|
||||
{!user ? (
|
||||
<LoginPage />
|
||||
@@ -56,14 +59,6 @@ const ClientDetailPage: React.FC = () => {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background text-foreground font-sans">
|
||||
<header className="flex justify-between items-center p-4 bg-card text-card-foreground shadow-md">
|
||||
<h1 className="text-xl font-bold">Radio App Admin</h1>
|
||||
<div className="flex items-center space-x-4">
|
||||
<span className="text-sm text-muted-foreground">Logged in as: {user.username} ({user.role})</span>
|
||||
<Button onClick={() => router.push('/')} variant="outline">Back to Management</Button>
|
||||
<Button onClick={logout} variant="outline">Logout</Button>
|
||||
</div>
|
||||
</header>
|
||||
<main className="p-6">
|
||||
<IndividualClientPage clientId={clientIdentifier} token={token} logoutUser={logout} />
|
||||
</main>
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
"use client";
|
||||
import React from 'react';
|
||||
import { AuthProvider } from '@/context/AuthContext';
|
||||
import AppContent from '@/components/AppContent';
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<AuthProvider>
|
||||
<AppContent />
|
||||
</AuthProvider>
|
||||
<AppContent />
|
||||
);
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
"use client";
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useAuth } from '@/context/AuthContext'; // Correct path to useAuth
|
||||
import { useAuth } from '@/context/AuthContext';
|
||||
import LoginPage from '@/components/LoginPage';
|
||||
import BotsManagement from '@/components/BotsManagement';
|
||||
import SystemsManagement from '@/components/SystemsManagement';
|
||||
@@ -21,7 +21,8 @@ const AppContent: React.FC = () => {
|
||||
|
||||
// Display a loading indicator while AuthContext is determining authentication status
|
||||
if (loading) {
|
||||
return <div className="flex items-center justify-center min-h-screen bg-gray-100 dark:bg-gray-900">Loading Authentication...</div>;
|
||||
// Changed this line to use bg-background
|
||||
return <div className="flex items-center justify-center min-h-screen bg-background text-foreground">Loading Authentication...</div>;
|
||||
}
|
||||
|
||||
// Once loading is false, if no user is authenticated, display the LoginPage
|
||||
@@ -32,7 +33,8 @@ const AppContent: React.FC = () => {
|
||||
// If a user is authenticated but lacks the required permission, display an access denied message
|
||||
if (user && !hasPermission(UserRoles.MOD)) {
|
||||
return (
|
||||
<div className="min-h-screen flex flex-col items-center justify-center bg-gray-100 dark:bg-gray-900 text-gray-900 dark:text-gray-100 font-sans p-6">
|
||||
// Changed this line to use bg-background
|
||||
<div className="min-h-screen flex flex-col items-center justify-center bg-background text-foreground font-sans p-6">
|
||||
<h2 className="text-xl font-bold text-red-500 mb-4">Access Denied</h2>
|
||||
<p className="text-lg text-center mb-6">
|
||||
You do not have sufficient permissions to view this page. Your role: {user.role}. Required: {UserRoles.MOD}.
|
||||
@@ -44,14 +46,9 @@ const AppContent: React.FC = () => {
|
||||
|
||||
// If loading is false, and we have a user with MOD permission and a token, render the main app content
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-100 dark:bg-gray-900 text-gray-900 dark:text-gray-100 font-sans">
|
||||
<header className="flex justify-between items-center p-4 bg-white dark:bg-gray-800 shadow-md">
|
||||
<h1 className="text-xl font-bold">Radio App Admin</h1>
|
||||
<div className="flex items-center space-x-4">
|
||||
<span className="text-sm">Logged in as: {user.username} ({user.role})</span>
|
||||
<Button onClick={handleLogoutAndRedirect} variant="outline">Logout</Button>
|
||||
</div>
|
||||
</header>
|
||||
// Changed this line to use bg-background
|
||||
<div className="min-h-screen bg-background text-foreground font-sans">
|
||||
{/* Header is now in layout.tsx */}
|
||||
|
||||
<main className="p-6">
|
||||
<div className="mb-4">
|
||||
|
||||
39
src/components/Header.tsx
Normal file
39
src/components/Header.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
// components/Header.tsx
|
||||
"use client";
|
||||
import React from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useAuth } from '@/context/AuthContext';
|
||||
import { useHeader } from '@/context/HeaderContext'; // Import useHeader hook
|
||||
import { ThemeToggle } from '@/components/ThemeToggle';
|
||||
|
||||
const Header: React.FC = () => {
|
||||
const router = useRouter();
|
||||
const { user, logout } = useAuth();
|
||||
const { config } = useHeader(); // Consume the header config from context
|
||||
|
||||
const handleLogoutAndRedirect = () => {
|
||||
logout();
|
||||
router.push('/');
|
||||
};
|
||||
|
||||
return (
|
||||
<header className="flex justify-between items-center p-4 bg-card text-card-foreground shadow-md">
|
||||
<div className="flex items-center">
|
||||
{config.showBackButton && ( // Use config from context
|
||||
<Button onClick={() => router.back()} variant="outline" className="mr-4">
|
||||
Back
|
||||
</Button>
|
||||
)}
|
||||
<h1 className="text-xl font-bold">Radio App Admin</h1>
|
||||
</div>
|
||||
<div className="flex items-center space-x-4">
|
||||
{user && <span className="text-sm text-muted-foreground">Logged in as: {user.username} ({user.role})</span>}
|
||||
{user && <Button onClick={handleLogoutAndRedirect} variant="outline">Logout</Button>}
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
||||
42
src/context/HeaderContext.tsx
Normal file
42
src/context/HeaderContext.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
// context/HeaderContext.tsx
|
||||
"use client";
|
||||
|
||||
import React, { createContext, useContext, useState, ReactNode, useCallback } from 'react';
|
||||
|
||||
interface HeaderConfig {
|
||||
showBackButton: boolean;
|
||||
// Add other configurable items here, e.g., title, custom elements
|
||||
}
|
||||
|
||||
interface HeaderContextType {
|
||||
config: HeaderConfig;
|
||||
setHeaderConfig: (newConfig: Partial<HeaderConfig>) => void;
|
||||
}
|
||||
|
||||
const defaultHeaderConfig: HeaderConfig = {
|
||||
showBackButton: false,
|
||||
};
|
||||
|
||||
const HeaderContext = createContext<HeaderContextType | undefined>(undefined);
|
||||
|
||||
export const HeaderProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
|
||||
const [config, setConfig] = useState<HeaderConfig>(defaultHeaderConfig);
|
||||
|
||||
const setHeaderConfig = useCallback((newConfig: Partial<HeaderConfig>) => {
|
||||
setConfig(prevConfig => ({ ...prevConfig, ...newConfig }));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<HeaderContext.Provider value={{ config, setHeaderConfig }}>
|
||||
{children}
|
||||
</HeaderContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useHeader = () => {
|
||||
const context = useContext(HeaderContext);
|
||||
if (context === undefined) {
|
||||
throw new Error('useHeader must be used within a HeaderProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
Reference in New Issue
Block a user