--- /dev/null
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Models\DiscordChannel;
+use App\Models\DiscordGuild;
+use Illuminate\Http\Request;
+
+class DiscordChannelController extends Controller
+{
+
+ public function search(Request $request, $guild_id) {
+ $guild = DiscordGuild::where('guild_id', '=', $guild_id)->firstOrFail();
+ $this->authorize('view', $guild);
+
+ $validatedData = $request->validate([
+ 'parents' => 'array|nullable',
+ 'parents.*' => 'string',
+ 'phrase' => 'string|nullable',
+ 'types' => 'array|nullable',
+ 'types.*' => 'integer',
+ ]);
+
+ $channels = $guild->channels();
+ if (!empty($validatedData['parents'])) {
+ $channels = $channels->whereIn('parent', $validatedData['parents']);
+ }
+ if (!empty($validatedData['phrase'])) {
+ $channels = $channels->where('name', 'LIKE', '%'.$validatedData['phrase'].'%');
+ }
+ if (!empty($validatedData['types'])) {
+ $channels = $channels->whereIn('type', $validatedData['types']);
+ }
+ return $channels->get()->toJson();
+ }
+
+}
public function discordSettings(Request $request, Tournament $tournament) {
$this->authorize('update', $tournament);
$validatedData = $request->validate([
+ 'round_category' => 'string|nullable',
'round_template' => 'string|nullable',
]);
+ if (array_key_exists('round_category', $validatedData)) {
+ $tournament->discord_round_category = $validatedData['round_category'];
+ }
if (array_key_exists('round_template', $validatedData)) {
$tournament->discord_round_template = $validatedData['round_template'];
}
}
public function channels() {
- return $this->hasMany(DiscordChannel::class);
+ return $this->hasMany(DiscordChannel::class)->orderBy('position');
}
public function roles() {
- return $this->hasMany(DiscordRole::class);
+ return $this->hasMany(DiscordRole::class)->orderBy('position');
}
protected $fillable = [
--- /dev/null
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+ /**
+ * Run the migrations.
+ *
+ * @return void
+ */
+ public function up()
+ {
+ Schema::table('tournaments', function(Blueprint $table) {
+ $table->string('discord_round_category')->default('');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::table('tournaments', function(Blueprint $table) {
+ $table->dropColumn('discord_round_category');
+ });
+ }
+};
--- /dev/null
+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 debounce from '../../helpers/debounce';
+import i18n from '../../i18n';
+
+const DiscordChannelSelect = ({
+ guild,
+ isInvalid,
+ name,
+ onBlur,
+ onChange,
+ types,
+ value,
+}) => {
+ const [results, setResults] = useState([]);
+
+ let ctrl = null;
+ const fetch = useCallback(debounce(async (guild, types) => {
+ if (ctrl) {
+ ctrl.abort();
+ }
+ ctrl = new AbortController();
+ try {
+ const response = await axios.get(`/api/discord-guilds/${guild}/channels`, {
+ params: {
+ types,
+ },
+ signal: ctrl.signal,
+ });
+ ctrl = null;
+ setResults(response.data);
+ } catch (e) {
+ ctrl = null;
+ console.error(e);
+ }
+ return () => {
+ if (ctrl) ctrl.abort();
+ };
+ }, 300), []);
+
+ useEffect(() => {
+ fetch(guild, types);
+ }, [guild, ...types]);
+
+ 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>;
+};
+
+DiscordChannelSelect.propTypes = {
+ guild: PropTypes.string,
+ isInvalid: PropTypes.bool,
+ name: PropTypes.string,
+ onBlur: PropTypes.func,
+ onChange: PropTypes.func,
+ types: PropTypes.arrayOf(PropTypes.number),
+ value: PropTypes.string,
+};
+
+export default withTranslation()(DiscordChannelSelect);
import { withFormik } from 'formik';
import PropTypes from 'prop-types';
import React from 'react';
-import { Button, Col, Form, Row } from 'react-bootstrap';
+import { Button, Form } from 'react-bootstrap';
import { withTranslation } from 'react-i18next';
import toastr from 'toastr';
+import DiscordChannelSelect from '../common/DiscordChannelSelect';
import laravelErrorsToFormik from '../../helpers/laravelErrorsToFormik';
import i18n from '../../i18n';
import yup from '../../schema/yup';
handleChange,
handleSubmit,
touched,
+ tournament,
values,
}) =>
<Form noValidate onSubmit={handleSubmit}>
<fieldset>
<legend>{i18n.t('tournaments.discordSettings')}</legend>
+ <Form.Group controlId="tournament.discord_round_category">
+ <Form.Label>
+ {i18n.t('tournaments.discordRoundCategory')}
+ </Form.Label>
+ <DiscordChannelSelect
+ guild={tournament.discord}
+ isInvalid={!!(touched.round_category && errors.round_category)}
+ name="round_category"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ types={[4]}
+ value={values.round_category || ''}
+ />
+ </Form.Group>
<Form.Group controlId="tournament.discord_round_template">
<Form.Label>
{i18n.t('tournaments.discordRoundTemplate')}
DiscordForm.propTypes = {
errors: PropTypes.shape({
+ round_category: PropTypes.string,
round_template: PropTypes.string,
}),
handleBlur: PropTypes.func,
handleChange: PropTypes.func,
handleSubmit: PropTypes.func,
touched: PropTypes.shape({
+ round_category: PropTypes.bool,
round_template: PropTypes.bool,
}),
+ tournament: PropTypes.shape({
+ discord: PropTypes.string,
+ }),
values: PropTypes.shape({
+ round_category: PropTypes.string,
round_template: PropTypes.string,
}),
};
displayName: 'DiscordForm',
enableReinitialize: true,
handleSubmit: async (values, actions) => {
- const { round_template } = values;
+ const { round_category, round_template } = values;
const { setErrors } = actions;
const { tournament } = actions.props;
try {
await axios.post(`/api/tournaments/${tournament.id}/discord-settings`, {
+ round_category,
round_template,
});
toastr.success(i18n.t('tournaments.discordSettingsSuccess'));
}
},
mapPropsToValues: ({ tournament }) => ({
+ round_category: tournament.discord_round_category || '',
round_template: tournament.discord_round_template || '',
}),
validationSchema: yup.object().shape({
+ round_category: yup.string(),
round_template: yup.string(),
}),
})(withTranslation()(DiscordForm));
closeSuccess: 'Anmeldung geschlossen',
discord: 'Discord',
discordError: 'Fehler beim Zuordnen',
+ discordNoCategory: 'Keine Kategorie',
+ discordRoundCategory: 'Kategorie für Runden-Kanäle',
discordRoundTemplate: 'Template für Runden-Kanäle',
discordSettings: 'Discord Einstellungen',
discordSettingsError: 'Fehler beim Speichern der Discord Einstellungen',
closeSuccess: 'Registration closed',
discord: 'Discord',
discordError: 'Error connecting',
+ discordNoCategory: 'No category',
+ discordRoundCategory: 'Category for round channels',
discordRoundTemplate: 'Template for round channels',
discordSettings: 'Discord settings',
discordSettingsError: 'Error saving discord settings',
Route::get('discord-guilds', 'App\Http\Controllers\DiscordGuildController@search');
Route::get('discord-guilds/{guild_id}', 'App\Http\Controllers\DiscordGuildController@single');
+Route::get('discord-guilds/{guild_id}/channels', 'App\Http\Controllers\DiscordChannelController@search');
Route::get('protocol/{tournament}', 'App\Http\Controllers\ProtocolController@forTournament');