feat(All): 第一版项目

This commit is contained in:
2025-12-19 09:33:04 +08:00
parent 154132f17e
commit 2f6831336e
35 changed files with 5120 additions and 0 deletions

View 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>
)
}