Refactor: Split Sidebar.tsx into smaller components
Refactored the Sidebar component into smaller, more manageable files to improve code organization and maintainability. Fix sidebar auto-expansion issue Ensured the sidebar's collapsed state is maintained when navigating between pages.
This commit is contained in:
+107
-129
@@ -1,137 +1,115 @@
|
||||
|
||||
import { Toaster } from "@/components/ui/toaster";
|
||||
import { Toaster as Sonner } from "@/components/ui/sonner";
|
||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
|
||||
import { useEffect, useState } from "react";
|
||||
import { authService } from "@/services/authService";
|
||||
import { ThemeProvider } from "@/contexts/ThemeContext";
|
||||
import { LanguageProvider } from "@/contexts/LanguageContext";
|
||||
import React from 'react';
|
||||
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { ThemeProvider } from './contexts/ThemeContext';
|
||||
import { LanguageProvider } from './contexts/LanguageContext';
|
||||
import { SidebarProvider } from './contexts/SidebarContext';
|
||||
import { ErrorBoundary } from './components/ErrorBoundary';
|
||||
import { Toaster } from './components/ui/sonner';
|
||||
import { authService } from './services/authService';
|
||||
|
||||
import Login from "./pages/Login";
|
||||
import Dashboard from "./pages/Dashboard";
|
||||
import ServiceDetail from "./pages/ServiceDetail";
|
||||
import Settings from "./pages/Settings";
|
||||
import Profile from "./pages/Profile";
|
||||
import NotFound from "./pages/NotFound";
|
||||
import SslDomain from "./pages/SslDomain";
|
||||
import ScheduleIncident from "./pages/ScheduleIncident";
|
||||
import OperationalPage from "./pages/OperationalPage";
|
||||
import PublicStatusPage from "./pages/PublicStatusPage";
|
||||
|
||||
// Create a Protected route component
|
||||
const ProtectedRoute = ({ children }: { children: React.ReactNode }) => {
|
||||
const isAuthenticated = authService.isAuthenticated();
|
||||
|
||||
if (!isAuthenticated) {
|
||||
return <Navigate to="/login" replace />;
|
||||
}
|
||||
|
||||
return <>{children}</>;
|
||||
};
|
||||
// Pages
|
||||
import Index from './pages/Index';
|
||||
import Login from './pages/Login';
|
||||
import Dashboard from './pages/Dashboard';
|
||||
import ServiceDetail from './pages/ServiceDetail';
|
||||
import Settings from './pages/Settings';
|
||||
import Profile from './pages/Profile';
|
||||
import SslDomain from './pages/SslDomain';
|
||||
import ScheduleIncident from './pages/ScheduleIncident';
|
||||
import OperationalPage from './pages/OperationalPage';
|
||||
import PublicStatusPage from './pages/PublicStatusPage';
|
||||
import NotFound from './pages/NotFound';
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
const App = () => {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
// Check authentication status when the app loads
|
||||
const checkAuth = async () => {
|
||||
try {
|
||||
// Just check the auth state
|
||||
authService.isAuthenticated();
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
checkAuth();
|
||||
}, []);
|
||||
|
||||
if (isLoading) {
|
||||
return <div className="flex items-center justify-center h-screen bg-background text-foreground">Loading...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ThemeProvider>
|
||||
<LanguageProvider>
|
||||
<TooltipProvider>
|
||||
<Toaster />
|
||||
<Sonner />
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route path="/login" element={<Login />} />
|
||||
<Route path="/" element={<Navigate to="/dashboard" replace />} />
|
||||
<Route
|
||||
path="/dashboard"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<Dashboard />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/service/:id"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<ServiceDetail />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/settings"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<Settings />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/profile"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<Profile />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/ssl-domain"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<SslDomain />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/schedule-incident"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<ScheduleIncident />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/operational-page"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<OperationalPage />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
{/* Public status page route */}
|
||||
<Route path="/status/:slug" element={<PublicStatusPage />} />
|
||||
{/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
|
||||
<Route path="*" element={<NotFound />} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
</TooltipProvider>
|
||||
</LanguageProvider>
|
||||
</ThemeProvider>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
// Protected Route Component
|
||||
const ProtectedRoute = ({ children }: { children: React.ReactNode }) => {
|
||||
const isAuthenticated = authService.isAuthenticated();
|
||||
return isAuthenticated ? <>{children}</> : <Navigate to="/login" replace />;
|
||||
};
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ThemeProvider>
|
||||
<LanguageProvider>
|
||||
<SidebarProvider>
|
||||
<Router>
|
||||
<div className="min-h-screen bg-background text-foreground">
|
||||
<Routes>
|
||||
<Route path="/" element={<Index />} />
|
||||
<Route path="/login" element={<Login />} />
|
||||
<Route
|
||||
path="/dashboard"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<Dashboard />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/service/:id"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<ServiceDetail />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/settings"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<Settings />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/profile"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<Profile />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/ssl-domain"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<SslDomain />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/schedule-incident"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<ScheduleIncident />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/operational-page"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<OperationalPage />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route path="/status/:pageSlug" element={<PublicStatusPage />} />
|
||||
<Route path="*" element={<NotFound />} />
|
||||
</Routes>
|
||||
<Toaster />
|
||||
</div>
|
||||
</Router>
|
||||
</SidebarProvider>
|
||||
</LanguageProvider>
|
||||
</ThemeProvider>
|
||||
</QueryClientProvider>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
@@ -0,0 +1,49 @@
|
||||
|
||||
import React from 'react';
|
||||
|
||||
interface ErrorBoundaryState {
|
||||
hasError: boolean;
|
||||
error?: Error;
|
||||
}
|
||||
|
||||
interface ErrorBoundaryProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
||||
constructor(props: ErrorBoundaryProps) {
|
||||
super(props);
|
||||
this.state = { hasError: false };
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
|
||||
return { hasError: true, error };
|
||||
}
|
||||
|
||||
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
||||
console.error('ErrorBoundary caught an error:', error, errorInfo);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-background">
|
||||
<div className="text-center p-6">
|
||||
<h2 className="text-2xl font-bold text-destructive mb-4">Something went wrong</h2>
|
||||
<p className="text-muted-foreground mb-4">
|
||||
{this.state.error?.message || 'An unexpected error occurred'}
|
||||
</p>
|
||||
<button
|
||||
onClick={() => window.location.reload()}
|
||||
className="px-4 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90"
|
||||
>
|
||||
Reload Page
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
@@ -1,140 +1,22 @@
|
||||
import { Globe, Boxes, Radar, Calendar, BarChart2, LineChart, FileText, Settings, User, UserCog, Bell, FileClock, Database, RefreshCw, Info, ChevronDown, BookOpen } from "lucide-react";
|
||||
|
||||
import React from "react";
|
||||
import { useTheme } from "@/contexts/ThemeContext";
|
||||
import { Link, useLocation } from "react-router-dom";
|
||||
import { useState, useEffect } from "react";
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { useLanguage } from "@/contexts/LanguageContext";
|
||||
import { SidebarHeader } from "./sidebar/SidebarHeader";
|
||||
import { MainNavigation } from "./sidebar/MainNavigation";
|
||||
import { SettingsPanel } from "./sidebar/SettingsPanel";
|
||||
|
||||
interface SidebarProps {
|
||||
collapsed: boolean;
|
||||
}
|
||||
|
||||
export const Sidebar = ({
|
||||
collapsed
|
||||
}: SidebarProps) => {
|
||||
const {
|
||||
theme
|
||||
} = useTheme();
|
||||
const {
|
||||
t
|
||||
} = useLanguage();
|
||||
const location = useLocation();
|
||||
const [activeSettingsItem, setActiveSettingsItem] = useState<string | null>("general");
|
||||
const [settingsPanelOpen, setSettingsPanelOpen] = useState(false);
|
||||
|
||||
// Update active settings item based on URL
|
||||
useEffect(() => {
|
||||
if (location.pathname === '/settings') {
|
||||
const params = new URLSearchParams(location.search);
|
||||
const panel = params.get('panel');
|
||||
if (panel) {
|
||||
setActiveSettingsItem(panel);
|
||||
}
|
||||
}
|
||||
}, [location]);
|
||||
export const Sidebar = ({ collapsed }: SidebarProps) => {
|
||||
const { theme } = useTheme();
|
||||
|
||||
const handleSettingsItemClick = (item: string) => {
|
||||
setActiveSettingsItem(item);
|
||||
};
|
||||
|
||||
const getMenuItemClasses = (isActive: boolean) => {
|
||||
return `p-2 ${isActive ? theme === 'dark' ? 'bg-[#1a1a1a]' : 'bg-sidebar-accent' : `hover:${theme === 'dark' ? 'bg-[#1a1a1a]' : 'bg-sidebar-accent'}`} rounded-lg flex items-center`;
|
||||
};
|
||||
|
||||
// New larger icon size for the main menu
|
||||
const mainIconSize = "h-6 w-6";
|
||||
|
||||
return <div className={`${collapsed ? 'w-16' : 'w-64'} ${theme === 'dark' ? 'bg-[#121212] border-[#1e1e1e]' : 'bg-sidebar border-sidebar-border'} border-r flex flex-col transition-all duration-300 h-full`}>
|
||||
<div className={`p-4 ${theme === 'dark' ? 'border-[#1e1e1e]' : 'border-sidebar-border'} border-b flex items-center ${collapsed ? 'justify-center' : ''}`}>
|
||||
<div className="h-8 w-8 bg-green-500 rounded flex items-center justify-center mr-2">
|
||||
<span className="text-white font-bold">C</span>
|
||||
</div>
|
||||
{!collapsed && <h1 className="text-xl font-semibold">CheckCle App</h1>}
|
||||
</div>
|
||||
|
||||
<nav className="my-2 mx-1 py-1 px-1">
|
||||
<Link to="/dashboard" className={`${collapsed ? 'p-3' : 'p-2 pl-3'} mb-1 rounded-lg ${location.pathname === '/dashboard' ? theme === 'dark' ? 'bg-gray-800' : 'bg-sidebar-accent' : `hover:${theme === 'dark' ? 'bg-gray-800' : 'bg-sidebar-accent'}`} flex items-center ${collapsed ? 'justify-center' : ''} transition-colors duration-200`}>
|
||||
<Globe className={`${mainIconSize} text-purple-400`} />
|
||||
{!collapsed && <span className="ml-2.5 font-medium text-foreground tracking-wide text-[15px]">{t("uptimeMonitoring")}</span>}
|
||||
</Link>
|
||||
<div className={`${collapsed ? 'p-3' : 'p-2 pl-3'} mb-1 rounded-lg hover:${theme === 'dark' ? 'bg-gray-800' : 'bg-sidebar-accent'} flex items-center ${collapsed ? 'justify-center' : ''} transition-colors duration-200`}>
|
||||
<Boxes className={`${mainIconSize} text-blue-400`} />
|
||||
{!collapsed && <span className="ml-2.5 font-medium text-foreground tracking-wide text-[15px]">{t("instanceMonitoring")}</span>}
|
||||
</div>
|
||||
<Link to="/ssl-domain" className={`${collapsed ? 'p-3' : 'p-2 pl-3'} mb-1 rounded-lg ${location.pathname === '/ssl-domain' ? theme === 'dark' ? 'bg-gray-800' : 'bg-sidebar-accent' : `hover:${theme === 'dark' ? 'bg-gray-800' : 'bg-sidebar-accent'}`} flex items-center ${collapsed ? 'justify-center' : ''} transition-colors duration-200`}>
|
||||
<Radar className={`${mainIconSize} text-cyan-400`} />
|
||||
{!collapsed && <span className="ml-2.5 font-medium text-foreground tracking-wide text-[15px]">{t("sslDomain")}</span>}
|
||||
</Link>
|
||||
<Link to="/schedule-incident" className={`${collapsed ? 'p-3' : 'p-2 pl-3'} mb-1 rounded-lg ${location.pathname === '/schedule-incident' ? theme === 'dark' ? 'bg-gray-800' : 'bg-sidebar-accent' : `hover:${theme === 'dark' ? 'bg-gray-800' : 'bg-sidebar-accent'}`} flex items-center ${collapsed ? 'justify-center' : ''} transition-colors duration-200`}>
|
||||
<Calendar className={`${mainIconSize} text-emerald-400`} />
|
||||
{!collapsed && <span className="ml-2.5 font-medium text-foreground tracking-wide text-[15px]">{t("scheduleIncident")}</span>}
|
||||
</Link>
|
||||
<Link to="/operational-page" className={`${collapsed ? 'p-3' : 'p-2 pl-3'} mb-1 rounded-lg ${location.pathname === '/operational-page' ? theme === 'dark' ? 'bg-gray-800' : 'bg-sidebar-accent' : `hover:${theme === 'dark' ? 'bg-gray-800' : 'bg-sidebar-accent'}`} flex items-center ${collapsed ? 'justify-center' : ''} transition-colors duration-200`}>
|
||||
<BarChart2 className={`${mainIconSize} text-amber-400`} />
|
||||
{!collapsed && <span className="ml-2.5 font-medium text-foreground tracking-wide text-[15px]">{t("operationalPage")}</span>}
|
||||
</Link>
|
||||
<div className={`${collapsed ? 'p-3' : 'p-2 pl-3'} mb-1 rounded-lg hover:${theme === 'dark' ? 'bg-gray-800' : 'bg-sidebar-accent'} flex items-center ${collapsed ? 'justify-center' : ''} transition-colors duration-200`}>
|
||||
<LineChart className={`${mainIconSize} text-rose-400`} />
|
||||
{!collapsed && <span className="ml-2.5 font-medium text-foreground tracking-wide text-[15px]">{t("reports")}</span>}
|
||||
</div>
|
||||
<div className={`${collapsed ? 'p-3' : 'p-2 pl-3'} mb-1 rounded-lg hover:${theme === 'dark' ? 'bg-gray-800' : 'bg-sidebar-accent'} flex items-center ${collapsed ? 'justify-center' : ''} transition-colors duration-200`}>
|
||||
<FileText className={`${mainIconSize} text-indigo-400`} />
|
||||
{!collapsed && <span className="ml-2.5 font-medium text-foreground tracking-wide text-[15px]">{t("apiDocumentation")}</span>}
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
{!collapsed && <div className={`flex-1 flex flex-col border-t ${theme === 'dark' ? 'border-[#1e1e1e] bg-[#121212]' : 'border-sidebar-border bg-sidebar'} p-4`}>
|
||||
<Collapsible open={settingsPanelOpen} onOpenChange={setSettingsPanelOpen} className="w-full flex flex-col flex-1">
|
||||
<CollapsibleTrigger className={`flex items-center justify-between w-full mb-4 px-2 py-2 rounded-lg ${theme === 'dark' ? 'hover:bg-[#1a1a1a]' : 'hover:bg-sidebar-accent'}`}>
|
||||
<div className="flex items-center">
|
||||
<span className="font-medium tracking-wide">{t("settingPanel")}</span>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<Settings className="h-4 w-4 mr-1" />
|
||||
<ChevronDown className={`h-4 w-4 transition-transform duration-200 ${settingsPanelOpen ? 'rotate-180' : ''}`} />
|
||||
</div>
|
||||
</CollapsibleTrigger>
|
||||
|
||||
<CollapsibleContent className={`${theme === 'dark' ? 'bg-[#121212]' : 'bg-sidebar'} flex-1 flex flex-col`}>
|
||||
<div className="max-h-[300px] overflow-y-auto custom-scrollbar relative pr-1">
|
||||
<ScrollArea className="h-full">
|
||||
<div className="space-y-2 pr-4">
|
||||
<Link to={`/settings?panel=general`} className={getMenuItemClasses(activeSettingsItem === 'general')} onClick={() => handleSettingsItemClick('general')}>
|
||||
<Settings className="h-4 w-4 mr-2" />
|
||||
<span className="text-sm">{t("generalSettings")}</span>
|
||||
</Link>
|
||||
<Link to={`/settings?panel=users`} className={getMenuItemClasses(activeSettingsItem === 'users')} onClick={() => handleSettingsItemClick('users')}>
|
||||
<User className="h-4 w-4 mr-2" />
|
||||
<span className="text-sm">{t("userManagement")}</span>
|
||||
</Link>
|
||||
<Link to={`/settings?panel=notifications`} className={getMenuItemClasses(activeSettingsItem === 'notifications')} onClick={() => handleSettingsItemClick('notifications')}>
|
||||
<Bell className="h-4 w-4 mr-2" />
|
||||
<span className="text-sm">{t("notificationSettings")}</span>
|
||||
</Link>
|
||||
<Link to={`/settings?panel=templates`} className={getMenuItemClasses(activeSettingsItem === 'templates')} onClick={() => handleSettingsItemClick('templates')}>
|
||||
<BookOpen className="h-4 w-4 mr-2" />
|
||||
<span className="text-sm">{t("alertsTemplates")}</span>
|
||||
</Link>
|
||||
<Link to={`/settings?panel=data-retention`} className={getMenuItemClasses(activeSettingsItem === 'data-retention')} onClick={() => handleSettingsItemClick('data-retention')}>
|
||||
<Database className="h-4 w-4 mr-2" />
|
||||
<span className="text-sm">{t("dataRetention")}</span>
|
||||
</Link>
|
||||
<Link to={`/settings?panel=about`} className={getMenuItemClasses(activeSettingsItem === 'about')} onClick={() => handleSettingsItemClick('about')}>
|
||||
<Info className="h-4 w-4 mr-2" />
|
||||
<span className="text-sm">{t("aboutSystem")}</span>
|
||||
</Link>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
</div>}
|
||||
|
||||
{collapsed && <div className={`border-t ${theme === 'dark' ? 'border-[#1e1e1e] bg-[#121212]' : 'border-sidebar-border bg-sidebar'} p-4 flex justify-center`}>
|
||||
<Link to="/settings">
|
||||
<Settings className={`${mainIconSize} text-purple-400`} />
|
||||
</Link>
|
||||
</div>}
|
||||
</div>;
|
||||
return (
|
||||
<div className={`${collapsed ? 'w-16' : 'w-64'} ${theme === 'dark' ? 'bg-[#121212] border-[#1e1e1e]' : 'bg-sidebar border-sidebar-border'} border-r flex flex-col transition-all duration-300 h-full`}>
|
||||
<SidebarHeader collapsed={collapsed} />
|
||||
<MainNavigation collapsed={collapsed} />
|
||||
<SettingsPanel collapsed={collapsed} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
|
||||
import React from "react";
|
||||
import { MenuItem } from "./MenuItem";
|
||||
import { mainMenuItems } from "./navigationData";
|
||||
|
||||
interface MainNavigationProps {
|
||||
collapsed: boolean;
|
||||
}
|
||||
|
||||
export const MainNavigation: React.FC<MainNavigationProps> = ({ collapsed }) => {
|
||||
return (
|
||||
<nav className="my-2 mx-1 py-1 px-1">
|
||||
{mainMenuItems.map((item) => (
|
||||
<MenuItem
|
||||
key={item.id}
|
||||
id={item.id}
|
||||
path={item.path}
|
||||
icon={item.icon}
|
||||
translationKey={item.translationKey}
|
||||
color={item.color}
|
||||
hasNavigation={item.hasNavigation}
|
||||
collapsed={collapsed}
|
||||
/>
|
||||
))}
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
|
||||
import React from "react";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import { useTheme } from "@/contexts/ThemeContext";
|
||||
import { useLanguage } from "@/contexts/LanguageContext";
|
||||
import { LucideIcon } from "lucide-react";
|
||||
|
||||
interface MenuItemProps {
|
||||
id: string;
|
||||
path: string | null;
|
||||
icon: LucideIcon;
|
||||
translationKey: string;
|
||||
color: string;
|
||||
hasNavigation: boolean;
|
||||
collapsed: boolean;
|
||||
}
|
||||
|
||||
export const MenuItem: React.FC<MenuItemProps> = ({
|
||||
id,
|
||||
path,
|
||||
icon: Icon,
|
||||
translationKey,
|
||||
color,
|
||||
hasNavigation,
|
||||
collapsed
|
||||
}) => {
|
||||
const { theme } = useTheme();
|
||||
const { t } = useLanguage();
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleClick = (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if (hasNavigation && path) {
|
||||
// Use navigate instead of window.location to prevent full page reload
|
||||
navigate(path, { replace: false });
|
||||
}
|
||||
};
|
||||
|
||||
const isActive = path && location.pathname === path;
|
||||
const mainIconSize = "h-6 w-6";
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`${collapsed ? 'p-3' : 'p-2 pl-3'} mb-1 rounded-lg ${isActive ? theme === 'dark' ? 'bg-gray-800' : 'bg-sidebar-accent' : `hover:${theme === 'dark' ? 'bg-gray-800' : 'bg-sidebar-accent'}`} flex items-center ${collapsed ? 'justify-center' : ''} transition-colors duration-200 cursor-pointer`}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<Icon className={`${mainIconSize} ${color}`} />
|
||||
{!collapsed && <span className="ml-2.5 font-medium text-foreground tracking-wide text-[15px]">{t(translationKey)}</span>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,100 @@
|
||||
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import { useTheme } from "@/contexts/ThemeContext";
|
||||
import { useLanguage } from "@/contexts/LanguageContext";
|
||||
import { Settings, ChevronDown } from "lucide-react";
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { settingsMenuItems } from "./navigationData";
|
||||
|
||||
interface SettingsPanelProps {
|
||||
collapsed: boolean;
|
||||
}
|
||||
|
||||
export const SettingsPanel: React.FC<SettingsPanelProps> = ({ collapsed }) => {
|
||||
const { theme } = useTheme();
|
||||
const { t } = useLanguage();
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const [activeSettingsItem, setActiveSettingsItem] = useState<string | null>("general");
|
||||
const [settingsPanelOpen, setSettingsPanelOpen] = useState(false);
|
||||
|
||||
// Update active settings item based on URL
|
||||
useEffect(() => {
|
||||
if (location.pathname === '/settings') {
|
||||
const params = new URLSearchParams(location.search);
|
||||
const panel = params.get('panel');
|
||||
if (panel) {
|
||||
setActiveSettingsItem(panel);
|
||||
}
|
||||
}
|
||||
}, [location]);
|
||||
|
||||
const handleSettingsItemClick = (item: string) => {
|
||||
setActiveSettingsItem(item);
|
||||
};
|
||||
|
||||
const handleMenuItemClick = (path: string, event: React.MouseEvent) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
// Use navigate instead of window.location to prevent full page reload
|
||||
navigate(path, { replace: false });
|
||||
};
|
||||
|
||||
const getMenuItemClasses = (isActive: boolean) => {
|
||||
return `p-2 ${isActive ? theme === 'dark' ? 'bg-[#1a1a1a]' : 'bg-sidebar-accent' : `hover:${theme === 'dark' ? 'bg-[#1a1a1a]' : 'bg-sidebar-accent'}`} rounded-lg flex items-center`;
|
||||
};
|
||||
|
||||
if (collapsed) {
|
||||
const mainIconSize = "h-6 w-6";
|
||||
return (
|
||||
<div className={`border-t ${theme === 'dark' ? 'border-[#1e1e1e] bg-[#121212]' : 'border-sidebar-border bg-sidebar'} p-4 flex justify-center`}>
|
||||
<div
|
||||
onClick={(e) => handleMenuItemClick('/settings', e)}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<Settings className={`${mainIconSize} text-purple-400`} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`flex-1 flex flex-col border-t ${theme === 'dark' ? 'border-[#1e1e1e] bg-[#121212]' : 'border-sidebar-border bg-sidebar'} p-4`}>
|
||||
<Collapsible open={settingsPanelOpen} onOpenChange={setSettingsPanelOpen} className="w-full flex flex-col flex-1">
|
||||
<CollapsibleTrigger className={`flex items-center justify-between w-full mb-4 px-2 py-2 rounded-lg ${theme === 'dark' ? 'hover:bg-[#1a1a1a]' : 'hover:bg-sidebar-accent'}`}>
|
||||
<div className="flex items-center">
|
||||
<span className="font-medium tracking-wide">{t("settingPanel")}</span>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<Settings className="h-4 w-4 mr-1" />
|
||||
<ChevronDown className={`h-4 w-4 transition-transform duration-200 ${settingsPanelOpen ? 'rotate-180' : ''}`} />
|
||||
</div>
|
||||
</CollapsibleTrigger>
|
||||
|
||||
<CollapsibleContent className={`${theme === 'dark' ? 'bg-[#121212]' : 'bg-sidebar'} flex-1 flex flex-col`}>
|
||||
<div className="max-h-[300px] overflow-y-auto custom-scrollbar relative pr-1">
|
||||
<ScrollArea className="h-full">
|
||||
<div className="space-y-2 pr-4">
|
||||
{settingsMenuItems.map((item) => (
|
||||
<div
|
||||
key={item.id}
|
||||
className={getMenuItemClasses(activeSettingsItem === item.id)}
|
||||
onClick={(e) => {
|
||||
handleMenuItemClick(`/settings?panel=${item.id}`, e);
|
||||
handleSettingsItemClick(item.id);
|
||||
}}
|
||||
>
|
||||
<item.icon className="h-4 w-4 mr-2" />
|
||||
<span className="text-sm">{t(item.translationKey)}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
|
||||
import React from "react";
|
||||
import { useTheme } from "@/contexts/ThemeContext";
|
||||
|
||||
interface SidebarHeaderProps {
|
||||
collapsed: boolean;
|
||||
}
|
||||
|
||||
export const SidebarHeader: React.FC<SidebarHeaderProps> = ({ collapsed }) => {
|
||||
const { theme } = useTheme();
|
||||
|
||||
return (
|
||||
<div className={`p-4 ${theme === 'dark' ? 'border-[#1e1e1e]' : 'border-sidebar-border'} border-b flex items-center ${collapsed ? 'justify-center' : ''}`}>
|
||||
<div className="h-8 w-8 bg-green-500 rounded flex items-center justify-center mr-2">
|
||||
<span className="text-white font-bold">C</span>
|
||||
</div>
|
||||
{!collapsed && <h1 className="text-xl font-semibold">CheckCle App</h1>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
|
||||
export { SidebarHeader } from './SidebarHeader';
|
||||
export { MainNavigation } from './MainNavigation';
|
||||
export { MenuItem } from './MenuItem';
|
||||
export { SettingsPanel } from './SettingsPanel';
|
||||
export { mainMenuItems, settingsMenuItems } from './navigationData';
|
||||
@@ -0,0 +1,94 @@
|
||||
|
||||
import { Globe, Boxes, Radar, Calendar, BarChart2, LineChart, FileText, Settings, User, Bell, Database, Info, BookOpen } from "lucide-react";
|
||||
|
||||
export const mainMenuItems = [
|
||||
{
|
||||
id: 'uptime-monitoring',
|
||||
path: '/dashboard',
|
||||
icon: Globe,
|
||||
translationKey: 'uptimeMonitoring',
|
||||
color: 'text-purple-400',
|
||||
hasNavigation: true
|
||||
},
|
||||
{
|
||||
id: 'instance-monitoring',
|
||||
path: null,
|
||||
icon: Boxes,
|
||||
translationKey: 'instanceMonitoring',
|
||||
color: 'text-blue-400',
|
||||
hasNavigation: false
|
||||
},
|
||||
{
|
||||
id: 'ssl-domain',
|
||||
path: '/ssl-domain',
|
||||
icon: Radar,
|
||||
translationKey: 'sslDomain',
|
||||
color: 'text-cyan-400',
|
||||
hasNavigation: true
|
||||
},
|
||||
{
|
||||
id: 'schedule-incident',
|
||||
path: '/schedule-incident',
|
||||
icon: Calendar,
|
||||
translationKey: 'scheduleIncident',
|
||||
color: 'text-emerald-400',
|
||||
hasNavigation: true
|
||||
},
|
||||
{
|
||||
id: 'operational-page',
|
||||
path: '/operational-page',
|
||||
icon: BarChart2,
|
||||
translationKey: 'operationalPage',
|
||||
color: 'text-amber-400',
|
||||
hasNavigation: true
|
||||
},
|
||||
{
|
||||
id: 'reports',
|
||||
path: null,
|
||||
icon: LineChart,
|
||||
translationKey: 'reports',
|
||||
color: 'text-rose-400',
|
||||
hasNavigation: false
|
||||
},
|
||||
{
|
||||
id: 'api-documentation',
|
||||
path: null,
|
||||
icon: FileText,
|
||||
translationKey: 'apiDocumentation',
|
||||
color: 'text-indigo-400',
|
||||
hasNavigation: false
|
||||
}
|
||||
];
|
||||
|
||||
export const settingsMenuItems = [
|
||||
{
|
||||
id: 'general',
|
||||
icon: Settings,
|
||||
translationKey: 'generalSettings'
|
||||
},
|
||||
{
|
||||
id: 'users',
|
||||
icon: User,
|
||||
translationKey: 'userManagement'
|
||||
},
|
||||
{
|
||||
id: 'notifications',
|
||||
icon: Bell,
|
||||
translationKey: 'notificationSettings'
|
||||
},
|
||||
{
|
||||
id: 'templates',
|
||||
icon: BookOpen,
|
||||
translationKey: 'alertsTemplates'
|
||||
},
|
||||
{
|
||||
id: 'data-retention',
|
||||
icon: Database,
|
||||
translationKey: 'dataRetention'
|
||||
},
|
||||
{
|
||||
id: 'about',
|
||||
icon: Info,
|
||||
translationKey: 'aboutSystem'
|
||||
}
|
||||
];
|
||||
@@ -0,0 +1,38 @@
|
||||
|
||||
import React, { createContext, useContext, useState } from 'react';
|
||||
|
||||
interface SidebarContextType {
|
||||
sidebarCollapsed: boolean;
|
||||
setSidebarCollapsed: (collapsed: boolean) => void;
|
||||
toggleSidebar: () => void;
|
||||
}
|
||||
|
||||
const SidebarContext = createContext<SidebarContextType | undefined>(undefined);
|
||||
|
||||
export const SidebarProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
|
||||
|
||||
const toggleSidebar = () => {
|
||||
setSidebarCollapsed(prev => !prev);
|
||||
};
|
||||
|
||||
const value = {
|
||||
sidebarCollapsed,
|
||||
setSidebarCollapsed,
|
||||
toggleSidebar
|
||||
};
|
||||
|
||||
return (
|
||||
<SidebarContext.Provider value={value}>
|
||||
{children}
|
||||
</SidebarContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useSidebar = () => {
|
||||
const context = useContext(SidebarContext);
|
||||
if (context === undefined) {
|
||||
throw new Error('useSidebar must be used within a SidebarProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useEffect } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { Header } from "@/components/dashboard/Header";
|
||||
import { Sidebar } from "@/components/dashboard/Sidebar";
|
||||
@@ -8,11 +8,11 @@ import { serviceService } from "@/services/serviceService";
|
||||
import { authService } from "@/services/authService";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { LoadingState } from "@/components/services/LoadingState";
|
||||
import { useSidebar } from "@/contexts/SidebarContext";
|
||||
|
||||
const Dashboard = () => {
|
||||
// State for sidebar collapse functionality
|
||||
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
|
||||
const toggleSidebar = () => setSidebarCollapsed(prev => !prev);
|
||||
// Use shared sidebar state
|
||||
const { sidebarCollapsed, toggleSidebar } = useSidebar();
|
||||
|
||||
// Get current user
|
||||
const currentUser = authService.getCurrentUser();
|
||||
@@ -71,4 +71,4 @@ const Dashboard = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default Dashboard;
|
||||
export default Dashboard;
|
||||
@@ -1,14 +1,29 @@
|
||||
// Update this page (the content is just a fallback if you fail to update the page)
|
||||
|
||||
import React, { useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { authService } from '@/services/authService';
|
||||
|
||||
const Index = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
// Check if user is authenticated and redirect accordingly
|
||||
if (authService.isAuthenticated()) {
|
||||
navigate('/dashboard', { replace: true });
|
||||
} else {
|
||||
navigate('/login', { replace: true });
|
||||
}
|
||||
}, [navigate]);
|
||||
|
||||
// Show a loading state while redirecting
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-100">
|
||||
<div className="min-h-screen flex items-center justify-center bg-background">
|
||||
<div className="text-center">
|
||||
<h1 className="text-4xl font-bold mb-4">Welcome to Your Blank App</h1>
|
||||
<p className="text-xl text-gray-600">Start building your amazing project here!</p>
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary mx-auto mb-4"></div>
|
||||
<p className="text-muted-foreground">Loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Index;
|
||||
export default Index;
|
||||
@@ -1,15 +1,15 @@
|
||||
|
||||
import React, { useState } from "react";
|
||||
import React from "react";
|
||||
import { Header } from "@/components/dashboard/Header";
|
||||
import { Sidebar } from "@/components/dashboard/Sidebar";
|
||||
import { OperationalPageContent } from '@/components/operational-page/OperationalPageContent';
|
||||
import { authService } from "@/services/authService";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useSidebar } from "@/contexts/SidebarContext";
|
||||
|
||||
const OperationalPage = () => {
|
||||
// State for sidebar collapse functionality
|
||||
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
|
||||
const toggleSidebar = () => setSidebarCollapsed(prev => !prev);
|
||||
// Use shared sidebar state
|
||||
const { sidebarCollapsed, toggleSidebar } = useSidebar();
|
||||
|
||||
// Get current user
|
||||
const currentUser = authService.getCurrentUser();
|
||||
|
||||
@@ -9,11 +9,11 @@ import { ProfileContent } from "@/components/profile/ProfileContent";
|
||||
import { User } from "@/services/userService";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import { Loader2 } from "lucide-react";
|
||||
import { useSidebar } from "@/contexts/SidebarContext";
|
||||
|
||||
const Profile = () => {
|
||||
// State for sidebar collapse functionality
|
||||
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
|
||||
const toggleSidebar = () => setSidebarCollapsed(prev => !prev);
|
||||
// Use shared sidebar state
|
||||
const { sidebarCollapsed, toggleSidebar } = useSidebar();
|
||||
|
||||
// Get current user
|
||||
const currentUser = authService.getCurrentUser();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { Header } from "@/components/dashboard/Header";
|
||||
import { Sidebar } from "@/components/dashboard/Sidebar";
|
||||
import { useTheme } from "@/contexts/ThemeContext";
|
||||
@@ -8,11 +8,11 @@ import { ScheduleIncidentContent } from "@/components/schedule-incident/Schedule
|
||||
import { authService } from "@/services/authService";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { initMaintenanceNotifications, stopMaintenanceNotifications } from "@/services/maintenance/maintenanceNotificationService";
|
||||
import { useSidebar } from "@/contexts/SidebarContext";
|
||||
|
||||
const ScheduleIncident = () => {
|
||||
// State for sidebar collapse functionality
|
||||
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
|
||||
const toggleSidebar = () => setSidebarCollapsed(prev => !prev);
|
||||
// Use shared sidebar state
|
||||
const { sidebarCollapsed, toggleSidebar } = useSidebar();
|
||||
|
||||
// Get current theme and language
|
||||
const { theme } = useTheme();
|
||||
@@ -24,12 +24,10 @@ const ScheduleIncident = () => {
|
||||
|
||||
// Initialize maintenance notifications
|
||||
useEffect(() => {
|
||||
console.log("Initializing maintenance notifications");
|
||||
initMaintenanceNotifications();
|
||||
|
||||
// Clean up on unmount
|
||||
return () => {
|
||||
console.log("Stopping maintenance notifications");
|
||||
stopMaintenanceNotifications();
|
||||
};
|
||||
}, []);
|
||||
|
||||
@@ -10,11 +10,11 @@ import { NotificationSettings } from "@/components/settings/notification-setting
|
||||
import { AlertsTemplates } from "@/components/settings/alerts-templates";
|
||||
import { AboutSystem } from "@/components/settings/about-system";
|
||||
import DataRetentionSettings from "@/components/settings/data-retention/DataRetentionSettings";
|
||||
import { useSidebar } from "@/contexts/SidebarContext";
|
||||
|
||||
const Settings = () => {
|
||||
// State for sidebar collapse functionality
|
||||
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
|
||||
const toggleSidebar = () => setSidebarCollapsed(prev => !prev);
|
||||
// Use shared sidebar state
|
||||
const { sidebarCollapsed, toggleSidebar } = useSidebar();
|
||||
|
||||
// Get current user
|
||||
const currentUser = authService.getCurrentUser();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
|
||||
import React, { useEffect } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { authService } from "@/services/authService";
|
||||
@@ -8,14 +9,14 @@ import { SSLDomainContent } from "@/components/ssl-domain/SSLDomainContent";
|
||||
import { LoadingState } from "@/components/services/LoadingState";
|
||||
import { fetchSSLCertificates, shouldRunDailyCheck, checkAllCertificatesAndNotify } from "@/services/sslCertificateService";
|
||||
import { useLanguage } from "@/contexts/LanguageContext";
|
||||
import { useSidebar } from "@/contexts/SidebarContext";
|
||||
|
||||
const SslDomain = () => {
|
||||
// Get language context for translations
|
||||
const { t } = useLanguage();
|
||||
|
||||
// State for sidebar collapse functionality
|
||||
const [sidebarCollapsed, setSidebarCollapsed] = React.useState(false);
|
||||
const toggleSidebar = () => setSidebarCollapsed(prev => !prev);
|
||||
// Use shared sidebar state
|
||||
const { sidebarCollapsed, toggleSidebar } = useSidebar();
|
||||
|
||||
// Get current user
|
||||
const currentUser = authService.getCurrentUser();
|
||||
|
||||
Reference in New Issue
Block a user