feat(All): 第一版项目
This commit is contained in:
62
src/client/components/IpInput.tsx
Normal file
62
src/client/components/IpInput.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { fetchUserIp } from '../api/latency'
|
||||
import './IpInput.css'
|
||||
|
||||
interface IpInputProps {
|
||||
onTest: (ip: string) => void
|
||||
testing: boolean
|
||||
}
|
||||
|
||||
const IP_REGEX = /^(?:(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)$/
|
||||
|
||||
export default function IpInput({ onTest, testing }: IpInputProps) {
|
||||
const [ip, setIp] = useState('')
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
fetchUserIp()
|
||||
.then(setIp)
|
||||
.catch(() => setError('Failed to detect IP'))
|
||||
.finally(() => setLoading(false))
|
||||
}, [])
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
if (!IP_REGEX.test(ip)) {
|
||||
setError('Invalid IP address')
|
||||
return
|
||||
}
|
||||
setError('')
|
||||
onTest(ip)
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className="ip-input-form">
|
||||
<div className="input-wrapper">
|
||||
<input
|
||||
type="text"
|
||||
value={ip}
|
||||
onChange={(e) => {
|
||||
setIp(e.target.value)
|
||||
setError('')
|
||||
}}
|
||||
placeholder={loading ? 'Detecting IP...' : 'Enter IP address'}
|
||||
className={`ip-input ${error ? 'ip-input-error' : ''}`}
|
||||
disabled={testing || loading}
|
||||
/>
|
||||
{error && <span className="error-hint">{error}</span>}
|
||||
</div>
|
||||
<button type="submit" className="test-button" disabled={testing || loading || !ip}>
|
||||
{testing ? (
|
||||
<>
|
||||
<span className="spinner" />
|
||||
Testing...
|
||||
</>
|
||||
) : (
|
||||
'Test Latency'
|
||||
)}
|
||||
</button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user