11import { useState , useRef , useEffect , useCallback } from 'react'
2+ import { useSearchParams } from 'react-router-dom'
23import { Search , X , Star , Loader2 , ExternalLink , MapPin , GitFork , Eye } from 'lucide-react'
34import { PortfolioPreviewModal } from './PortfolioPreviewModal'
45import type { PreviewUser , PreviewRepo } from './PortfolioPreviewModal'
@@ -45,12 +46,33 @@ type NavSearchProps = {
4546}
4647
4748export function NavSearch ( { variant = 'default' } : NavSearchProps ) {
49+ const [ searchParams , setSearchParams ] = useSearchParams ( )
4850 const [ open , setOpen ] = useState ( false )
4951 const [ query , setQuery ] = useState ( '' )
5052 const [ state , setState ] = useState < SearchState > ( { status : 'idle' } )
5153 const [ previewOpen , setPreviewOpen ] = useState ( false )
5254 const inputRef = useRef < HTMLInputElement > ( null )
5355 const containerRef = useRef < HTMLDivElement > ( null )
56+ const autoPreviewRef = useRef ( searchParams . get ( 'preview' ) )
57+
58+ const openPreview = ( ) => {
59+ if ( state . status !== 'done' ) return
60+ setSearchParams ( { preview : state . user . login } , { replace : true } )
61+ setPreviewOpen ( true )
62+ }
63+
64+ const closePreview = ( ) => {
65+ setSearchParams ( { } , { replace : true } )
66+ setPreviewOpen ( false )
67+ }
68+
69+ const close = ( ) => {
70+ setOpen ( false )
71+ setQuery ( '' )
72+ setState ( { status : 'idle' } )
73+ setSearchParams ( { } , { replace : true } )
74+ setPreviewOpen ( false )
75+ }
5476
5577 // Close dropdown when clicking outside — but NOT when the preview modal is open
5678 // (the modal is a portal outside containerRef; clicking inside it must not close the search)
@@ -68,7 +90,7 @@ export function NavSearch({ variant = 'default' }: NavSearchProps) {
6890 useEffect ( ( ) => {
6991 const onKey = ( e : KeyboardEvent ) => {
7092 if ( e . key !== 'Escape' ) return
71- if ( previewOpen ) { setPreviewOpen ( false ) } else { close ( ) }
93+ if ( previewOpen ) { closePreview ( ) } else { close ( ) }
7294 }
7395 window . addEventListener ( 'keydown' , onKey )
7496 return ( ) => window . removeEventListener ( 'keydown' , onKey )
@@ -79,13 +101,6 @@ export function NavSearch({ variant = 'default' }: NavSearchProps) {
79101 if ( open ) setTimeout ( ( ) => inputRef . current ?. focus ( ) , 50 )
80102 } , [ open ] )
81103
82- const close = ( ) => {
83- setOpen ( false )
84- setQuery ( '' )
85- setState ( { status : 'idle' } )
86- setPreviewOpen ( false )
87- }
88-
89104 const search = useCallback ( async ( username : string ) => {
90105 const name = username . trim ( )
91106 if ( ! name ) return
@@ -107,6 +122,28 @@ export function NavSearch({ variant = 'default' }: NavSearchProps) {
107122 }
108123 } , [ ] )
109124
125+ // Auto-fetch from ?preview=username on mount
126+ useEffect ( ( ) => {
127+ const username = autoPreviewRef . current
128+ if ( username ) {
129+ setQuery ( username )
130+ void search ( username )
131+ }
132+ // eslint-disable-next-line react-hooks/exhaustive-deps
133+ } , [ ] )
134+
135+ // Auto-open modal once the auto-fetch completes
136+ useEffect ( ( ) => {
137+ if (
138+ autoPreviewRef . current &&
139+ state . status === 'done' &&
140+ state . user . login . toLowerCase ( ) === autoPreviewRef . current . toLowerCase ( )
141+ ) {
142+ setPreviewOpen ( true )
143+ autoPreviewRef . current = null
144+ }
145+ } , [ state ] )
146+
110147 const handleKeyDown = ( e : React . KeyboardEvent < HTMLInputElement > ) => {
111148 if ( e . key === 'Enter' ) void search ( query )
112149 }
@@ -279,7 +316,7 @@ export function NavSearch({ variant = 'default' }: NavSearchProps) {
279316 { /* Preview CTA — full width, prominent */ }
280317 < button
281318 type = "button"
282- onClick = { ( ) => setPreviewOpen ( true ) }
319+ onClick = { openPreview }
283320 className = { `w-full flex items-center justify-center gap-1.5 rounded-md py-2 text-xs font-semibold transition ${
284321 isThreejs
285322 ? 'bg-blue-600/20 border border-blue-500/40 text-blue-300 hover:bg-blue-600/30'
@@ -288,6 +325,7 @@ export function NavSearch({ variant = 'default' }: NavSearchProps) {
288325 >
289326 < Eye className = "h-3.5 w-3.5" /> Preview portfolio
290327 </ button >
328+
291329 { /* Secondary row */ }
292330 < div className = "flex gap-2" >
293331 < a href = { state . user . html_url } target = "_blank" rel = "noreferrer" className = { viewBtnCls } >
@@ -309,7 +347,7 @@ export function NavSearch({ variant = 'default' }: NavSearchProps) {
309347 user = { state . user }
310348 repos = { state . repos }
311349 allRepos = { state . allRepos }
312- onClose = { ( ) => setPreviewOpen ( false ) }
350+ onClose = { closePreview }
313351 />
314352 ) }
315353 </ div >
0 commit comments