sidebar
This commit is contained in:
parent
27807fc9d6
commit
9d579385d7
@ -24,7 +24,7 @@
|
|||||||
work correctly both with client-side routing and a non-root public URL.
|
work correctly both with client-side routing and a non-root public URL.
|
||||||
Learn how to configure a non-root public URL by running `npm run build`.
|
Learn how to configure a non-root public URL by running `npm run build`.
|
||||||
-->
|
-->
|
||||||
<title>Channel Partner Dashboard</title>
|
<title>Cloudtopiaa reseller Dashboard</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
|||||||
@ -31,7 +31,7 @@ import ResellerBilling from './pages/reseller/Billing';
|
|||||||
import ResellerSupport from './pages/reseller/Support';
|
import ResellerSupport from './pages/reseller/Support';
|
||||||
import ResellerReports from './pages/reseller/Reports';
|
import ResellerReports from './pages/reseller/Reports';
|
||||||
import ResellerTraining from './pages/reseller/Training';
|
import ResellerTraining from './pages/reseller/Training';
|
||||||
import ResellerLayout from './components/reseller/layout/ResellerLayout';
|
import ResellerLayout from './components/Layout/ResellerLayout';
|
||||||
import CookieConsent from './components/CookieConsent';
|
import CookieConsent from './components/CookieConsent';
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
|
|||||||
92
src/components/Layout/ResellerLayout.tsx
Normal file
92
src/components/Layout/ResellerLayout.tsx
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ResellerSidebar from './ResellerSidebar';
|
||||||
|
|
||||||
|
interface ResellerLayoutProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ResellerLayout: React.FC<ResellerLayoutProps> = ({ children }) => {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-emerald-50 to-teal-50 dark:from-slate-900 dark:via-slate-800 dark:to-slate-900">
|
||||||
|
<div className="flex h-screen">
|
||||||
|
{/* Sidebar */}
|
||||||
|
<div className="flex-shrink-0">
|
||||||
|
<ResellerSidebar />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Main Content */}
|
||||||
|
<div className="flex-1 flex flex-col overflow-hidden">
|
||||||
|
{/* Top Navigation Bar */}
|
||||||
|
<div className="bg-white/90 dark:bg-slate-900/95 backdrop-blur-xl border-b border-slate-200/50 dark:border-slate-700/50 sticky top-0 z-40 shadow-sm">
|
||||||
|
<div className="flex items-center justify-between px-8 py-5 h-20">
|
||||||
|
{/* Left Side - Logo and Title */}
|
||||||
|
<div className="flex items-center space-x-5">
|
||||||
|
<div className="w-10 h-10 bg-gradient-to-br from-emerald-500 via-teal-500 to-cyan-500 rounded-xl flex items-center justify-center flex-shrink-0 shadow-lg">
|
||||||
|
<div className="w-5 h-5 bg-white rounded-md shadow-sm"></div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col justify-center">
|
||||||
|
<h1 className="text-xl font-bold text-slate-900 dark:text-white leading-tight tracking-tight">
|
||||||
|
Reseller Portal
|
||||||
|
</h1>
|
||||||
|
<p className="text-sm font-medium text-slate-500 dark:text-slate-400 leading-tight">
|
||||||
|
Cloud Services Management
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Right Side - Search, Notifications, User */}
|
||||||
|
<div className="flex items-center space-x-6">
|
||||||
|
{/* Search Bar */}
|
||||||
|
<div className="relative flex-shrink-0">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Search anything..."
|
||||||
|
className="w-72 pl-12 pr-6 py-3 bg-slate-50 dark:bg-slate-800/80 border border-slate-200/50 dark:border-slate-600/50 rounded-xl text-slate-900 dark:text-white placeholder-slate-400 dark:placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-emerald-500/50 focus:border-emerald-500/50 transition-all duration-200 backdrop-blur-sm"
|
||||||
|
/>
|
||||||
|
<div className="absolute left-4 top-1/2 transform -translate-y-1/2">
|
||||||
|
<svg className="w-5 h-5 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Notifications */}
|
||||||
|
<button className="relative p-3 text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white hover:bg-slate-100/80 dark:hover:bg-slate-700/50 rounded-xl transition-all duration-200 flex-shrink-0 group">
|
||||||
|
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 17h5l-5 5v-5zM4.5 19.5h15a2.25 2.25 0 002.25-2.25V6.75A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25v10.5A2.25 2.25 0 004.5 19.5z" />
|
||||||
|
</svg>
|
||||||
|
<span className="absolute -top-1 -right-1 w-3 h-3 bg-red-500 rounded-full border-2 border-white dark:border-slate-900"></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Divider */}
|
||||||
|
<div className="w-px h-8 bg-slate-200 dark:bg-slate-700"></div>
|
||||||
|
|
||||||
|
{/* User Menu */}
|
||||||
|
<div className="flex items-center space-x-4 flex-shrink-0 group cursor-pointer">
|
||||||
|
<div className="text-right flex flex-col justify-center">
|
||||||
|
<p className="text-sm font-semibold text-slate-900 dark:text-white leading-tight">John Reseller</p>
|
||||||
|
<p className="text-xs font-medium text-slate-500 dark:text-slate-400 leading-tight">Tech Solutions Inc</p>
|
||||||
|
</div>
|
||||||
|
<div className="w-10 h-10 bg-gradient-to-br from-emerald-500 via-teal-500 to-cyan-500 rounded-xl flex items-center justify-center flex-shrink-0 shadow-lg group-hover:shadow-xl transition-all duration-200">
|
||||||
|
<span className="text-white text-sm font-bold">JR</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Page Content */}
|
||||||
|
<div className="flex-1 overflow-auto">
|
||||||
|
<div className="p-6">
|
||||||
|
<div className="max-w-7xl mx-auto">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ResellerLayout;
|
||||||
163
src/components/Layout/ResellerSidebar.tsx
Normal file
163
src/components/Layout/ResellerSidebar.tsx
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Link, useLocation } from 'react-router-dom';
|
||||||
|
import { useAppSelector, useAppDispatch } from '../../store/hooks';
|
||||||
|
import {
|
||||||
|
Home,
|
||||||
|
Users,
|
||||||
|
Cloud,
|
||||||
|
CreditCard,
|
||||||
|
Headphones,
|
||||||
|
BarChart3,
|
||||||
|
Wallet,
|
||||||
|
BookOpen,
|
||||||
|
ShoppingBag,
|
||||||
|
Award,
|
||||||
|
HelpCircle,
|
||||||
|
Settings,
|
||||||
|
Menu,
|
||||||
|
X,
|
||||||
|
Sun,
|
||||||
|
Moon,
|
||||||
|
LogOut,
|
||||||
|
Building,
|
||||||
|
Target,
|
||||||
|
TrendingUp,
|
||||||
|
Package
|
||||||
|
} from 'lucide-react';
|
||||||
|
import { RootState } from '../../store';
|
||||||
|
import { toggleTheme } from '../../store/slices/themeSlice';
|
||||||
|
import { logout } from '../../store/slices/authSlice';
|
||||||
|
import { cn } from '../../utils/cn';
|
||||||
|
|
||||||
|
const resellerNavigation = [
|
||||||
|
{ name: 'Dashboard', href: '/reseller-dashboard', icon: Home },
|
||||||
|
{ name: 'Customers', href: '/reseller-dashboard/customers', icon: Users },
|
||||||
|
{ name: 'Cloud Instances', href: '/reseller-dashboard/instances', icon: Cloud },
|
||||||
|
{ name: 'Billing', href: '/reseller-dashboard/billing', icon: CreditCard },
|
||||||
|
{ name: 'Support', href: '/reseller-dashboard/support', icon: Headphones },
|
||||||
|
{ name: 'Reports', href: '/reseller-dashboard/reports', icon: BarChart3 },
|
||||||
|
{ name: 'Wallet', href: '/reseller-dashboard/wallet', icon: Wallet },
|
||||||
|
{ name: 'Training', href: '/reseller-dashboard/training', icon: BookOpen },
|
||||||
|
{ name: 'Marketplace', href: '/reseller-dashboard/marketplace', icon: ShoppingBag },
|
||||||
|
{ name: 'Certifications', href: '/reseller-dashboard/certifications', icon: Award },
|
||||||
|
{ name: 'Knowledge Base', href: '/reseller-dashboard/knowledge-base', icon: HelpCircle },
|
||||||
|
{ name: 'Settings', href: '/reseller-dashboard/settings', icon: Settings },
|
||||||
|
];
|
||||||
|
|
||||||
|
const ResellerSidebar: React.FC = () => {
|
||||||
|
const [isCollapsed, setIsCollapsed] = useState(false);
|
||||||
|
const location = useLocation();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const { theme } = useAppSelector((state: RootState) => state.theme);
|
||||||
|
const { user } = useAppSelector((state: RootState) => state.auth);
|
||||||
|
|
||||||
|
const handleLogout = () => {
|
||||||
|
dispatch(logout());
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cn(
|
||||||
|
"flex flex-col h-full bg-gradient-to-b from-slate-900 via-slate-800 to-slate-900 border-r border-slate-700/50 transition-all duration-300",
|
||||||
|
isCollapsed ? "w-16" : "w-64"
|
||||||
|
)}>
|
||||||
|
{/* Header */}
|
||||||
|
<div className="flex items-center justify-between p-4 border-b border-slate-700/50">
|
||||||
|
{!isCollapsed && (
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<div className="w-8 h-8 bg-gradient-to-r from-emerald-500 to-teal-500 rounded-lg flex items-center justify-center">
|
||||||
|
<Cloud className="w-5 h-5 text-white" />
|
||||||
|
</div>
|
||||||
|
<span className="text-lg font-semibold text-white">
|
||||||
|
Reseller
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
onClick={() => setIsCollapsed(!isCollapsed)}
|
||||||
|
className="p-1 rounded-md hover:bg-slate-700/50 transition-colors"
|
||||||
|
>
|
||||||
|
{isCollapsed ? (
|
||||||
|
<Menu className="w-6 h-6 text-slate-400" />
|
||||||
|
) : (
|
||||||
|
<X className="w-5 h-5 text-slate-400" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Navigation */}
|
||||||
|
<nav className={cn(
|
||||||
|
"flex-1 py-4 space-y-1 overflow-y-auto",
|
||||||
|
isCollapsed ? "px-3" : "px-2"
|
||||||
|
)}>
|
||||||
|
{resellerNavigation.map((item) => {
|
||||||
|
const isActive = location.pathname === item.href;
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
key={item.name}
|
||||||
|
to={item.href}
|
||||||
|
className={cn(
|
||||||
|
"flex items-center text-sm font-medium rounded-xl transition-all duration-200 group",
|
||||||
|
isCollapsed ? "px-2 py-3 justify-center" : "px-4 py-3",
|
||||||
|
isActive
|
||||||
|
? "bg-gradient-to-r from-emerald-500/20 to-teal-500/20 text-emerald-400 border border-emerald-500/30 shadow-lg"
|
||||||
|
: "text-slate-300 hover:bg-slate-800/50 hover:text-white"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<item.icon className={cn(
|
||||||
|
"flex-shrink-0 transition-colors duration-200",
|
||||||
|
isCollapsed ? "w-6 h-6" : "w-5 h-5",
|
||||||
|
isActive
|
||||||
|
? "text-emerald-400"
|
||||||
|
: "text-slate-400 group-hover:text-white"
|
||||||
|
)} />
|
||||||
|
{!isCollapsed && (
|
||||||
|
<span className="ml-3">{item.name}</span>
|
||||||
|
)}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{/* User Profile & Actions */}
|
||||||
|
<div className="border-t border-slate-700/50 p-4 space-y-2">
|
||||||
|
{/* Theme Toggle */}
|
||||||
|
<button
|
||||||
|
onClick={() => dispatch(toggleTheme())}
|
||||||
|
className={cn(
|
||||||
|
"w-full flex items-center rounded-xl text-sm font-medium transition-all duration-200",
|
||||||
|
isCollapsed ? "justify-center px-2 py-3" : "px-4 py-3",
|
||||||
|
"text-slate-300 hover:bg-slate-800/50 hover:text-white"
|
||||||
|
)}
|
||||||
|
title={theme === 'dark' ? 'Switch to light mode' : 'Switch to dark mode'}
|
||||||
|
>
|
||||||
|
{theme === 'dark' ? (
|
||||||
|
<Sun className="w-6 h-6 text-amber-400" />
|
||||||
|
) : (
|
||||||
|
<Moon className="w-6 h-6 text-slate-400" />
|
||||||
|
)}
|
||||||
|
{!isCollapsed && (
|
||||||
|
<span className="ml-3">
|
||||||
|
{theme === 'dark' ? 'Light Mode' : 'Dark Mode'}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Logout */}
|
||||||
|
<button
|
||||||
|
onClick={handleLogout}
|
||||||
|
className={cn(
|
||||||
|
"w-full flex items-center rounded-xl text-sm font-medium transition-all duration-200",
|
||||||
|
isCollapsed ? "justify-center px-2 py-3" : "px-4 py-3",
|
||||||
|
"text-red-400 hover:bg-red-500/10 hover:text-red-300"
|
||||||
|
)}
|
||||||
|
title="Logout"
|
||||||
|
>
|
||||||
|
<LogOut className="w-6 h-6" />
|
||||||
|
{!isCollapsed && <span className="ml-3">Logout</span>}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ResellerSidebar;
|
||||||
@ -82,7 +82,7 @@ const Sidebar: React.FC = () => {
|
|||||||
className="p-1 rounded-md hover:bg-secondary-100 dark:hover:bg-secondary-800 transition-colors"
|
className="p-1 rounded-md hover:bg-secondary-100 dark:hover:bg-secondary-800 transition-colors"
|
||||||
>
|
>
|
||||||
{isCollapsed ? (
|
{isCollapsed ? (
|
||||||
<Menu className="w-5 h-5 text-secondary-600 dark:text-secondary-400" />
|
<Menu className="w-6 h-6 text-secondary-600 dark:text-secondary-400" />
|
||||||
) : (
|
) : (
|
||||||
<X className="w-5 h-5 text-secondary-600 dark:text-secondary-400" />
|
<X className="w-5 h-5 text-secondary-600 dark:text-secondary-400" />
|
||||||
)}
|
)}
|
||||||
@ -90,7 +90,10 @@ const Sidebar: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Navigation */}
|
{/* Navigation */}
|
||||||
<nav className="flex-1 px-2 py-4 space-y-1 overflow-y-auto">
|
<nav className={cn(
|
||||||
|
"flex-1 py-4 space-y-1 overflow-y-auto",
|
||||||
|
isCollapsed ? "px-3" : "px-2"
|
||||||
|
)}>
|
||||||
{navigation.map((item) => {
|
{navigation.map((item) => {
|
||||||
const isActive = location.pathname === item.href;
|
const isActive = location.pathname === item.href;
|
||||||
return (
|
return (
|
||||||
@ -98,14 +101,16 @@ const Sidebar: React.FC = () => {
|
|||||||
key={item.name}
|
key={item.name}
|
||||||
to={item.href}
|
to={item.href}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex items-center px-3 py-2 text-sm font-medium rounded-md transition-colors group",
|
"flex items-center text-sm font-medium rounded-md transition-colors group",
|
||||||
|
isCollapsed ? "px-2 py-3 justify-center" : "px-3 py-2",
|
||||||
isActive
|
isActive
|
||||||
? "bg-primary-100 text-primary-700 dark:bg-primary-900 dark:text-primary-300"
|
? "bg-primary-100 text-primary-700 dark:bg-primary-900 dark:text-primary-300"
|
||||||
: "text-secondary-700 hover:bg-secondary-100 hover:text-secondary-900 dark:text-secondary-300 dark:hover:bg-secondary-800 dark:hover:text-white"
|
: "text-secondary-700 hover:bg-secondary-100 hover:text-secondary-900 dark:text-secondary-300 dark:hover:bg-secondary-800 dark:hover:text-white"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<item.icon className={cn(
|
<item.icon className={cn(
|
||||||
"flex-shrink-0 w-5 h-5 transition-colors",
|
"flex-shrink-0 transition-colors",
|
||||||
|
isCollapsed ? "w-6 h-6" : "w-5 h-5",
|
||||||
isActive
|
isActive
|
||||||
? "text-primary-600 dark:text-primary-400"
|
? "text-primary-600 dark:text-primary-400"
|
||||||
: "text-secondary-500 group-hover:text-secondary-700 dark:text-secondary-400 dark:group-hover:text-white"
|
: "text-secondary-500 group-hover:text-secondary-700 dark:text-secondary-400 dark:group-hover:text-white"
|
||||||
@ -133,9 +138,9 @@ const Sidebar: React.FC = () => {
|
|||||||
title={theme === 'dark' ? 'Switch to light mode' : 'Switch to dark mode'}
|
title={theme === 'dark' ? 'Switch to light mode' : 'Switch to dark mode'}
|
||||||
>
|
>
|
||||||
{theme === 'dark' ? (
|
{theme === 'dark' ? (
|
||||||
<Sun className="w-5 h-5 text-secondary-600 dark:text-secondary-400" />
|
<Sun className={cn(isCollapsed ? "w-6 h-6" : "w-5 h-5", "text-secondary-600 dark:text-secondary-400")} />
|
||||||
) : (
|
) : (
|
||||||
<Moon className="w-5 h-5 text-secondary-600 dark:text-secondary-400" />
|
<Moon className={cn(isCollapsed ? "w-6 h-6" : "w-5 h-5", "text-secondary-600 dark:text-secondary-400")} />
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -175,7 +180,7 @@ const Sidebar: React.FC = () => {
|
|||||||
className="p-1 rounded-md hover:bg-secondary-100 dark:hover:bg-secondary-800 transition-colors"
|
className="p-1 rounded-md hover:bg-secondary-100 dark:hover:bg-secondary-800 transition-colors"
|
||||||
title="Logout"
|
title="Logout"
|
||||||
>
|
>
|
||||||
<LogOut className="w-4 h-4 text-secondary-600 dark:text-secondary-400" />
|
<LogOut className={cn(isCollapsed ? "w-5 h-5" : "w-4 h-4", "text-secondary-600 dark:text-secondary-400")} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -103,7 +103,7 @@ const ResellerDashboard: React.FC = () => {
|
|||||||
<div className="relative z-10">
|
<div className="relative z-10">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold mb-2">Welcome back, John! 👋</h1>
|
<h1 className="text-3xl font-bold mb-2">Welcome back, John! </h1>
|
||||||
<p className="text-emerald-100 text-lg">Here's what's happening with your cloud services business today.</p>
|
<p className="text-emerald-100 text-lg">Here's what's happening with your cloud services business today.</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="hidden lg:flex items-center space-x-4">
|
<div className="hidden lg:flex items-center space-x-4">
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user