|
1
|
import { useState } from "react"; |
|
2
|
import { useLocation } from "wouter"; |
|
3
|
import { useQuery } from "@tanstack/react-query"; |
|
4
|
import { Input } from "@/components/ui/input"; |
|
5
|
import { Button } from "@/components/ui/button"; |
|
6
|
import { MobileMenu } from "@/components/layout/mobile-menu"; |
|
7
|
import { |
|
8
|
DropdownMenu, |
|
9
|
DropdownMenuContent, |
|
10
|
DropdownMenuItem, |
|
11
|
DropdownMenuSeparator, |
|
12
|
DropdownMenuTrigger, |
|
13
|
DropdownMenuLabel, |
|
14
|
} from "@/components/ui/dropdown-menu"; |
|
15
|
import { useAuth } from "@/hooks/useAuth"; |
|
16
|
import { useTheme } from "@/contexts/theme-context"; |
|
17
|
import { ChevronDown, Search, Heart, User as UserIcon, LayoutGrid, Wrench, HelpCircle, Monitor, Download, CloudOff, Mail, Settings } from "lucide-react"; |
|
18
|
import type { RepairGuide, User } from "@shared/schema"; |
|
19
|
|
|
20
|
export default function Header() { |
|
21
|
const [, setLocation] = useLocation(); |
|
22
|
const [searchQuery, setSearchQuery] = useState(""); |
|
23
|
const [showResults, setShowResults] = useState(false); |
|
24
|
const { user, isAuthenticated } = useAuth(); |
|
25
|
const { setTheme } = useTheme(); |
|
26
|
|
|
27
|
const { data: searchResults = [] } = useQuery<RepairGuide[]>({ |
|
28
|
queryKey: ["/api/repair-guides/search", searchQuery], |
|
29
|
queryFn: () => fetch(`/api/repair-guides/search?q=${encodeURIComponent(searchQuery)}`).then(res => res.json()), |
|
30
|
enabled: searchQuery.length > 2, |
|
31
|
}); |
|
32
|
|
|
33
|
const handleSearch = (e: React.FormEvent) => { |
|
34
|
e.preventDefault(); |
|
35
|
if (searchQuery.trim()) { |
|
36
|
setShowResults(false); |
|
37
|
setLocation(`/?search=${encodeURIComponent(searchQuery)}`); |
|
38
|
} |
|
39
|
}; |
|
40
|
|
|
41
|
return ( |
|
42
|
<header className="sticky top-0 z-50 w-full border-b bg-background/80 backdrop-blur-xl supports-[backdrop-filter]:bg-background/60 border-border/40"> |
|
43
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> |
|
44
|
<div className="flex items-center justify-between h-16 gap-4"> |
|
45
|
|
|
46
|
{/* Brand */} |
|
47
|
<div className="flex items-center space-x-2 shrink-0"> |
|
48
|
<MobileMenu /> |
|
49
|
<button |
|
50
|
onClick={() => setLocation("/")} |
|
51
|
className="flex items-center gap-2 group transition-all" |
|
52
|
> |
|
53
|
<div className="p-1.5 rounded-lg bg-primary/10 text-primary group-hover:bg-primary group-hover:text-primary-foreground transition-all duration-300"> |
|
54
|
<Wrench className="h-5 w-5" /> |
|
55
|
</div> |
|
56
|
<h1 className="text-lg font-bold tracking-tight bg-gradient-to-r from-primary to-indigo-400 bg-clip-text text-transparent hidden sm:block"> |
|
57
|
JCRguru |
|
58
|
</h1> |
|
59
|
</button> |
|
60
|
</div> |
|
61
|
|
|
62
|
{/* Elegant Search Bar */} |
|
63
|
<div className="flex-1 max-w-2xl relative group"> |
|
64
|
<form onSubmit={handleSearch} className="relative"> |
|
65
|
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground group-focus-within:text-primary transition-colors" /> |
|
66
|
<Input |
|
67
|
type="text" |
|
68
|
value={searchQuery} |
|
69
|
onChange={(e) => { |
|
70
|
setSearchQuery(e.target.value); |
|
71
|
setShowResults(e.target.value.length > 2); |
|
72
|
}} |
|
73
|
onBlur={() => setTimeout(() => setShowResults(false), 200)} |
|
74
|
onFocus={() => setShowResults(searchQuery.length > 2)} |
|
75
|
className="w-full pl-10 h-10 bg-muted/50 border-none rounded-full focus-visible:ring-2 focus-visible:ring-primary/20 transition-all text-sm" |
|
76
|
placeholder="Search tech guides..." |
|
77
|
/> |
|
78
|
</form> |
|
79
|
|
|
80
|
{/* Glass Search Results */} |
|
81
|
{showResults && searchResults.length > 0 && ( |
|
82
|
<div className="absolute top-full left-0 right-0 mt-2 bg-popover/90 backdrop-blur-2xl border border-border/50 rounded-2xl shadow-glass overflow-hidden z-50"> |
|
83
|
<div className="p-2 space-y-1"> |
|
84
|
{searchResults.slice(0, 5).map((guide) => ( |
|
85
|
<button |
|
86
|
key={guide.id} |
|
87
|
onClick={() => { |
|
88
|
setLocation(`/guide/${guide.id}`); |
|
89
|
setShowResults(false); |
|
90
|
}} |
|
91
|
className="w-full p-3 flex flex-col items-start hover:bg-primary/10 rounded-xl transition-colors text-left" |
|
92
|
> |
|
93
|
<span className="text-sm font-semibold">{guide.title}</span> |
|
94
|
<span className="text-xs text-muted-foreground">{guide.category}</span> |
|
95
|
</button> |
|
96
|
))} |
|
97
|
</div> |
|
98
|
</div> |
|
99
|
)} |
|
100
|
</div> |
|
101
|
|
|
102
|
{/* Actions */} |
|
103
|
<div className="flex items-center gap-1 sm:gap-2 shrink-0"> |
|
104
|
|
|
105
|
<nav className="hidden lg:flex items-center gap-1 mr-2"> |
|
106
|
<Button variant="ghost" size="sm" onClick={() => setLocation("/")} className="rounded-full h-9 gap-2"> |
|
107
|
<LayoutGrid className="h-4 w-4" /> |
|
108
|
Guides |
|
109
|
</Button> |
|
110
|
|
|
111
|
<DropdownMenu> |
|
112
|
<DropdownMenuTrigger asChild> |
|
113
|
<Button variant="ghost" size="sm" className="rounded-full h-9 gap-2"> |
|
114
|
<Settings className="h-4 w-4" /> |
|
115
|
Tools |
|
116
|
<ChevronDown className="h-3 w-3" /> |
|
117
|
</Button> |
|
118
|
</DropdownMenuTrigger> |
|
119
|
<DropdownMenuContent align="end" className="w-56 mt-2 rounded-2xl p-2 shadow-glass border-border/50"> |
|
120
|
<DropdownMenuLabel className="px-3 py-2 text-xs font-bold uppercase tracking-wider text-muted-foreground">Repair Hub</DropdownMenuLabel> |
|
121
|
<DropdownMenuItem onClick={() => setLocation("/troubleshooting")} className="rounded-xl gap-3 p-2.5"> |
|
122
|
<HelpCircle className="h-4 w-4" /> Troubleshooting |
|
123
|
</DropdownMenuItem> |
|
124
|
<DropdownMenuItem onClick={() => setLocation("/device-finder")} className="rounded-xl gap-3 p-2.5"> |
|
125
|
<Monitor className="h-4 w-4" /> Device Finder |
|
126
|
</DropdownMenuItem> |
|
127
|
|
|
128
|
<DropdownMenuSeparator className="my-1 opacity-50" /> |
|
129
|
|
|
130
|
<DropdownMenuLabel className="px-3 py-2 text-xs font-bold uppercase tracking-wider text-muted-foreground">Manager</DropdownMenuLabel> |
|
131
|
<DropdownMenuItem onClick={() => setLocation("/downloads")} className="rounded-xl gap-3 p-2.5"> |
|
132
|
<Download className="h-4 w-4" /> Downloads |
|
133
|
</DropdownMenuItem> |
|
134
|
<DropdownMenuItem onClick={() => setLocation("/offline-settings")} className="rounded-xl gap-3 p-2.5"> |
|
135
|
<CloudOff className="h-4 w-4" /> Offline Mode |
|
136
|
</DropdownMenuItem> |
|
137
|
</DropdownMenuContent> |
|
138
|
</DropdownMenu> |
|
139
|
</nav> |
|
140
|
|
|
141
|
{isAuthenticated && ( |
|
142
|
<Button |
|
143
|
variant="ghost" |
|
144
|
size="icon" |
|
145
|
onClick={() => setLocation("/favorites")} |
|
146
|
className="rounded-full hover:bg-primary/10 hover:text-primary transition-colors h-9 w-9" |
|
147
|
> |
|
148
|
<Heart className="h-5 w-5" /> |
|
149
|
</Button> |
|
150
|
)} |
|
151
|
|
|
152
|
{/* Profile & Theme */} |
|
153
|
<DropdownMenu> |
|
154
|
<DropdownMenuTrigger asChild> |
|
155
|
<Button variant="ghost" className="rounded-full p-0.5 h-9 w-9 border border-border/50 overflow-hidden hover:ring-2 hover:ring-primary/20 transition-all"> |
|
156
|
{user?.profileImageUrl ? ( |
|
157
|
<img src={user.profileImageUrl} className="h-full w-full object-cover" alt="Me" /> |
|
158
|
) : ( |
|
159
|
<div className="h-full w-full bg-primary/10 flex items-center justify-center text-primary"> |
|
160
|
<UserIcon className="h-5 w-5" /> |
|
161
|
</div> |
|
162
|
)} |
|
163
|
</Button> |
|
164
|
</DropdownMenuTrigger> |
|
165
|
<DropdownMenuContent align="end" className="w-64 mt-2 rounded-2xl p-2 shadow-glass border-border/50"> |
|
166
|
<div className="px-3 py-3 mb-2 flex items-center gap-3 bg-muted/30 rounded-xl"> |
|
167
|
<div className="h-10 w-10 rounded-full bg-primary/20 flex items-center justify-center font-bold text-primary"> |
|
168
|
{user?.firstName?.charAt(0) || 'U'} |
|
169
|
</div> |
|
170
|
<div className="flex flex-col"> |
|
171
|
<span className="text-sm font-bold truncate max-w-[140px]">{user?.firstName || 'Tech Expert'}</span> |
|
172
|
<span className="text-[10px] text-muted-foreground truncate max-w-[140px]">{user?.email}</span> |
|
173
|
</div> |
|
174
|
</div> |
|
175
|
<DropdownMenuSeparator className="opacity-50" /> |
|
176
|
<DropdownMenuLabel className="px-3 py-2 text-[10px] font-bold uppercase tracking-widest text-muted-foreground">Appearance</DropdownMenuLabel> |
|
177
|
<div className="grid grid-cols-2 gap-1 p-1"> |
|
178
|
<Button variant="ghost" size="sm" onClick={() => setTheme("light")} className="h-8 text-[11px] rounded-lg justify-start px-2">☀️ Light</Button> |
|
179
|
<Button variant="ghost" size="sm" onClick={() => setTheme("dark")} className="h-8 text-[11px] rounded-lg justify-start px-2">🌙 Dark</Button> |
|
180
|
</div> |
|
181
|
</DropdownMenuContent> |
|
182
|
</DropdownMenu> |
|
183
|
|
|
184
|
</div> |
|
185
|
</div> |
|
186
|
</div> |
|
187
|
</header> |
|
188
|
); |
|
189
|
} |