import { useEffect, useState } from 'react'; import type { Ref } from 'react'; import { useTranslation } from 'react-i18next'; import { Button, Input, Space, Tooltip, Typography } from 'antd'; import type { InputRef } from 'antd'; import { DatabaseOutlined } from '@ant-design/icons'; import { useValidateGeoTokens, type GeoTokenKind } from '@/api/queries/useGeodata'; import { parseTokens } from '@/lib/xray/geoTokens'; import type { GeodataTokenIssue, GeoKind } from '@/generated/types'; import GeoBrowserModal from './GeoBrowserModal'; const VALIDATION_DELAY = 600; // Each reason needs its own wording: a missing database is fixed under Geodata, // a missing category by picking another one, and a bad token by editing it. const REASON_KEYS: Record = { fileMissing: 'pages.xray.geoBrowser.missingDatabase', categoryMissing: 'pages.xray.geoBrowser.unknownCategories', attributeMissing: 'pages.xray.geoBrowser.unknownAttribute', syntax: 'pages.xray.geoBrowser.invalidToken', wrongKind: 'pages.xray.geoBrowser.wrongKind', }; export interface GeoTokenInputProps { value?: string; onChange?: (value: string) => void; onBlur?: () => void; kind: GeoTokenKind; placeholder?: string; id?: string; ref?: Ref; } export default function GeoTokenInput({ value = '', onChange, onBlur, kind, placeholder, id, ref, }: GeoTokenInputProps) { const { t } = useTranslation(); const [browsing, setBrowsing] = useState(false); const [issues, setIssues] = useState([]); const [checkFailed, setCheckFailed] = useState(false); const validate = useValidateGeoTokens(); const { mutateAsync } = validate; // An empty field has nothing to validate, so it clears during render rather // than waiting a commit for the effect to catch up. const isEmpty = parseTokens(value).length === 0; const [wasEmpty, setWasEmpty] = useState(isEmpty); if (isEmpty !== wasEmpty) { setWasEmpty(isEmpty); if (isEmpty) { setIssues([]); setCheckFailed(false); } } useEffect(() => { const tokens = parseTokens(value); if (tokens.length === 0) return; let cancelled = false; const timer = setTimeout(() => { mutateAsync({ tokens, kind }) .then((found) => { if (cancelled) return; setIssues(found); setCheckFailed(false); }) // A rejected check says nothing about the tokens, so the warnings are // dropped but replaced by a notice — silence here reads as "all valid". .catch(() => { if (cancelled) return; setIssues([]); setCheckFailed(true); }); }, VALIDATION_DELAY); return () => { cancelled = true; clearTimeout(timer); }; }, [value, kind, mutateAsync]); return ( <> onChange?.(event.target.value)} onBlur={onBlur} />