Update Navbar header to match reference design with authentic owl logo, cyan links, dividers, search, and login button

This commit is contained in:
alireza 2026-08-24 13:21:00 +03:30
parent 41d4d2af80
commit 4a68f54c33
1 changed files with 344 additions and 163 deletions

View File

@ -1,23 +1,21 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect, useRef } from 'react';
import { Link, useLocation } from 'react-router-dom'; import { Link, useLocation, useNavigate } from 'react-router-dom';
import { import {
Sparkles, Search,
Menu, Menu,
X, X,
Layers,
Cpu,
CreditCard,
Building2,
GraduationCap,
BookOpen, BookOpen,
Users, Cpu,
Phone, Layers,
Download, Building2,
LogIn, CreditCard,
Bot, Sparkles,
MessageSquare ArrowLeft,
GraduationCap
} from 'lucide-react'; } from 'lucide-react';
import siteInfo from '../../data/siteInfo.json'; import postsData from '../../data/posts.json';
import toolsData from '../../data/tools.json';
import modelsData from '../../data/models.json';
interface NavbarProps { interface NavbarProps {
onOpenAuth: () => void; onOpenAuth: () => void;
@ -25,12 +23,16 @@ interface NavbarProps {
export const Navbar: React.FC<NavbarProps> = ({ onOpenAuth }) => { export const Navbar: React.FC<NavbarProps> = ({ onOpenAuth }) => {
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const [isSearchOpen, setIsSearchOpen] = useState(false);
const [searchQuery, setSearchQuery] = useState('');
const [scrolled, setScrolled] = useState(false); const [scrolled, setScrolled] = useState(false);
const location = useLocation(); const location = useLocation();
const navigate = useNavigate();
const searchInputRef = useRef<HTMLInputElement>(null);
useEffect(() => { useEffect(() => {
const handleScroll = () => { const handleScroll = () => {
if (window.scrollY > 20) { if (window.scrollY > 15) {
setScrolled(true); setScrolled(true);
} else { } else {
setScrolled(false); setScrolled(false);
@ -40,174 +42,353 @@ export const Navbar: React.FC<NavbarProps> = ({ onOpenAuth }) => {
return () => window.removeEventListener('scroll', handleScroll); return () => window.removeEventListener('scroll', handleScroll);
}, []); }, []);
// Close mobile menu on route change // Close menus on route change
useEffect(() => { useEffect(() => {
setIsOpen(false); setIsOpen(false);
setIsSearchOpen(false);
}, [location.pathname]); }, [location.pathname]);
// Focus search input when modal opens
useEffect(() => {
if (isSearchOpen) {
setTimeout(() => {
searchInputRef.current?.focus();
}, 100);
}
}, [isSearchOpen]);
// Handle escape key to close search modal
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
setIsSearchOpen(false);
}
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, []);
const navLinks = [ const navLinks = [
{ name: 'صفحه اصلی', path: '/' }, { name: 'خانه', path: '/' },
{ name: 'ابزارها', path: '/tools', icon: Layers }, { name: 'رسانه', path: '/blog' },
{ name: 'مدل‌های زبانی', path: '/models', icon: Cpu }, { name: 'مدل‌های زبانی', path: '/models' },
{ name: 'تعرفه‌ها', path: '/pricing', icon: CreditCard }, { name: 'ابزارها', path: '/tools' },
{ name: 'راهکارهای سازمانی', path: '/enterprise', icon: Building2 }, { name: 'راهکارهای سازمانی', path: '/enterprise' },
{ name: 'آموزش هوش مصنوعی', path: '/academy', icon: GraduationCap }, { name: 'تعرفه', path: '/pricing' },
{ name: 'رسانه و مقالات', path: '/blog', icon: BookOpen }, { name: '+ هوشان', path: '/academy' },
{ name: 'دستیار چت', path: '/chat', icon: MessageSquare, highlight: true },
{ name: 'درباره ما', path: '/about', icon: Users },
{ name: 'تماس با ما', path: '/contact', icon: Phone },
]; ];
// Quick search filter
const filteredPosts = searchQuery.trim()
? postsData.filter(p => p.title.toLowerCase().includes(searchQuery.toLowerCase())).slice(0, 4)
: [];
const filteredTools = searchQuery.trim()
? toolsData.filter(t => t.title.toLowerCase().includes(searchQuery.toLowerCase()) || t.description.toLowerCase().includes(searchQuery.toLowerCase())).slice(0, 3)
: [];
const filteredModels = searchQuery.trim()
? modelsData.filter(m => m.name.toLowerCase().includes(searchQuery.toLowerCase()) || m.provider.toLowerCase().includes(searchQuery.toLowerCase())).slice(0, 3)
: [];
const hasSearchResults = filteredPosts.length > 0 || filteredTools.length > 0 || filteredModels.length > 0;
return ( return (
<header className={`fixed top-0 left-0 right-0 z-50 transition-all duration-300 ${ <>
scrolled <header
? 'bg-slate-950/85 backdrop-blur-md border-b border-slate-800/80 shadow-lg shadow-black/20 py-3' className={`fixed top-0 left-0 right-0 z-50 bg-white transition-all duration-200 border-b border-gray-100 ${
: 'bg-transparent py-5' scrolled ? 'shadow-md py-2.5 sm:py-3' : 'shadow-xs py-3 sm:py-3.5'
}`}> }`}
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> >
<div className="flex items-center justify-between"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex items-center justify-between">
{/* Right: Logo & Brand */}
<Link to="/" className="flex items-center gap-3 group"> {/* Right Side: Logo & Navigation Links */}
<div className="relative flex items-center justify-center w-10 h-10 rounded-xl bg-gradient-to-tr from-indigo-600 via-indigo-500 to-cyan-400 p-[1.5px] shadow-lg shadow-indigo-500/25 group-hover:scale-105 transition-transform overflow-hidden"> <div className="flex items-center gap-4 lg:gap-6">
<div className="w-full h-full bg-slate-950 rounded-[10px] flex items-center justify-center p-1"> {/* Authentic Houshan Owl Logo */}
<Link to="/" className="flex items-center flex-shrink-0 group">
<img <img
src="/storage/banner/owl_logo.png" src="/storage/banner/owl_logo.png"
alt="Houshan AI Logo" alt="هوشان"
className="w-full h-full object-contain" className="h-10 sm:h-11 w-auto object-contain transition-transform group-hover:scale-105"
/> />
</div> </Link>
<span className="absolute -top-1 -right-1 flex h-2.5 w-2.5">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-cyan-400 opacity-75"></span> {/* Desktop Navigation Links separated by | */}
<span className="relative inline-flex rounded-full h-2.5 w-2.5 bg-cyan-500"></span> <nav className="hidden lg:flex items-center">
</span> {navLinks.map((link, index) => {
const isActive = location.pathname === link.path;
return (
<React.Fragment key={link.path}>
<Link
to={link.path}
className={`text-sm xl:text-[15px] font-bold transition-colors duration-150 px-2.5 py-1 ${
isActive
? 'text-sky-600 font-extrabold'
: 'text-[#38b6ff] hover:text-sky-600'
}`}
>
{link.name}
</Link>
{index < navLinks.length - 1 && (
<span className="text-gray-800/90 font-light text-sm select-none px-1">
|
</span>
)}
</React.Fragment>
);
})}
</nav>
</div> </div>
<div className="flex flex-col"> {/* Left Side: Search Button & Login/Register Button */}
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-3 sm:gap-4">
<span className="text-xl font-extrabold tracking-tight text-white font-vazir">
{siteInfo.name} {/* Search Icon Button */}
</span> <button
<span className="px-1.5 py-0.5 text-[10px] font-bold rounded bg-indigo-500/20 text-indigo-300 border border-indigo-500/30"> type="button"
AI onClick={() => setIsSearchOpen(true)}
</span> className="w-9 h-9 sm:w-10 sm:h-10 rounded-lg bg-gray-100 hover:bg-gray-200/80 border border-gray-200/70 flex items-center justify-center text-[#38b6ff] hover:text-sky-600 transition-all focus:outline-none"
</div> aria-label="جستجو در سایت"
<span className="text-[10px] text-slate-400 font-medium hidden sm:inline"> title="جستجو"
دستیار هوش مصنوعی مدیران >
</span> <Search className="w-4 h-4 sm:w-5 sm:h-5 stroke-[2.4]" />
</button>
{/* Login / Register Button */}
<button
type="button"
onClick={onOpenAuth}
className="bg-[#54b4d3] hover:bg-[#44a5c4] active:scale-[0.98] text-white font-bold text-xs sm:text-sm px-4 sm:px-6 py-2 sm:py-2.5 rounded-xl shadow-xs transition-all duration-150 flex items-center justify-center whitespace-nowrap cursor-pointer"
>
ورود / ثبت نام
</button>
{/* Mobile Menu Toggle Button */}
<button
type="button"
onClick={() => setIsOpen(!isOpen)}
className="lg:hidden p-2 rounded-lg bg-gray-100 text-gray-700 hover:text-sky-600 border border-gray-200 focus:outline-none"
aria-label="منو"
>
{isOpen ? <X className="w-5 h-5" /> : <Menu className="w-5 h-5" />}
</button>
</div> </div>
</Link>
{/* Center: Desktop Navigation Links */}
<nav className="hidden xl:flex items-center gap-1 bg-slate-900/60 p-1.5 rounded-full border border-slate-800/80 backdrop-blur-md">
{navLinks.slice(0, 8).map((link) => {
const isActive = location.pathname === link.path;
return (
<Link
key={link.path}
to={link.path}
className={`px-3 py-1.5 rounded-full text-xs font-medium transition-all duration-200 flex items-center gap-1.5 ${
link.highlight
? 'bg-gradient-to-r from-indigo-600 to-cyan-600 text-white shadow-sm shadow-indigo-500/20 hover:opacity-95'
: isActive
? 'bg-indigo-600/20 text-indigo-300 border border-indigo-500/30 shadow-inner'
: 'text-slate-300 hover:text-white hover:bg-slate-800/60'
}`}
>
{link.name}
</Link>
);
})}
</nav>
{/* Left: Actions & Auth Button */}
<div className="hidden sm:flex items-center gap-2.5">
<a
href={siteInfo.appDownloadUrl}
target="_blank"
rel="noopener noreferrer"
className="px-3.5 py-2 rounded-xl text-xs font-medium text-slate-300 hover:text-white bg-slate-900/80 hover:bg-slate-800 border border-slate-800 transition-all flex items-center gap-1.5"
>
<Download className="w-3.5 h-3.5 text-cyan-400" />
<span>دانلود اپلیکیشن</span>
</a>
<button
onClick={onOpenAuth}
className="relative group overflow-hidden px-4 py-2 rounded-xl text-xs font-semibold text-white bg-gradient-to-r from-indigo-600 to-indigo-500 hover:from-indigo-500 hover:to-cyan-500 shadow-md shadow-indigo-600/20 hover:shadow-indigo-600/30 transition-all flex items-center gap-1.5"
>
<LogIn className="w-3.5 h-3.5" />
<span>ورود / ثبتنام</span>
</button>
</div> </div>
{/* Mobile Menu Toggle Button */}
<div className="flex xl:hidden items-center gap-2">
<button
onClick={onOpenAuth}
className="p-2 rounded-lg bg-indigo-600/20 text-indigo-300 border border-indigo-500/30 text-xs sm:hidden"
>
<LogIn className="w-4 h-4" />
</button>
<button
onClick={() => setIsOpen(!isOpen)}
className="p-2 rounded-xl bg-slate-900 border border-slate-800 text-slate-300 hover:text-white focus:outline-none"
aria-label="منو"
>
{isOpen ? <X className="w-5 h-5" /> : <Menu className="w-5 h-5" />}
</button>
</div>
</div> </div>
</div>
{/* Mobile Drawer Navigation */} {/* Mobile Navigation Drawer */}
{isOpen && ( {isOpen && (
<div className="xl:hidden bg-slate-950/95 border-b border-slate-800 backdrop-blur-xl px-4 pt-3 pb-6 space-y-2 animate-in slide-in-from-top duration-200"> <div className="lg:hidden bg-white border-t border-gray-100 px-4 pt-3 pb-6 space-y-2 shadow-lg animate-in slide-in-from-top-2 duration-150">
<div className="grid grid-cols-2 gap-2 pt-2"> <div className="grid grid-cols-2 gap-2 py-2">
{navLinks.map((link) => { {navLinks.map((link) => {
const Icon = link.icon; const isActive = location.pathname === link.path;
const isActive = location.pathname === link.path; return (
return ( <Link
<Link key={link.path}
key={link.path} to={link.path}
to={link.path} onClick={() => setIsOpen(false)}
className={`flex items-center gap-2 p-2.5 rounded-xl text-xs font-medium transition-colors ${ className={`flex items-center justify-between p-2.5 rounded-xl text-sm font-bold transition-colors ${
isActive isActive
? 'bg-indigo-600/20 text-indigo-300 border border-indigo-500/30' ? 'bg-sky-50 text-sky-600 border border-sky-200'
: 'text-slate-300 hover:bg-slate-900 hover:text-white border border-transparent' : 'text-[#38b6ff] hover:bg-gray-50 hover:text-sky-600'
}`} }`}
> >
{Icon && <Icon className="w-4 h-4 text-indigo-400" />} <span>{link.name}</span>
<span>{link.name}</span> </Link>
</Link> );
); })}
})} </div>
<div className="pt-3 border-t border-gray-100 flex flex-col gap-2">
<button
onClick={() => {
setIsOpen(false);
setIsSearchOpen(true);
}}
className="w-full py-2.5 rounded-xl text-center text-sm font-medium text-gray-700 bg-gray-100 hover:bg-gray-200/80 border border-gray-200 flex items-center justify-center gap-2"
>
<Search className="w-4 h-4 text-[#38b6ff]" />
<span>جستجو در مقالات و ابزارها</span>
</button>
<button
onClick={() => {
setIsOpen(false);
onOpenAuth();
}}
className="w-full py-2.5 rounded-xl text-center text-sm font-bold text-white bg-[#54b4d3] hover:bg-[#44a5c4] shadow-xs flex items-center justify-center gap-2"
>
<span>ورود / ثبت نام در هوشان</span>
</button>
</div>
</div> </div>
)}
</header>
<div className="pt-3 border-t border-slate-800/80 flex flex-col gap-2"> {/* Global Interactive Search Modal */}
<a {isSearchOpen && (
href={siteInfo.appDownloadUrl} <div className="fixed inset-0 z-50 flex items-start justify-center pt-20 sm:pt-28 px-4 bg-black/50 backdrop-blur-xs animate-in fade-in duration-150">
target="_blank" <div
rel="noopener noreferrer" className="w-full max-w-2xl bg-white rounded-2xl shadow-2xl border border-gray-200 overflow-hidden text-slate-800 animate-in zoom-in-95 duration-150"
className="w-full py-2.5 rounded-xl text-center text-xs font-medium text-slate-300 bg-slate-900 border border-slate-800 flex items-center justify-center gap-2" onClick={(e) => e.stopPropagation()}
> >
<Download className="w-4 h-4 text-cyan-400" /> {/* Search Input Bar */}
<span>دانلود مستقیم اپلیکیشن هوشان</span> <div className="relative flex items-center px-4 py-3.5 border-b border-gray-100 bg-gray-50/50">
</a> <Search className="w-5 h-5 text-[#38b6ff] ml-3 flex-shrink-0" />
<button <input
onClick={() => { ref={searchInputRef}
setIsOpen(false); type="text"
onOpenAuth(); value={searchQuery}
}} onChange={(e) => setSearchQuery(e.target.value)}
className="w-full py-2.5 rounded-xl text-center text-xs font-semibold text-white bg-gradient-to-r from-indigo-600 to-cyan-600 shadow-md flex items-center justify-center gap-2" placeholder="جستجو در مقالات، ابزارها، مدل‌ها و دوره‌ها..."
> className="w-full bg-transparent text-slate-800 placeholder-gray-400 text-sm sm:text-base focus:outline-none"
<LogIn className="w-4 h-4" /> />
<span>ورود / ثبتنام در حساب کاربری</span> {searchQuery && (
</button> <button
type="button"
onClick={() => setSearchQuery('')}
className="p-1 rounded-md text-gray-400 hover:text-gray-600 mr-2"
>
<X className="w-4 h-4" />
</button>
)}
<button
type="button"
onClick={() => setIsSearchOpen(false)}
className="p-1.5 rounded-lg text-gray-400 hover:text-gray-700 hover:bg-gray-200/50 transition-colors"
title="بستن (Esc)"
>
<X className="w-5 h-5" />
</button>
</div>
{/* Live Search Results */}
<div className="max-h-[60vh] overflow-y-auto p-4 space-y-4">
{!searchQuery.trim() ? (
<div className="py-6 text-center text-gray-500 space-y-3">
<p className="text-sm">عبارت مورد نظر خود را برای جستجو وارد کنید</p>
<div className="flex flex-wrap items-center justify-center gap-2 pt-2">
<span className="text-xs text-gray-400">پیشنهادها:</span>
{['هوش مصنوعی', 'چت‌بات', 'Claude 3.5', 'ChatGPT', 'تولید تصویر', 'سئو'].map((tag) => (
<button
key={tag}
type="button"
onClick={() => setSearchQuery(tag)}
className="px-2.5 py-1 rounded-lg bg-gray-100 hover:bg-sky-50 hover:text-sky-600 text-xs text-gray-600 transition-colors"
>
{tag}
</button>
))}
</div>
</div>
) : !hasSearchResults ? (
<div className="py-8 text-center text-gray-500">
<p className="text-sm">نتیجهای برای «{searchQuery}» یافت نشد.</p>
</div>
) : (
<div className="space-y-4">
{/* Blog Articles */}
{filteredPosts.length > 0 && (
<div>
<h4 className="text-xs font-bold text-gray-400 mb-2 flex items-center gap-1.5">
<BookOpen className="w-3.5 h-3.5 text-[#38b6ff]" />
<span>مقالات و آموزشها</span>
</h4>
<div className="space-y-1.5">
{filteredPosts.map((post) => (
<Link
key={post.id}
to={`/blog/${post.slug}`}
onClick={() => setIsSearchOpen(false)}
className="flex items-center justify-between p-2.5 rounded-xl hover:bg-gray-50 border border-transparent hover:border-gray-200 transition-all text-right group"
>
<span className="text-xs sm:text-sm font-medium text-gray-800 group-hover:text-sky-600 line-clamp-1">
{post.title}
</span>
<ArrowLeft className="w-3.5 h-3.5 text-gray-400 group-hover:text-sky-600 -translate-x-1 group-hover:translate-x-0 transition-transform flex-shrink-0 mr-2" />
</Link>
))}
</div>
</div>
)}
{/* AI Tools */}
{filteredTools.length > 0 && (
<div>
<h4 className="text-xs font-bold text-gray-400 mb-2 flex items-center gap-1.5">
<Layers className="w-3.5 h-3.5 text-[#38b6ff]" />
<span>ابزارهای هوش مصنوعی</span>
</h4>
<div className="space-y-1.5">
{filteredTools.map((tool) => (
<Link
key={tool.id}
to="/tools"
onClick={() => setIsSearchOpen(false)}
className="flex items-center justify-between p-2.5 rounded-xl hover:bg-gray-50 border border-transparent hover:border-gray-200 transition-all text-right group"
>
<div>
<span className="text-xs sm:text-sm font-medium text-gray-800 group-hover:text-sky-600 block">
{tool.title}
</span>
<span className="text-[11px] text-gray-500 line-clamp-1">
{tool.description}
</span>
</div>
<ArrowLeft className="w-3.5 h-3.5 text-gray-400 group-hover:text-sky-600 flex-shrink-0 mr-2" />
</Link>
))}
</div>
</div>
)}
{/* AI Models */}
{filteredModels.length > 0 && (
<div>
<h4 className="text-xs font-bold text-gray-400 mb-2 flex items-center gap-1.5">
<Cpu className="w-3.5 h-3.5 text-[#38b6ff]" />
<span>مدلهای زبانی</span>
</h4>
<div className="space-y-1.5">
{filteredModels.map((model) => (
<Link
key={model.id}
to="/models"
onClick={() => setIsSearchOpen(false)}
className="flex items-center justify-between p-2.5 rounded-xl hover:bg-gray-50 border border-transparent hover:border-gray-200 transition-all text-right group"
>
<div>
<span className="text-xs sm:text-sm font-medium text-gray-800 group-hover:text-sky-600 block">
{model.name}
</span>
<span className="text-[11px] text-gray-500">
ارائه دهنده: {model.provider}
</span>
</div>
<ArrowLeft className="w-3.5 h-3.5 text-gray-400 group-hover:text-sky-600 flex-shrink-0 mr-2" />
</Link>
))}
</div>
</div>
)}
</div>
)}
</div>
{/* Modal Footer */}
<div className="px-4 py-2.5 bg-gray-50 border-t border-gray-100 flex items-center justify-between text-[11px] text-gray-500">
<span>هوشان پلتفرم جامع هوش مصنوعی</span>
<div className="flex items-center gap-1.5">
<kbd className="px-1.5 py-0.5 rounded bg-gray-200 text-gray-600 text-[10px] font-mono">ESC</kbd>
<span>جهت بستن</span>
</div>
</div>
</div> </div>
</div> </div>
)} )}
</header> </>
); );
}; };