http checking

This commit is contained in:
2025-12-21 03:46:37 +07:00
parent d3adf07c3f
commit 6bc35fc0bb

View File

@@ -29,9 +29,43 @@ export function LobbyPage() {
const [showAddGame, setShowAddGame] = useState(false) const [showAddGame, setShowAddGame] = useState(false)
const [gameTitle, setGameTitle] = useState('') const [gameTitle, setGameTitle] = useState('')
const [gameUrl, setGameUrl] = useState('') const [gameUrl, setGameUrl] = useState('')
const [gameUrlError, setGameUrlError] = useState<string | null>(null)
const [gameGenre, setGameGenre] = useState('') const [gameGenre, setGameGenre] = useState('')
const [isAddingGame, setIsAddingGame] = useState(false) const [isAddingGame, setIsAddingGame] = useState(false)
const validateUrl = (url: string): boolean => {
if (!url.trim()) return true // Empty is ok, will be caught by required check
try {
const parsed = new URL(url.trim())
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
return false
}
// Check that hostname has at least one dot (domain.tld)
const hostname = parsed.hostname
if (!hostname || !hostname.includes('.')) {
return false
}
// Check that TLD is valid (2-6 letters only, like com, ru, org, online)
const parts = hostname.split('.')
const tld = parts[parts.length - 1].toLowerCase()
if (tld.length < 2 || tld.length > 6 || !/^[a-z]+$/.test(tld)) {
return false
}
return true
} catch {
return false
}
}
const handleGameUrlChange = (value: string) => {
setGameUrl(value)
if (value.trim() && !validateUrl(value)) {
setGameUrlError('Введите корректную ссылку (например: https://store.steampowered.com/...)')
} else {
setGameUrlError(null)
}
}
// Moderation // Moderation
const [moderatingGameId, setModeratingGameId] = useState<number | null>(null) const [moderatingGameId, setModeratingGameId] = useState<number | null>(null)
@@ -141,7 +175,7 @@ export function LobbyPage() {
} }
const handleAddGame = async () => { const handleAddGame = async () => {
if (!id || !gameTitle.trim() || !gameUrl.trim()) return if (!id || !gameTitle.trim() || !gameUrl.trim() || !validateUrl(gameUrl)) return
setIsAddingGame(true) setIsAddingGame(true)
try { try {
@@ -152,6 +186,7 @@ export function LobbyPage() {
}) })
setGameTitle('') setGameTitle('')
setGameUrl('') setGameUrl('')
setGameUrlError(null)
setGameGenre('') setGameGenre('')
setShowAddGame(false) setShowAddGame(false)
await loadData() await loadData()
@@ -1696,9 +1731,10 @@ export function LobbyPage() {
onChange={(e) => setGameTitle(e.target.value)} onChange={(e) => setGameTitle(e.target.value)}
/> />
<Input <Input
placeholder="Ссылка для скачивания" placeholder="Ссылка для скачивания (https://...)"
value={gameUrl} value={gameUrl}
onChange={(e) => setGameUrl(e.target.value)} onChange={(e) => handleGameUrlChange(e.target.value)}
error={gameUrlError || undefined}
/> />
<Input <Input
placeholder="Жанр (необязательно)" placeholder="Жанр (необязательно)"
@@ -1709,11 +1745,11 @@ export function LobbyPage() {
<NeonButton <NeonButton
onClick={handleAddGame} onClick={handleAddGame}
isLoading={isAddingGame} isLoading={isAddingGame}
disabled={!gameTitle || !gameUrl} disabled={!gameTitle || !gameUrl || !!gameUrlError}
> >
{isOrganizer ? 'Добавить' : 'Предложить'} {isOrganizer ? 'Добавить' : 'Предложить'}
</NeonButton> </NeonButton>
<NeonButton variant="outline" onClick={() => setShowAddGame(false)}> <NeonButton variant="outline" onClick={() => { setShowAddGame(false); setGameUrlError(null) }}>
Отмена Отмена
</NeonButton> </NeonButton>
</div> </div>