@@ -7,7 +7,7 @@ import { Button } from "@/components/ui/button"
77import { Input } from "@/components/ui/input"
88import { Label } from "@/components/ui/label"
99import { Card , CardContent , CardDescription , CardHeader , CardTitle } from "@/components/ui/card"
10- import { FolderIcon } from "lucide-react"
10+ import { FolderIcon , SearchIcon , ServerIcon , Loader2Icon , InfoIcon } from "lucide-react"
1111
1212interface LoginFormProps {
1313 onLogin : ( serverUrl : string ) => void
@@ -18,6 +18,36 @@ export function LoginForm({ onLogin }: LoginFormProps) {
1818 const [ password , setPassword ] = useState ( "" )
1919 const [ isLoading , setIsLoading ] = useState ( false )
2020 const [ error , setError ] = useState ( "" )
21+ const [ isScanning , setIsScanning ] = useState ( false )
22+ const [ scannedServers , setScannedServers ] = useState < string [ ] > ( [ ] )
23+ const [ hasScanned , setHasScanned ] = useState ( false )
24+
25+ const isDemo = serverUrl . startsWith ( "demo://" )
26+
27+ const handleScan = async ( ) => {
28+ if ( isDemo ) return
29+ setIsScanning ( true )
30+ setError ( "" )
31+ try {
32+ const res = await fetch ( `/api/scan?serverUrl=${ encodeURIComponent ( serverUrl ) } ` )
33+ if ( res . ok ) {
34+ const data = await res . json ( )
35+ setScannedServers ( data . targets || [ ] )
36+ setHasScanned ( true )
37+ } else {
38+ const data = await res . json ( ) . catch ( ( ) => ( { } ) )
39+ setError ( data . error || "Failed to scan local network." )
40+ }
41+ } catch ( err ) {
42+ setError ( "Error scanning local network." )
43+ } finally {
44+ setIsScanning ( false )
45+ }
46+ }
47+
48+ const handleSelectServer = ( ip : string ) => {
49+ setServerUrl ( `http://${ ip } :3923` )
50+ }
2151
2252 const handleSubmit = async ( e : React . FormEvent ) => {
2353 e . preventDefault ( )
@@ -65,18 +95,71 @@ export function LoginForm({ onLogin }: LoginFormProps) {
6595 < CardContent >
6696 < form onSubmit = { handleSubmit } className = "space-y-4" >
6797 < div className = "space-y-2" >
68- < Label htmlFor = "serverUrl" className = "text-sm font-medium" >
69- Server URL
70- </ Label >
98+ < div className = "flex items-center justify-between" >
99+ < Label htmlFor = "serverUrl" className = "text-sm font-medium" >
100+ Server URL
101+ </ Label >
102+ < Button
103+ type = "button"
104+ variant = "ghost"
105+ size = "sm"
106+ onClick = { handleScan }
107+ disabled = { isScanning || isDemo }
108+ className = "h-8 px-2 text-xs"
109+ >
110+ { isScanning ? (
111+ < Loader2Icon className = "h-3 w-3 mr-1 animate-spin" />
112+ ) : (
113+ < SearchIcon className = "h-3 w-3 mr-1" />
114+ ) }
115+ { isScanning ? "Scanning..." : "Scan Network" }
116+ </ Button >
117+ </ div >
71118 < Input
72119 id = "serverUrl"
73120 type = "text"
74- placeholder = "https ://127.0.0.1:3923"
121+ placeholder = "http ://127.0.0.1:3923"
75122 value = { serverUrl }
76123 onChange = { ( e ) => setServerUrl ( e . target . value ) }
77124 required
78125 className = "bg-background"
79126 />
127+
128+ { isDemo && (
129+ < div className = "mt-2 text-xs text-muted-foreground flex items-center gap-1.5 px-3 py-2 bg-yellow-500/10 text-yellow-500 rounded-md border border-yellow-500/20" >
130+ < InfoIcon className = "h-3.5 w-3.5" />
131+ < span > Network scanning is disabled for demo targets.</ span >
132+ </ div >
133+ ) }
134+
135+ { hasScanned && ! isDemo && (
136+ < div className = "mt-2 text-sm border rounded-md overflow-hidden bg-muted/30" >
137+ < div className = "bg-muted px-3 py-1.5 text-xs font-semibold flex items-center justify-between" >
138+ < span > Local Servers Found</ span >
139+ < span className = "text-muted-foreground" > { scannedServers . length } </ span >
140+ </ div >
141+ { scannedServers . length > 0 ? (
142+ < ul className = "divide-y max-h-32 overflow-y-auto" >
143+ { scannedServers . map ( ( ip ) => (
144+ < li key = { ip } >
145+ < button
146+ type = "button"
147+ onClick = { ( ) => handleSelectServer ( ip ) }
148+ className = "w-full text-left px-3 py-2 hover:bg-accent hover:text-accent-foreground flex items-center gap-2 text-xs transition-colors"
149+ >
150+ < ServerIcon className = "h-3 w-3 text-primary" />
151+ { ip } :3923
152+ </ button >
153+ </ li >
154+ ) ) }
155+ </ ul >
156+ ) : (
157+ < div className = "px-3 py-4 text-center text-xs text-muted-foreground" >
158+ No CopyParty servers found on port 3923.
159+ </ div >
160+ ) }
161+ </ div >
162+ ) }
80163 </ div >
81164 < div className = "space-y-2" > </ div >
82165 < div className = "space-y-2" >
0 commit comments