"use client"; // ============================================================================ // Application Sidebar // ============================================================================ import * as React from "react"; import { useParams } from "next/navigation"; import { BarChart3, Building2, Check, ChevronsUpDown, ClipboardList, ExternalLink, Folder, HelpCircle, LayoutDashboard, Megaphone, MessageCircle, Plus, Settings, ShoppingCart, Tv, BookOpen, type LucideIcon, } from "lucide-react"; import { NavMain } from "@/components/nav-main"; import { NavUser } from "@/components/nav-user"; import { Sidebar, SidebarContent, SidebarFooter, SidebarHeader, SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarRail, SidebarSeparator, SidebarGroup, SidebarGroupContent, } from "@/components/ui/sidebar"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { useAuth } from "@/components/auth/auth-provider"; import { useWorkspace } from "@/components/providers/workspace-provider"; import { buildRoute, BOT_USERNAME } from "@/lib/utils/constants"; // ============================================================================ // Types // ============================================================================ interface NavItem { title: string; url: string; icon?: LucideIcon; isActive?: boolean; requiredPermission?: "projects_read" | "placements_read" | "analytics_read" | "admin_full"; items?: { title: string; url: string; }[]; } interface FooterLink { title: string; url: string; icon: LucideIcon; external?: boolean; } // ============================================================================ // Footer Links Configuration // ============================================================================ const footerLinks: FooterLink[] = [ { title: "Поддержка", url: `https://t.me/${BOT_USERNAME}`, icon: MessageCircle, external: true, }, { title: "Информационный канал", url: "https://t.me/tgex_news", icon: Megaphone, external: true, }, { title: "База знаний", url: "https://docs.tgex.io", icon: BookOpen, external: true, }, ]; // ============================================================================ // Component // ============================================================================ export function AppSidebar({ ...props }: React.ComponentProps) { const params = useParams(); const workspaceId = params?.workspaceId as string; const { user } = useAuth(); const { workspaces, currentWorkspace, setCurrentWorkspace, hasPermission, isAdmin, createWorkspace, } = useWorkspace(); const [showCreateDialog, setShowCreateDialog] = React.useState(false); const [newWorkspaceName, setNewWorkspaceName] = React.useState(""); const [isCreating, setIsCreating] = React.useState(false); // Build navigation items based on permissions const navItems: NavItem[] = React.useMemo(() => { if (!workspaceId) return []; const items: NavItem[] = [ { title: "Главная", url: buildRoute.dashboard(workspaceId), icon: LayoutDashboard, isActive: true, }, ]; // Projects - always visible (contains instructions how to add projects via bot) items.push({ title: "Проекты", url: buildRoute.projects(workspaceId), icon: Tv, }); // Channels catalog (available to all) items.push({ title: "Каталог каналов", url: buildRoute.channels(workspaceId), icon: Building2, }); // Purchase Plans (requires placements_read or admin) if (hasPermission("placements_read")) { items.push({ title: "Планы закупок", url: buildRoute.purchasePlans(workspaceId), icon: ClipboardList, requiredPermission: "placements_read", }); } // Creatives (requires placements_read or admin) if (hasPermission("placements_read")) { items.push({ title: "Креативы", url: buildRoute.creatives(workspaceId), icon: Folder, requiredPermission: "placements_read", items: [ { title: "Все креативы", url: buildRoute.creatives(workspaceId), }, ...(hasPermission("placements_write") ? [ { title: "Создать", url: buildRoute.creativeNew(workspaceId), }, ] : []), ], }); } // Placements (requires placements_read or admin) if (hasPermission("placements_read")) { items.push({ title: "Размещения", url: buildRoute.placements(workspaceId), icon: ShoppingCart, requiredPermission: "placements_read", items: [ { title: "Все размещения", url: buildRoute.placements(workspaceId), }, ...(hasPermission("placements_write") ? [ { title: "Создать", url: buildRoute.placementNew(workspaceId), }, ] : []), ], }); } // Analytics (requires analytics_read or admin) if (hasPermission("analytics_read")) { items.push({ title: "Аналитика", url: buildRoute.analytics(workspaceId), icon: BarChart3, requiredPermission: "analytics_read", items: [ { title: "Обзор", url: buildRoute.analytics(workspaceId), }, { title: "Расходы", url: buildRoute.analyticsSpending(workspaceId), }, { title: "Каналы", url: buildRoute.analyticsChannels(workspaceId), }, { title: "Креативы", url: buildRoute.analyticsCreatives(workspaceId), }, ], }); } // Settings (admin only) if (isAdmin) { items.push({ title: "Настройки", url: buildRoute.settings(workspaceId), icon: Settings, requiredPermission: "admin_full", items: [ { title: "Участники", url: buildRoute.members(workspaceId), }, { title: "Приглашения", url: buildRoute.invites(workspaceId), }, ], }); } return items; }, [workspaceId, hasPermission, isAdmin]); const handleCreateWorkspace = async () => { if (!newWorkspaceName.trim()) return; try { setIsCreating(true); const workspace = await createWorkspace(newWorkspaceName.trim()); setShowCreateDialog(false); setNewWorkspaceName(""); setCurrentWorkspace(workspace); } catch (err) { console.error("Failed to create workspace:", err); } finally { setIsCreating(false); } }; return ( <> {/* Workspace Switcher */}
{currentWorkspace?.name || "TGEX"} {workspaces.length} воркспейс{workspaces.length === 1 ? "" : workspaces.length < 5 ? "а" : "ов"}
Воркспейсы {workspaces.map((workspace) => ( setCurrentWorkspace(workspace)} className="gap-2 p-2" >
{workspace.name} {currentWorkspace?.id === workspace.id && ( )}
))} setShowCreateDialog(true)} className="gap-2 p-2" >
Создать воркспейс
{/* Footer Links */} {footerLinks.map((link) => ( {link.title} {link.external && ( )} ))} {/* User */} {user && ( )}
{/* Create Workspace Dialog */} Создать воркспейс Воркспейс — это изолированное пространство для вашей команды
setNewWorkspaceName(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") { handleCreateWorkspace(); } }} />
); }