33import Link from "next/link" ;
44import { Button } from "@/components/ui/button" ;
55import { Menu , X } from "lucide-react" ;
6- import { useState } from "react" ;
6+ import { useState , useEffect } from "react" ;
77import Image from "next/image" ;
88
9- // GitHub stars count - Update periodically or fetch dynamically
10- // Last updated: December 2024
11- const GITHUB_STARS = "13.6K" ;
9+ // Default fallback value if API fetch fails
10+ const DEFAULT_GITHUB_STARS = "13.6K" ;
11+
12+ // Format star count (e.g., 13600 -> "13.6K")
13+ function formatStarCount ( count : number ) : string {
14+ if ( count >= 1000 ) {
15+ const formatted = ( count / 1000 ) . toFixed ( 1 ) ;
16+ // Remove trailing .0 (e.g., "14.0K" -> "14K")
17+ return formatted . endsWith ( '.0' )
18+ ? formatted . slice ( 0 , - 2 ) + 'K'
19+ : formatted + 'K' ;
20+ }
21+ return count . toString ( ) ;
22+ }
23+
24+ // Custom hook to fetch GitHub stars
25+ function useGitHubStars ( owner : string , repo : string ) : string {
26+ const [ stars , setStars ] = useState < string > ( DEFAULT_GITHUB_STARS ) ;
27+
28+ useEffect ( ( ) => {
29+ const fetchStars = async ( ) => {
30+ try {
31+ const response = await fetch (
32+ `https://api.github.com/repos/${ owner } /${ repo } ` ,
33+ {
34+ headers : {
35+ 'Accept' : 'application/vnd.github.v3+json' ,
36+ } ,
37+ // Cache for 1 hour to avoid rate limiting
38+ next : { revalidate : 3600 }
39+ }
40+ ) ;
41+
42+ if ( ! response . ok ) {
43+ throw new Error ( 'Failed to fetch GitHub stars' ) ;
44+ }
45+
46+ const data = await response . json ( ) ;
47+ setStars ( formatStarCount ( data . stargazers_count ) ) ;
48+ } catch ( error ) {
49+ console . error ( 'Error fetching GitHub stars:' , error ) ;
50+ // Keep default value on error
51+ }
52+ } ;
53+
54+ fetchStars ( ) ;
55+ } , [ owner , repo ] ) ;
56+
57+ return stars ;
58+ }
1259
1360const navLinks = [
1461 { href : "#home" , label : "Home" } ,
@@ -28,6 +75,7 @@ const GitHubIcon = ({ className }: { className?: string }) => (
2875
2976export function Header ( ) {
3077 const [ isMenuOpen , setIsMenuOpen ] = useState ( false ) ;
78+ const githubStars = useGitHubStars ( 'keploy' , 'keploy' ) ;
3179
3280 return (
3381 < header className = "fixed top-0 left-0 right-0 z-50 bg-white border-b border-gray-100" >
@@ -45,36 +93,23 @@ export function Header() {
4593 </ div >
4694 </ Link >
4795
48- { /* Desktop Navigation */ }
49- < nav className = "hidden lg:flex items-center gap-8" aria-label = "Main navigation" >
50- { navLinks . map ( ( link ) => (
51- < Link
52- key = { link . href }
53- href = { link . href }
54- className = "text-sm font-medium text-gray-600 hover:text-[#F89559] transition-colors"
55- >
56- { link . label }
57- </ Link >
58- ) ) }
59- </ nav >
60-
61- { /* Right Side - GitHub Stars & Sign In */ }
62- < div className = "hidden md:flex items-center gap-4" >
63- { /* GitHub Stars Badge */ }
96+ { /* Right Side - GitHub Stars, Sign In & Menu */ }
97+ < div className = "flex items-center gap-4" >
98+ { /* GitHub Stars Badge - Hidden on mobile */ }
6499 < a
65100 href = "https://github.com/keploy/keploy"
66101 target = "_blank"
67102 rel = "noopener noreferrer"
68- className = "flex items-center gap-2 px-3 py-1.5 rounded-full bg-gray-50 hover:bg-gray-100 transition-colors"
103+ className = "hidden md: flex items-center gap-2 px-3 py-1.5 rounded-full bg-gray-50 hover:bg-gray-100 transition-colors"
69104 >
70105 < GitHubIcon className = "w-5 h-5 text-gray-800" />
71- < span className = "font-semibold text-gray-800" > { GITHUB_STARS } </ span >
106+ < span className = "font-semibold text-gray-800" > { githubStars } </ span >
72107 </ a >
73108
74- { /* Sign In Button */ }
109+ { /* Sign In Button - Hidden on mobile */ }
75110 < Button
76111 asChild
77- className = "bg-[#F89559] text-white hover:bg-[#e87b3a] font-semibold px-6 py-2.5 rounded-lg shadow-sm"
112+ className = "hidden md:flex bg-[#F89559] text-white hover:bg-[#e87b3a] font-semibold px-6 py-2.5 rounded-lg shadow-sm"
78113 >
79114 < a
80115 href = "https://forms.gle/R7RbuL39sc1TFW449"
@@ -84,65 +119,97 @@ export function Header() {
84119 Sign In
85120 </ a >
86121 </ Button >
87- </ div >
88122
89- { /* Mobile Menu Button */ }
90- < button
91- className = "lg:hidden p-2 rounded-lg hover:bg-gray-50 transition-colors"
92- onClick = { ( ) => setIsMenuOpen ( ! isMenuOpen ) }
93- aria-label = { isMenuOpen ? "Close menu" : "Open menu" }
94- aria-expanded = { isMenuOpen }
95- >
96- { isMenuOpen ? (
97- < X className = "w-6 h-6 text-gray-700" />
98- ) : (
99- < Menu className = "w-6 h-6 text-gray-700" />
100- ) }
101- </ button >
123+ { /* Hamburger Menu Button */ }
124+ < button
125+ className = "flex items-center gap-2 p-2 rounded-lg hover:bg-gray-50 transition-colors"
126+ onClick = { ( ) => setIsMenuOpen ( ! isMenuOpen ) }
127+ aria-label = { isMenuOpen ? "Close menu" : "Open menu" }
128+ aria-expanded = { isMenuOpen }
129+ >
130+ < span className = "hidden sm:inline text-sm font-medium text-gray-700" > MENU</ span >
131+ { isMenuOpen ? (
132+ < X className = "w-6 h-6 text-gray-700" />
133+ ) : (
134+ < Menu className = "w-6 h-6 text-gray-700" />
135+ ) }
136+ </ button >
137+ </ div >
102138 </ div >
103139
104- { /* Mobile Navigation */ }
140+ { /* Slide-in Navigation Panel */ }
105141 { isMenuOpen && (
106- < nav
107- className = "lg:hidden bg-white border-t border-gray-100 py-4"
108- aria-label = "Mobile navigation"
109- >
110- < div className = "flex flex-col gap-1 px-6" >
111- { navLinks . map ( ( link ) => (
112- < Link
113- key = { link . href }
114- href = { link . href }
115- className = "py-3 px-4 text-gray-700 hover:text-[#F89559] hover:bg-orange-50 rounded-lg transition-all font-medium"
142+ < >
143+ { /* Overlay */ }
144+ < div
145+ className = "fixed inset-0 bg-black/20 z-40"
146+ onClick = { ( ) => setIsMenuOpen ( false ) }
147+ aria-hidden = "true"
148+ />
149+
150+ { /* Side Panel */ }
151+ < nav
152+ className = "fixed top-0 right-0 h-full w-72 bg-white shadow-xl z-50 animate-in slide-in-from-right duration-300"
153+ aria-label = "Main navigation"
154+ >
155+ { /* Header */ }
156+ < div className = "flex items-center justify-between px-6 py-5 border-b border-gray-100" >
157+ < h2 className = "text-xl font-bold text-[#F89559]" > Navigation</ h2 >
158+ < button
116159 onClick = { ( ) => setIsMenuOpen ( false ) }
160+ className = "w-8 h-8 flex items-center justify-center rounded-md bg-[#F89559] text-white hover:bg-[#e87b3a] transition-colors"
161+ aria-label = "Close menu"
117162 >
118- { link . label }
119- </ Link >
120- ) ) }
121- < div className = "pt-4 mt-2 border-t border-gray-100 flex flex-col gap-3" >
122- < a
123- href = "https://github.com/keploy/keploy"
124- target = "_blank"
125- rel = "noopener noreferrer"
126- className = "flex items-center justify-center gap-2 py-3 px-4 rounded-lg bg-gray-50"
127- >
128- < GitHubIcon className = "w-5 h-5 text-gray-800" />
129- < span className = "font-semibold text-gray-800" > { GITHUB_STARS } Stars</ span >
130- </ a >
131- < Button
132- asChild
133- className = "w-full bg-[#F89559] text-white hover:bg-[#e87b3a] font-medium py-3 rounded-lg"
134- >
135- < a
136- href = "https://forms.gle/R7RbuL39sc1TFW449"
137- target = "_blank"
138- rel = "noopener noreferrer"
163+ < X className = "w-5 h-5" />
164+ </ button >
165+ </ div >
166+
167+ { /* Navigation Links */ }
168+ < div className = "flex flex-col px-6 py-4" >
169+ { navLinks . map ( ( link ) => (
170+ < Link
171+ key = { link . href }
172+ href = { link . href }
173+ className = "py-3 text-gray-700 hover:text-[#F89559] transition-colors font-medium text-lg"
174+ onClick = { ( ) => setIsMenuOpen ( false ) }
139175 >
140- Sign In
176+ { link . label }
177+ </ Link >
178+ ) ) }
179+ </ div >
180+
181+ { /* Social Icons at Bottom */ }
182+ < div className = "absolute bottom-8 left-6 right-6" >
183+ < div className = "flex items-center gap-4" >
184+ < a href = "https://keploy.slack.com" target = "_blank" rel = "noopener noreferrer" className = "text-gray-600 hover:text-[#F89559] transition-colors" aria-label = "Slack" >
185+ < svg className = "w-6 h-6" viewBox = "0 0 24 24" fill = "currentColor" >
186+ < path d = "M5.042 15.165a2.528 2.528 0 0 1-2.52 2.523A2.528 2.528 0 0 1 0 15.165a2.527 2.527 0 0 1 2.522-2.52h2.52v2.52zM6.313 15.165a2.527 2.527 0 0 1 2.521-2.52 2.527 2.527 0 0 1 2.521 2.52v6.313A2.528 2.528 0 0 1 8.834 24a2.528 2.528 0 0 1-2.521-2.522v-6.313zM8.834 5.042a2.528 2.528 0 0 1-2.521-2.52A2.528 2.528 0 0 1 8.834 0a2.528 2.528 0 0 1 2.521 2.522v2.52H8.834zM8.834 6.313a2.528 2.528 0 0 1 2.521 2.521 2.528 2.528 0 0 1-2.521 2.521H2.522A2.528 2.528 0 0 1 0 8.834a2.528 2.528 0 0 1 2.522-2.521h6.312zM18.956 8.834a2.528 2.528 0 0 1 2.522-2.521A2.528 2.528 0 0 1 24 8.834a2.528 2.528 0 0 1-2.522 2.521h-2.522V8.834zM17.688 8.834a2.528 2.528 0 0 1-2.523 2.521 2.527 2.527 0 0 1-2.52-2.521V2.522A2.527 2.527 0 0 1 15.165 0a2.528 2.528 0 0 1 2.523 2.522v6.312zM15.165 18.956a2.528 2.528 0 0 1 2.523 2.522A2.528 2.528 0 0 1 15.165 24a2.527 2.527 0 0 1-2.52-2.522v-2.522h2.52zM15.165 17.688a2.527 2.527 0 0 1-2.52-2.523 2.526 2.526 0 0 1 2.52-2.52h6.313A2.527 2.527 0 0 1 24 15.165a2.528 2.528 0 0 1-2.522 2.523h-6.313z" />
187+ </ svg >
188+ </ a >
189+ < a href = "https://twitter.com/keaboratory" target = "_blank" rel = "noopener noreferrer" className = "text-gray-600 hover:text-[#F89559] transition-colors" aria-label = "X (Twitter)" >
190+ < svg className = "w-6 h-6" viewBox = "0 0 24 24" fill = "currentColor" >
191+ < path d = "M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" />
192+ </ svg >
193+ </ a >
194+ < a href = "https://www.youtube.com/@kaboratory" target = "_blank" rel = "noopener noreferrer" className = "text-gray-600 hover:text-[#F89559] transition-colors" aria-label = "YouTube" >
195+ < svg className = "w-6 h-6" viewBox = "0 0 24 24" fill = "currentColor" >
196+ < path d = "M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z" />
197+ </ svg >
141198 </ a >
142- </ Button >
199+ < a href = "https://www.linkedin.com/company/keploy/" target = "_blank" rel = "noopener noreferrer" className = "text-gray-600 hover:text-[#F89559] transition-colors" aria-label = "LinkedIn" >
200+ < svg className = "w-6 h-6" viewBox = "0 0 24 24" fill = "currentColor" >
201+ < path d = "M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z" />
202+ </ svg >
203+ </ a >
204+ < a href = "https://github.com/keploy/keploy" target = "_blank" rel = "noopener noreferrer" className = "text-gray-600 hover:text-[#F89559] transition-colors" aria-label = "GitHub" >
205+ < svg className = "w-6 h-6" viewBox = "0 0 24 24" fill = "currentColor" >
206+ < path d = "M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
207+ </ svg >
208+ </ a >
209+ </ div >
143210 </ div >
144- </ div >
145- </ nav >
211+ </ nav >
212+ </ >
146213 ) }
147214 </ header >
148215 ) ;
0 commit comments