1 import axios from 'axios';
2 import PropTypes from 'prop-types';
3 import React, { useCallback, useEffect, useState } from 'react';
4 import { Alert, Button, Form, ListGroup } from 'react-bootstrap';
5 import { useTranslation } from 'react-i18next';
7 import Icon from './Icon';
8 import ChannelBox from '../discord-guilds/ChannelBox';
9 import debounce from '../../helpers/debounce';
11 const DiscordChannelSelect = ({
18 const [resolved, setResolved] = useState(null);
19 const [results, setResults] = useState([]);
20 const [search, setSearch] = useState('');
21 const [showResults, setShowResults] = useState(false);
23 const ref = React.useRef(null);
24 const { t } = useTranslation();
27 const handleEventOutside = e => {
28 if (ref.current && !ref.current.contains(e.target)) {
29 setShowResults(false);
32 document.addEventListener('mousedown', handleEventOutside, true);
33 document.addEventListener('focus', handleEventOutside, true);
35 document.removeEventListener('mousedown', handleEventOutside, true);
36 document.removeEventListener('focus', handleEventOutside, true);
41 const fetch = useCallback(debounce(async (guild, phrase, types) => {
45 ctrl = new AbortController();
47 const response = await axios.get(`/api/discord-guilds/${guild}/channels`, {
55 setResults(response.data);
61 if (ctrl) ctrl.abort();
66 fetch(guild, search, types);
67 }, [guild, search, ...types]);
72 .get(`/api/discord-channels/${value}`)
74 setResolved(response.data);
82 return <div className="d-flex align-items-center justify-content-between">
83 <span>{resolved ? <ChannelBox channel={resolved} /> : value}</span>
86 onClick={() => onChange({ guild: null, target: { name, value: '' }})}
87 title={t('button.unset')}
88 variant="outline-danger"
90 <Icon.REMOVE title="" />
94 return <div className={`discord-select ${showResults ? 'expanded' : 'collapsed'}`} ref={ref}>
96 className="search-input"
97 name={Math.random().toString(20).substr(2, 10)}
98 onChange={e => setSearch(e.target.value)}
99 onFocus={() => setShowResults(true)}
103 <div className="search-results-holder">
105 <ListGroup className="search-results">
106 {results.map(result =>
110 onClick={() => onChange({
112 target: { name, value: result.channel_id },
115 <ChannelBox channel={result} />
120 <Alert className="search-results" variant="info">
121 {t('search.noResults')}
128 DiscordChannelSelect.propTypes = {
129 guild: PropTypes.string,
130 isInvalid: PropTypes.bool,
131 name: PropTypes.string,
132 onBlur: PropTypes.func,
133 onChange: PropTypes.func,
134 types: PropTypes.arrayOf(PropTypes.number),
135 value: PropTypes.string,
138 export default DiscordChannelSelect;