89 lines
2.8 KiB
TypeScript
89 lines
2.8 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-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/
|
|
|
|
function normalizeTarget(value: string): string {
|
|
let target = value.trim().toLowerCase()
|
|
// Remove protocol prefix if present
|
|
target = target.replace(/^https?:\/\//, '')
|
|
// Strip userinfo if a full URL with credentials was pasted
|
|
const atIndex = target.lastIndexOf('@')
|
|
if (atIndex !== -1) {
|
|
target = target.slice(atIndex + 1)
|
|
}
|
|
// Remove path, query, and fragment
|
|
target = target.split(/[/?#]/)[0]
|
|
// Remove port if present
|
|
target = target.split(':')[0]
|
|
return target
|
|
}
|
|
|
|
function isValidTarget(value: string): boolean {
|
|
const normalized = normalizeTarget(value)
|
|
return IP_REGEX.test(normalized) || DOMAIN_REGEX.test(normalized)
|
|
}
|
|
|
|
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()
|
|
if (!isValidTarget(target)) {
|
|
setError(t('无效的IP地址或域名', 'Invalid IP address or domain'))
|
|
return
|
|
}
|
|
const normalized = normalizeTarget(target)
|
|
setTarget(normalized) // Update display to show normalized value
|
|
setError('')
|
|
onTest(normalized)
|
|
}
|
|
|
|
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>
|
|
)
|
|
}
|