You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
204 lines
6.7 KiB
204 lines
6.7 KiB
import { useState, useEffect } from 'react'; |
|
import { Link, useLocation, useSearchParams } from 'react-router-dom'; |
|
import { useAuth } from '../../hooks/useAuth'; |
|
import { useChannels } from '../../hooks/useChannels'; |
|
import { APPS } from '../../config/apps'; |
|
import { OptimizedImage } from '../OptimizedImage/OptimizedImage'; |
|
|
|
export function Navbar() { |
|
const { isAuthenticated, logout, isAdmin } = useAuth(); |
|
const location = useLocation(); |
|
const [searchParams, setSearchParams] = useSearchParams(); |
|
const { channels } = useChannels(); |
|
|
|
// Detect current app from registry |
|
const getCurrentApp = (pathname: string) => { |
|
return APPS.find(app => pathname === app.link || pathname.startsWith(app.link + '/')); |
|
}; |
|
|
|
const currentApp = getCurrentApp(location.pathname); |
|
const isVideoApp = currentApp?.id === 'videos'; |
|
const [searchInput, setSearchInput] = useState(searchParams.get('search') || ''); |
|
|
|
// Sync search input with URL params |
|
useEffect(() => { |
|
setSearchInput(searchParams.get('search') || ''); |
|
}, [searchParams]); |
|
|
|
const handleLogout = async () => { |
|
await logout(); |
|
}; |
|
|
|
const handleSearchSubmit = (e: React.FormEvent) => { |
|
e.preventDefault(); |
|
const newParams = new URLSearchParams(searchParams); |
|
if (searchInput) { |
|
newParams.set('search', searchInput); |
|
} else { |
|
newParams.delete('search'); |
|
} |
|
newParams.set('page', '1'); |
|
setSearchParams(newParams); |
|
}; |
|
|
|
const handleSortChange = (e: React.ChangeEvent<HTMLSelectElement>) => { |
|
const newParams = new URLSearchParams(searchParams); |
|
newParams.set('sort', e.target.value); |
|
newParams.set('page', '1'); |
|
setSearchParams(newParams); |
|
}; |
|
|
|
const handleChannelChange = (e: React.ChangeEvent<HTMLSelectElement>) => { |
|
const newParams = new URLSearchParams(searchParams); |
|
if (e.target.value) { |
|
newParams.set('channel', e.target.value); |
|
} else { |
|
newParams.delete('channel'); |
|
} |
|
newParams.set('page', '1'); |
|
setSearchParams(newParams); |
|
}; |
|
|
|
const handleClearFilters = () => { |
|
setSearchInput(''); |
|
setSearchParams(new URLSearchParams()); |
|
}; |
|
|
|
const hasFilters = searchParams.get('search') || searchParams.get('channel') || |
|
(searchParams.get('sort') && searchParams.get('sort') !== 'newest'); |
|
|
|
return ( |
|
<> |
|
<header className="bg-white border-b-4 border-primary sticky top-0 z-50"> |
|
<div className="max-w-5xl mx-auto px-4 py-5"> |
|
<div className="flex items-center gap-3 justify-between"> |
|
<Link to="/" className="flex items-center gap-3"> |
|
<OptimizedImage |
|
src="/rainbow.png" |
|
alt="Rainbow" |
|
className="h-10 w-10 md:h-12 md:w-12 object-contain" |
|
width={48} |
|
height={48} |
|
loading="eager" |
|
fetchPriority="high" |
|
/> |
|
<h1 className="text-3xl md:text-4xl font-bold text-foreground" style={{ fontFamily: "'Butterfly Kids', cursive" }}>Rainbows, Cupcakes & Unicorns</h1> |
|
<OptimizedImage |
|
src="/cupcake.png" |
|
alt="Cupcake" |
|
className="h-10 w-10 md:h-12 md:w-12 object-contain" |
|
width={48} |
|
height={48} |
|
loading="eager" |
|
fetchPriority="high" |
|
/> |
|
</Link> |
|
|
|
<div className="flex items-center gap-3"> |
|
<Link |
|
to="/" |
|
className={`text-sm font-semibold px-3 py-2 rounded-full transition-all active:scale-95 ${location.pathname === '/' |
|
? 'bg-primary text-primary-foreground shadow-md' |
|
: 'bg-white text-foreground border-2 border-primary hover:bg-pink-50' |
|
}`} |
|
> |
|
Home |
|
</Link> |
|
|
|
{!isAuthenticated && ( |
|
<Link |
|
to="/register" |
|
className={`text-sm font-semibold px-3 py-2 rounded-full transition-all active:scale-95 ${location.pathname === '/register' |
|
? 'bg-primary text-primary-foreground shadow-md' |
|
: 'bg-white text-foreground border-2 border-primary hover:bg-pink-50' |
|
}`} |
|
> |
|
Sign In / Register |
|
</Link> |
|
)} |
|
|
|
{isAdmin && ( |
|
<Link |
|
to="/admin" |
|
className={`text-sm font-semibold px-3 py-2 rounded-full transition-all active:scale-95 ${location.pathname.startsWith('/admin') |
|
? 'bg-primary text-primary-foreground shadow-md' |
|
: 'bg-white text-foreground border-2 border-primary hover:bg-pink-50' |
|
}`} |
|
> |
|
Admin |
|
</Link> |
|
)} |
|
|
|
{isAuthenticated && ( |
|
<button |
|
onClick={handleLogout} |
|
className="px-4 py-2 bg-primary text-primary-foreground rounded-full font-semibold text-sm hover:bg-primary/90 transition-all active:scale-95 shadow-md" |
|
> |
|
Logout |
|
</button> |
|
)} |
|
</div> |
|
</div> |
|
</div> |
|
</header> |
|
|
|
{isVideoApp && ( |
|
<div className="bg-muted border-b border-border"> |
|
<div className="max-w-5xl mx-auto px-4 py-4"> |
|
<div className="flex flex-col sm:flex-row gap-4 items-stretch sm:items-center"> |
|
<form onSubmit={handleSearchSubmit} className="flex gap-2 flex-1 max-w-md"> |
|
<input |
|
type="text" |
|
placeholder="Search videos..." |
|
value={searchInput} |
|
onChange={(e) => setSearchInput(e.target.value)} |
|
className="flex-1 px-4 py-2 border border-border rounded-full bg-white text-sm focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent" |
|
/> |
|
<button |
|
type="submit" |
|
className="px-4 py-2 bg-primary text-primary-foreground rounded-full hover:bg-primary/90 transition-colors text-sm font-semibold" |
|
> |
|
🔍 |
|
</button> |
|
</form> |
|
|
|
<div className="flex gap-2 flex-wrap sm:flex-nowrap"> |
|
<select |
|
value={searchParams.get('sort') || 'newest'} |
|
onChange={handleSortChange} |
|
className="px-4 py-2 border border-border rounded-full bg-white text-sm cursor-pointer focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent" |
|
> |
|
<option value="newest">Newest</option> |
|
<option value="oldest">Oldest</option> |
|
<option value="popular">Most Popular</option> |
|
</select> |
|
|
|
<select |
|
value={searchParams.get('channel') || ''} |
|
onChange={handleChannelChange} |
|
className="px-4 py-2 border border-border rounded-full bg-white text-sm cursor-pointer focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent" |
|
> |
|
<option value="">All Channels</option> |
|
{channels.map(channel => ( |
|
<option key={channel.id} value={channel.id}> |
|
{channel.name} |
|
</option> |
|
))} |
|
</select> |
|
|
|
{hasFilters && ( |
|
<button |
|
onClick={handleClearFilters} |
|
className="px-4 py-2 border border-border rounded-full bg-white text-sm hover:bg-muted transition-colors whitespace-nowrap" |
|
> |
|
Clear Filters |
|
</button> |
|
)} |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
)} |
|
</> |
|
); |
|
}
|
|
|