Hide admin section in sidebar, optimize translation service with parallel processing, improve UX

This commit is contained in:
2025-11-30 21:33:44 +01:00
parent fcabe882cd
commit d2b820c6f1
2 changed files with 149 additions and 74 deletions

View File

@@ -2,19 +2,18 @@
import Link from "next/link";
import { usePathname } from "next/navigation";
import { useState, useEffect } from "react";
import { useState, useEffect, useCallback, memo } from "react";
import { cn } from "@/lib/utils";
import {
Settings,
Cloud,
BookText,
Upload,
Shield,
CreditCard,
LayoutDashboard,
LogIn,
Crown,
LogOut,
Server,
} from "lucide-react";
import {
Tooltip,
@@ -38,12 +37,6 @@ const navigation = [
icon: Upload,
description: "Translate documents",
},
{
name: "General Settings",
href: "/settings",
icon: Settings,
description: "Configure general settings",
},
{
name: "Translation Services",
href: "/settings/services",
@@ -56,23 +49,61 @@ const navigation = [
icon: BookText,
description: "System prompts and glossary",
},
];
const adminNavigation = [
{
name: "Admin Dashboard",
href: "/admin",
icon: Shield,
description: "System monitoring (login required)",
name: "General Settings",
href: "/settings",
icon: Settings,
description: "Configure general settings",
},
];
const planColors: Record<string, string> = {
free: "bg-zinc-600",
starter: "bg-blue-500",
pro: "bg-teal-500",
business: "bg-purple-500",
enterprise: "bg-amber-500",
};
// Memoized NavItem for performance
const NavItem = memo(function NavItem({
item,
isActive
}: {
item: typeof navigation[0];
isActive: boolean;
}) {
const Icon = item.icon;
return (
<Tooltip>
<TooltipTrigger asChild>
<Link
href={item.href}
className={cn(
"flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-colors",
isActive
? "bg-teal-500/10 text-teal-400"
: "text-zinc-400 hover:bg-zinc-800 hover:text-zinc-100"
)}
>
<Icon className="h-5 w-5" />
<span>{item.name}</span>
</Link>
</TooltipTrigger>
<TooltipContent side="right">
<p>{item.description}</p>
</TooltipContent>
</Tooltip>
);
});
export function Sidebar() {
const pathname = usePathname();
const [user, setUser] = useState<User | null>(null);
const [mounted, setMounted] = useState(false);
useEffect(() => {
// Check for user in localStorage
setMounted(true);
const storedUser = localStorage.getItem("user");
if (storedUser) {
try {
@@ -81,26 +112,38 @@ export function Sidebar() {
setUser(null);
}
}
// Listen for storage changes
const handleStorage = (e: StorageEvent) => {
if (e.key === "user") {
setUser(e.newValue ? JSON.parse(e.newValue) : null);
}
};
window.addEventListener("storage", handleStorage);
return () => window.removeEventListener("storage", handleStorage);
}, []);
const handleLogout = () => {
const handleLogout = useCallback(() => {
localStorage.removeItem("token");
localStorage.removeItem("refresh_token");
localStorage.removeItem("user");
setUser(null);
window.location.href = "/";
};
}, []);
const planColors: Record<string, string> = {
free: "bg-zinc-600",
starter: "bg-blue-500",
pro: "bg-teal-500",
business: "bg-purple-500",
enterprise: "bg-amber-500",
};
// Prevent hydration mismatch
if (!mounted) {
return (
<aside className="fixed left-0 top-0 z-40 h-screen w-64 border-r border-zinc-800 bg-[#1a1a1a]">
<div className="flex h-16 items-center gap-3 border-b border-zinc-800 px-6">
<div className="flex h-9 w-9 items-center justify-center rounded-lg bg-teal-500 text-white font-bold">A</div>
<span className="text-lg font-semibold text-white">Translate Co.</span>
</div>
</aside>
);
}
return (
<TooltipProvider>
<TooltipProvider delayDuration={300}>
<aside className="fixed left-0 top-0 z-40 h-screen w-64 border-r border-zinc-800 bg-[#1a1a1a]">
{/* Logo */}
<div className="flex h-16 items-center gap-3 border-b border-zinc-800 px-6">
@@ -164,57 +207,51 @@ export function Sidebar() {
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Link
href="/pricing"
className={cn(
"flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-colors",
pathname === "/pricing"
? "bg-amber-500/10 text-amber-400"
: "text-zinc-400 hover:bg-zinc-800 hover:text-zinc-100"
)}
>
<Crown className="h-5 w-5" />
<span>Upgrade Plan</span>
</Link>
</TooltipTrigger>
<TooltipContent side="right">
<p>View plans and pricing</p>
</TooltipContent>
</Tooltip>
</div>
)}
{/* Admin Section */}
<div className="mt-4 pt-4 border-t border-zinc-800">
<p className="px-3 mb-2 text-xs font-medium text-zinc-600 uppercase tracking-wider">Admin</p>
{adminNavigation.map((item) => {
const isActive = pathname === item.href;
const Icon = item.icon;
return (
<Tooltip key={item.name}>
{user.plan === "free" && (
<Tooltip>
<TooltipTrigger asChild>
<Link
href={item.href}
href="/pricing"
className={cn(
"flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-colors",
isActive
? "bg-blue-500/10 text-blue-400"
: "text-zinc-500 hover:bg-zinc-800 hover:text-zinc-300"
pathname === "/pricing"
? "bg-amber-500/10 text-amber-400"
: "text-amber-400/70 hover:bg-zinc-800 hover:text-amber-400"
)}
>
<Icon className="h-5 w-5" />
<span>{item.name}</span>
<Crown className="h-5 w-5" />
<span>Upgrade Plan</span>
</Link>
</TooltipTrigger>
<TooltipContent side="right">
<p>{item.description}</p>
<p>Get more translations and features</p>
</TooltipContent>
</Tooltip>
);
})}
)}
</div>
)}
{/* Self-Host Option */}
<div className="mt-4 pt-4 border-t border-zinc-800">
<Tooltip>
<TooltipTrigger asChild>
<Link
href="/ollama-setup"
className={cn(
"flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-colors",
pathname === "/ollama-setup"
? "bg-orange-500/10 text-orange-400"
: "text-zinc-400 hover:bg-zinc-800 hover:text-zinc-100"
)}
>
<Server className="h-5 w-5" />
<span>Self-Host (Free)</span>
</Link>
</TooltipTrigger>
<TooltipContent side="right">
<p>Run your own Ollama for unlimited free translations</p>
</TooltipContent>
</Tooltip>
</div>
</nav>
@@ -222,17 +259,17 @@ export function Sidebar() {
<div className="absolute bottom-0 left-0 right-0 border-t border-zinc-800 p-4">
{user ? (
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-teal-600 text-white text-sm font-medium">
<Link href="/dashboard" className="flex items-center gap-3 flex-1 min-w-0">
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-gradient-to-br from-teal-500 to-teal-600 text-white text-sm font-medium shrink-0">
{user.name.charAt(0).toUpperCase()}
</div>
<div className="flex flex-col">
<span className="text-sm font-medium text-white">{user.name}</span>
<Badge className={cn("text-xs mt-0.5", planColors[user.plan] || "bg-zinc-600")}>
<div className="flex flex-col min-w-0">
<span className="text-sm font-medium text-white truncate">{user.name}</span>
<Badge className={cn("text-xs mt-0.5 w-fit", planColors[user.plan] || "bg-zinc-600")}>
{user.plan.charAt(0).toUpperCase() + user.plan.slice(1)}
</Badge>
</div>
</div>
</Link>
<button
onClick={handleLogout}
className="p-2 rounded-lg text-zinc-400 hover:text-white hover:bg-zinc-800"