Files
LatencyTest/src/client/components/IpInput.tsx

72 lines
2.2 KiB
TypeScript

import { useState, useEffect } from 'react'
import { fetchUserIp } from '../api/latency'
import { useLanguage } from '../contexts/LanguageContext'
import './IpInput.css'
interface IpInputProps {
onTest: (target: 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)$/
const DOMAIN_REGEX = /^(?!:\/\/)([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/
function isValidTarget(value: string): boolean {
const trimmed = value.trim().toLowerCase()
return IP_REGEX.test(trimmed) || DOMAIN_REGEX.test(trimmed)
}
export default function IpInput({ onTest, testing }: IpInputProps) {
const [target, setTarget] = useState('')
const [loading, setLoading] = useState(true)
const [error, setError] = useState('')
const { t } = useLanguage()
useEffect(() => {
fetchUserIp()
.then(setTarget)
.catch(() => setError(t('IP检测失败', 'Failed to detect IP')))
.finally(() => setLoading(false))
}, [t])
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
const trimmed = target.trim()
if (!isValidTarget(trimmed)) {
setError(t('无效的IP地址或域名', 'Invalid IP address or domain'))
return
}
setError('')
onTest(trimmed)
}
return (
<form onSubmit={handleSubmit} className="ip-input-form">
<div className="input-wrapper">
<input
type="text"
value={target}
onChange={(e) => {
setTarget(e.target.value)
setError('')
}}
placeholder={loading ? t('正在检测IP...', 'Detecting IP...') : t('输入IP或域名', 'Enter IP or domain')}
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 || !target.trim()}>
{testing ? (
<>
<span className="spinner" />
{t('测试中...', 'Testing...')}
</>
) : (
t('开始测试', 'Test Latency')
)}
</button>
</form>
)
}