import axios from 'axios';
import PropTypes from 'prop-types';
import React, { useCallback, useEffect, useState } from 'react';
-import { Form } from 'react-bootstrap';
-import { withTranslation } from 'react-i18next';
+import { Alert, Button, Form, ListGroup } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+import Icon from './Icon';
+import ChannelBox from '../discord-guilds/ChannelBox';
import debounce from '../../helpers/debounce';
-import i18n from '../../i18n';
const DiscordChannelSelect = ({
guild,
types,
value,
}) => {
+ const [resolved, setResolved] = useState(null);
const [results, setResults] = useState([]);
+ const [search, setSearch] = useState('');
+ const [showResults, setShowResults] = useState(false);
+
+ const ref = React.useRef(null);
+ const { t } = useTranslation();
+
+ useEffect(() => {
+ const handleEventOutside = e => {
+ if (ref.current && !ref.current.contains(e.target)) {
+ setShowResults(false);
+ }
+ };
+ document.addEventListener('click', handleEventOutside, true);
+ document.addEventListener('focus', handleEventOutside, true);
+ return () => {
+ document.removeEventListener('click', handleEventOutside, true);
+ document.removeEventListener('focus', handleEventOutside, true);
+ };
+ }, []);
let ctrl = null;
- const fetch = useCallback(debounce(async (guild, types) => {
+ const fetch = useCallback(debounce(async (guild, phrase, types) => {
if (ctrl) {
ctrl.abort();
}
try {
const response = await axios.get(`/api/discord-guilds/${guild}/channels`, {
params: {
+ phrase,
types,
},
signal: ctrl.signal,
}, 300), []);
useEffect(() => {
- fetch(guild, types);
- }, [guild, ...types]);
+ fetch(guild, search, types);
+ }, [guild, search, ...types]);
+
+ useEffect(() => {
+ if (value) {
+ axios
+ .get(`/api/discord-channels/${value}`)
+ .then(response => {
+ setResolved(response.data);
+ });
+ } else {
+ setResolved(null);
+ }
+ }, [value]);
- return <Form.Select
- isInvalid={isInvalid}
- name={name}
- onBlur={onBlur}
- onChange={onChange}
- type="search"
- value={value}
- >
- <option value="">{i18n.t('tournaments.discordNoCategory')}</option>
- {results && results.length ? results.map(result =>
- <option key={result.id} value={result.channel_id}>{result.name}</option>
- ) : null}
- </Form.Select>;
+ if (value) {
+ return <div className="d-flex align-items-center justify-content-between">
+ <span>{resolved ? <ChannelBox channel={resolved} /> : value}</span>
+ <Button
+ className="ms-2"
+ onClick={() => onChange({ guild: null, target: { name, value: '' }})}
+ title={t('button.unset')}
+ variant="outline-danger"
+ >
+ <Icon.REMOVE title="" />
+ </Button>
+ </div>;
+ }
+ return <div className={`discord-select ${showResults ? 'expanded' : 'collapsed'}`} ref={ref}>
+ <Form.Control
+ className="search-input"
+ name={Math.random().toString(20).substr(2, 10)}
+ onChange={e => setSearch(e.target.value)}
+ onFocus={() => setShowResults(true)}
+ type="search"
+ value={search}
+ />
+ <div className="search-results-holder">
+ {results.length ?
+ <ListGroup className="search-results">
+ {results.map(result =>
+ <ListGroup.Item
+ action
+ key={result.id}
+ onClick={() => onChange({
+ channel: result,
+ target: { name, value: result.channel_id },
+ })}
+ >
+ <ChannelBox channel={result} />
+ </ListGroup.Item>
+ )}
+ </ListGroup>
+ :
+ <Alert className="search-results" variant="info">
+ {t('search.noResults')}
+ </Alert>
+ }
+ </div>
+ </div>;
};
DiscordChannelSelect.propTypes = {
value: PropTypes.string,
};
-export default withTranslation()(DiscordChannelSelect);
+export default DiscordChannelSelect;