/
masterOK
/
FEF
Обзор
Документация
Войти
/
masterOK
/
FEF
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
Header.tsx
246 строк
9 KB
masterOK
upload files
10 ноя 2025, 19:52
10 ноя 2025, 19:52
2cb4a49
Код
Авторство
О чём код?
import { Link, useLocation } from "wouter"; import { Logo } from "./Logo"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Search, User, Menu, X, LogOut, Settings, Award, ShoppingBag, MessageSquare, Plus } from "lucide-react"; import { useState } from "react"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Sheet, SheetContent, SheetTrigger, } from "@/components/ui/sheet"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { NotificationsCenter } from "@/components/NotificationsCenter"; interface HeaderProps { currentUser?: { id: string; fullName: string; avatarUrl?: string; role: string; } | null; onLogout?: () => void; } export function Header({ currentUser, onLogout }: HeaderProps) { const [location] = useLocation(); const [mobileMenuOpen, setMobileMenuOpen] = useState(false); const [searchQuery, setSearchQuery] = useState(""); const navItems = [ { label: "Главная", path: "/" }, { label: "Мастера", path: "/masters" }, { label: "Топ мастера", path: "/masters/top" }, { label: "Статьи", path: "/articles" }, ...(currentUser?.role === "admin" ? [{ label: "Админ", path: "/admin" }] : []), ]; const handleSearch = (e: React.FormEvent) => { e.preventDefault(); if (searchQuery.trim()) { window.location.href = `/masters?search=${encodeURIComponent(searchQuery)}`; } }; return ( <header className="sticky top-0 z-50 w-full border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60"> <div className="container mx-auto flex h-16 items-center justify-between px-4"> <div className="flex items-center gap-6"> <Link href="/" className="flex items-center" data-testid="link-home"> <Logo size="md" /> </Link> <nav className="hidden md:flex items-center gap-6"> {navItems.map((item) => ( <Link key={item.path} href={item.path} className={`text-sm font-medium transition-colors hover:text-primary ${ location === item.path ? "text-primary" : "text-muted-foreground" }`} data-testid={`link-${item.label.toLowerCase()}`} > {item.label} </Link> ))} </nav> </div> <div className="hidden md:flex items-center gap-4 flex-1 max-w-md mx-8"> <form onSubmit={handleSearch} className="relative w-full"> <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" /> <Input type="search" placeholder="Поиск мастеров..." className="pl-10" value={searchQuery} onChange={(e) => setSearchQuery(e.target.value)} data-testid="input-search" /> </form> </div> <div className="flex items-center gap-2"> {currentUser ? ( <> <NotificationsCenter /> <DropdownMenu> <DropdownMenuTrigger asChild> <Button variant="ghost" size="icon" className="rounded-full" data-testid="button-profile"> <Avatar className="h-8 w-8"> <AvatarImage src={currentUser.avatarUrl} /> <AvatarFallback>{currentUser.fullName.charAt(0)}</AvatarFallback> </Avatar> </Button> </DropdownMenuTrigger> <DropdownMenuContent align="end" className="w-56"> <div className="flex items-center gap-2 p-2"> <Avatar className="h-8 w-8"> <AvatarImage src={currentUser.avatarUrl} /> <AvatarFallback>{currentUser.fullName.charAt(0)}</AvatarFallback> </Avatar> <div className="flex flex-col"> <p className="text-sm font-medium" data-testid="text-username">{currentUser.fullName}</p> <p className="text-xs text-muted-foreground">{currentUser.role}</p> </div> </div> <DropdownMenuSeparator /> <DropdownMenuItem asChild> <Link href="/profile" className="flex items-center gap-2 w-full" data-testid="link-profile"> <User className="h-4 w-4" /> Мой профиль </Link> </DropdownMenuItem> <DropdownMenuItem asChild> <Link href="/chats" className="flex items-center gap-2 w-full" data-testid="link-chats"> <MessageSquare className="h-4 w-4" /> Мои диалоги </Link> </DropdownMenuItem> <DropdownMenuItem asChild> <Link href="/orders" className="flex items-center gap-2 w-full" data-testid="link-orders"> <ShoppingBag className="h-4 w-4" /> Мои заказы </Link> </DropdownMenuItem> {currentUser.role === 'master' && ( <DropdownMenuItem asChild> <Link href="/services/create" className="flex items-center gap-2 w-full" data-testid="link-create-service"> <Plus className="h-4 w-4" /> Создать услугу </Link> </DropdownMenuItem> )} <DropdownMenuItem asChild> <Link href="/achievements" className="flex items-center gap-2 w-full" data-testid="link-achievements"> <Award className="h-4 w-4" /> Достижения </Link> </DropdownMenuItem> <DropdownMenuItem asChild> <Link href="/settings" className="flex items-center gap-2 w-full"> <Settings className="h-4 w-4" /> Настройки </Link> </DropdownMenuItem> <DropdownMenuSeparator /> <DropdownMenuItem onClick={onLogout} data-testid="button-logout"> <LogOut className="h-4 w-4 mr-2" /> Выйти </DropdownMenuItem> </DropdownMenuContent> </DropdownMenu> </> ) : ( <div className="hidden md:flex gap-2"> <Link href="/login"> <span> <Button variant="ghost" data-testid="button-login">Войти</Button> </span> </Link> <Link href="/register"> <span> <Button data-testid="button-register">Регистрация</Button> </span> </Link> </div> )} <Sheet open={mobileMenuOpen} onOpenChange={setMobileMenuOpen}> <SheetTrigger asChild className="md:hidden"> <Button variant="ghost" size="icon" data-testid="button-menu"> {mobileMenuOpen ? <X className="h-5 w-5" /> : <Menu className="h-5 w-5" />} </Button> </SheetTrigger> <SheetContent side="right" className="w-80"> <div className="flex flex-col gap-6 mt-8"> <form onSubmit={handleSearch} className="relative"> <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" /> <Input type="search" placeholder="Поиск мастеров..." className="pl-10" value={searchQuery} onChange={(e) => setSearchQuery(e.target.value)} /> </form> <nav className="flex flex-col gap-4"> {navItems.map((item) => ( <Link key={item.path} href={item.path} className={`text-base font-medium ${ location === item.path ? "text-primary" : "text-foreground" }`} onClick={() => setMobileMenuOpen(false)} > {item.label} </Link> ))} </nav> {!currentUser && ( <div className="flex flex-col gap-2 pt-4 border-t"> <Link href="/login"> <span> <Button variant="ghost" className="w-full" onClick={() => setMobileMenuOpen(false)}> Войти </Button> </span> </Link> <Link href="/register"> <span> <Button className="w-full" onClick={() => setMobileMenuOpen(false)}> Регистрация </Button> </span> </Link> </div> )} </div> </SheetContent> </Sheet> </div> </div> </header> ); }