| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126 |
- import {
- ChangeEvent,
- KeyboardEvent,
- useCallback,
- useEffect,
- useState,
- } from 'react'
- import { getJSON } from '../../../../infrastructure/fetch-json'
- import useAbortController from '../../../../shared/hooks/use-abort-controller'
- const LOCAL_AND_DOMAIN_REGEX = /([^@]+)@(.+)/
- function matchLocalAndDomain(emailHint: string) {
- const match = emailHint.match(LOCAL_AND_DOMAIN_REGEX)
- if (match) {
- return { local: match[1], domain: match[2] }
- } else {
- return { local: null, domain: null }
- }
- }
- type InstitutionInfo = { hostname: string; university: { id: number } }
- let domainCache = new Map<string, InstitutionInfo>()
- export function clearDomainCache() {
- domainCache = new Map<string, InstitutionInfo>()
- }
- type AddEmailInputProps = {
- onChange: (value: string, institution?: InstitutionInfo) => void
- }
- export function AddEmailInput({ onChange }: AddEmailInputProps) {
- const { signal } = useAbortController()
- const [suggestion, setSuggestion] = useState<string>(null)
- const [inputValue, setInputValue] = useState<string>(null)
- const [matchedInstitution, setMatchedInstitution] =
- useState<InstitutionInfo>(null)
- useEffect(() => {
- if (inputValue == null) {
- return
- }
- if (matchedInstitution && suggestion === inputValue) {
- onChange(inputValue, matchedInstitution)
- } else {
- onChange(inputValue)
- }
- }, [onChange, inputValue, suggestion, matchedInstitution])
- const handleEmailChange = useCallback(
- (event: ChangeEvent<HTMLInputElement>) => {
- const hint = event.target.value
- setInputValue(hint)
- const match = matchLocalAndDomain(hint)
- if (!matchedInstitution?.hostname.startsWith(match.domain)) {
- setSuggestion(null)
- }
- if (!match.domain) {
- return
- }
- if (domainCache.has(match.domain)) {
- const cachedDomain = domainCache.get(match.domain)
- setSuggestion(`${match.local}@${cachedDomain.hostname}`)
- setMatchedInstitution(cachedDomain)
- return
- }
- const query = `?hostname=${match.domain}&limit=1`
- getJSON(`/institutions/domains${query}`, { signal })
- .then(data => {
- if (!(data && data[0])) {
- return
- }
- const hostname = data[0]?.hostname
- if (hostname) {
- domainCache.set(match.domain, data[0])
- setSuggestion(`${match.local}@${hostname}`)
- setMatchedInstitution(data[0])
- } else {
- setSuggestion(null)
- setMatchedInstitution(null)
- }
- })
- .catch(error => {
- setSuggestion(null)
- setMatchedInstitution(null)
- console.error(error)
- })
- },
- [signal, matchedInstitution]
- )
- const handleKeyDownEvent = useCallback(
- (event: KeyboardEvent) => {
- if (event.key === 'Tab' || event.key === 'Enter') {
- event.preventDefault()
- if (suggestion) {
- setInputValue(suggestion)
- }
- }
- },
- [suggestion]
- )
- return (
- <div className="input-suggestions">
- <div className="form-control input-suggestions-shadow">
- <div className="input-suggestions-shadow-suggested">
- {suggestion || ''}
- </div>
- </div>
- <input
- id="affiliations-email"
- className="form-control input-suggestions-main"
- type="email"
- onChange={handleEmailChange}
- onKeyDown={handleKeyDownEvent}
- value={inputValue || ''}
- placeholder="e.g. johndoe@mit.edu"
- />
- </div>
- )
- }
|