]> git.localhorst.tv Git - alttp.git/commitdiff
tournament settings
authorDaniel Karbach <daniel.karbach@localhorst.tv>
Wed, 27 Apr 2022 13:36:02 +0000 (15:36 +0200)
committerDaniel Karbach <daniel.karbach@localhorst.tv>
Wed, 27 Apr 2022 13:36:02 +0000 (15:36 +0200)
app/Http/Controllers/TournamentController.php
app/Models/Protocol.php
database/migrations/2022_04_27_115504_tournament_discord_settings.php [new file with mode: 0644]
resources/js/components/tournament/DiscordForm.js [new file with mode: 0644]
resources/js/components/tournament/SettingsDialog.js
resources/js/i18n/de.js
resources/js/i18n/en.js
routes/api.php

index 7f6c913a2ba74b739f526acdb1f86dca96065262..e2fbe941a31b6b2b705b1e35c69724e495351191 100644 (file)
@@ -60,6 +60,22 @@ class TournamentController extends Controller
                return $tournament->toJson();
        }
 
+       public function discordSettings(Request $request, Tournament $tournament) {
+               $this->authorize('update', $tournament);
+               $validatedData = $request->validate([
+                       'round_template' => 'string|nullable',
+               ]);
+               if (array_key_exists('round_template', $validatedData)) {
+                       $tournament->discord_round_template = $validatedData['round_template'];
+               }
+               $tournament->save();
+               if ($tournament->wasChanged()) {
+                       TournamentChanged::dispatch($tournament);
+                       Protocol::tournamentDiscordSettings($tournament, $request->user());
+               }
+               return $tournament->toJson();
+       }
+
        public function open(Request $request, Tournament $tournament) {
                $this->authorize('update', $tournament);
                $tournament->accept_applications = true;
index 67fb1cecbe0d95ada851375ae7a1598c8bb6967a..9aaa5b08d1a8f5b519350ffa0ee2f85fd24543c1 100644 (file)
@@ -168,6 +168,18 @@ class Protocol extends Model
                ProtocolAdded::dispatch($protocol);
        }
 
+       public static function tournamentDiscordSettings(Tournament $tournament, User $user = null) {
+               $protocol = static::create([
+                       'tournament_id' => $tournament->id,
+                       'user_id' => $user ? $user->id : null,
+                       'type' => 'tournament.discordSettings',
+                       'details' => [
+                               'tournament' => static::tournamentMemo($tournament),
+                       ],
+               ]);
+               ProtocolAdded::dispatch($protocol);
+       }
+
        public static function tournamentLocked(Tournament $tournament, User $user = null) {
                $protocol = static::create([
                        'tournament_id' => $tournament->id,
diff --git a/database/migrations/2022_04_27_115504_tournament_discord_settings.php b/database/migrations/2022_04_27_115504_tournament_discord_settings.php
new file mode 100644 (file)
index 0000000..8dab17c
--- /dev/null
@@ -0,0 +1,32 @@
+<?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_template')->default('');
+               });
+       }
+
+       /**
+        * Reverse the migrations.
+        *
+        * @return void
+        */
+       public function down()
+       {
+               Schema::table('tournaments', function(Blueprint $table) {
+                       $table->dropColumn('discord_round_template');
+               });
+       }
+};
diff --git a/resources/js/components/tournament/DiscordForm.js b/resources/js/components/tournament/DiscordForm.js
new file mode 100644 (file)
index 0000000..f9eba93
--- /dev/null
@@ -0,0 +1,83 @@
+import axios from 'axios';
+import { withFormik } from 'formik';
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Button, Col, Form, Row } from 'react-bootstrap';
+import { withTranslation } from 'react-i18next';
+import toastr from 'toastr';
+
+import laravelErrorsToFormik from '../../helpers/laravelErrorsToFormik';
+import i18n from '../../i18n';
+import yup from '../../schema/yup';
+
+const DiscordForm = ({
+       errors,
+       handleBlur,
+       handleChange,
+       handleSubmit,
+       touched,
+       values,
+}) =>
+<Form noValidate onSubmit={handleSubmit}>
+       <fieldset>
+               <legend>{i18n.t('tournaments.discordSettings')}</legend>
+               <Form.Group controlId="tournament.discord_round_template">
+                       <Form.Label>
+                               {i18n.t('tournaments.discordRoundTemplate')}
+                       </Form.Label>
+                       <Form.Control
+                               isInvalid={!!(touched.round_template && errors.round_template)}
+                               name="round_template"
+                               onBlur={handleBlur}
+                               onChange={handleChange}
+                               type="text"
+                               value={values.round_template || ''}
+                       />
+               </Form.Group>
+               <Button className="mt-3" type="submit" variant="primary">
+                       {i18n.t('button.save')}
+               </Button>
+       </fieldset>
+</Form>;
+
+DiscordForm.propTypes = {
+       errors: PropTypes.shape({
+               round_template: PropTypes.string,
+       }),
+       handleBlur: PropTypes.func,
+       handleChange: PropTypes.func,
+       handleSubmit: PropTypes.func,
+       touched: PropTypes.shape({
+               round_template: PropTypes.bool,
+       }),
+       values: PropTypes.shape({
+               round_template: PropTypes.string,
+       }),
+};
+
+export default withFormik({
+       displayName: 'DiscordForm',
+       enableReinitialize: true,
+       handleSubmit: async (values, actions) => {
+               const { round_template } = values;
+               const { setErrors } = actions;
+               const { tournament } = actions.props;
+               try {
+                       await axios.post(`/api/tournaments/${tournament.id}/discord-settings`, {
+                               round_template,
+                       });
+                       toastr.success(i18n.t('tournaments.discordSettingsSuccess'));
+               } catch (e) {
+                       toastr.error(i18n.t('tournaments.discordSettingsError'));
+                       if (e.response && e.response.data && e.response.data.errors) {
+                               setErrors(laravelErrorsToFormik(e.response.data.errors));
+                       }
+               }
+       },
+       mapPropsToValues: ({ tournament }) => ({
+               round_template: tournament.discord_round_template || '',
+       }),
+       validationSchema: yup.object().shape({
+               round_template: yup.string(),
+       }),
+})(withTranslation()(DiscordForm));
index 995c07b8250ee4388a267b26bac67c0b0880ad62..af4d664efe0d2f60c435e169c9a000a395b39bd6 100644 (file)
@@ -1,10 +1,11 @@
 import axios from 'axios';
 import PropTypes from 'prop-types';
 import React from 'react';
-import { Button, Modal } from 'react-bootstrap';
+import { Button, Col, Modal, Row } from 'react-bootstrap';
 import { withTranslation } from 'react-i18next';
 import toastr from 'toastr';
 
+import DiscordForm from './DiscordForm';
 import DiscordSelect from '../common/DiscordSelect';
 import Icon from '../common/Icon';
 import ToggleSwitch from '../common/ToggleSwitch';
@@ -55,52 +56,72 @@ const setDiscord = async (tournament, guild_id) => {
        }
 };
 
+const inviteUrl = 'https://discordapp.com/oauth2/authorize?client_id=951113702839549982&scope=bot';
+
 const SettingsDialog = ({
        onHide,
        show,
        tournament,
 }) =>
-<Modal className="settings-dialog" onHide={onHide} show={show}>
+<Modal
+       className="settings-dialog"
+       onHide={onHide}
+       show={show}
+       size={tournament.discord ? 'lg' : 'md'}
+>
        <Modal.Header closeButton>
                <Modal.Title>
                        {i18n.t('tournaments.settings')}
                </Modal.Title>
        </Modal.Header>
        <Modal.Body>
-               <div className="d-flex align-items-center justify-content-between mb-3">
-                       <span>{i18n.t('tournaments.open')}</span>
-                       <ToggleSwitch
-                               onChange={({ target: { value } }) => value ? open(tournament) : close(tournament)}
-                               value={tournament.accept_applications}
-                       />
-               </div>
-               <div className="d-flex align-items-center justify-content-between mb-3">
-                       <span>{i18n.t('tournaments.locked')}</span>
-                       <ToggleSwitch
-                               onChange={({ target: { value } }) => value ? lock(tournament) : unlock(tournament)}
-                               value={tournament.locked}
-                       />
-               </div>
-               <div className="d-flex align-items-center justify-content-between">
-                       <div>
-                               <p>{i18n.t('tournaments.discord')}</p>
-                               <div>
-                                       <Button
-                                               href="https://discordapp.com/oauth2/authorize?client_id=951113702839549982&scope=bot"
-                                               target="_blank"
-                                               variant="discord"
-                                       >
-                                               <Icon.DISCORD />
-                                               {' '}
-                                               {i18n.t('tournaments.inviteBot')}
-                                       </Button>
+               <Row>
+                       <Col sm={tournament.discord ? 6 : 12}>
+                               <div className="d-flex align-items-center justify-content-between mb-3">
+                                       <span>{i18n.t('tournaments.open')}</span>
+                                       <ToggleSwitch
+                                               onChange={({ target: { value } }) => value
+                                                       ? open(tournament) : close(tournament)}
+                                               value={tournament.accept_applications}
+                                       />
+                               </div>
+                               <div className="d-flex align-items-center justify-content-between mb-3">
+                                       <span>{i18n.t('tournaments.locked')}</span>
+                                       <ToggleSwitch
+                                               onChange={({ target: { value } }) => value
+                                                       ? lock(tournament) : unlock(tournament)}
+                                               value={tournament.locked}
+                                       />
+                               </div>
+                               <div className="d-flex align-items-center justify-content-between">
+                                       <div>
+                                               <p>{i18n.t('tournaments.discord')}</p>
+                                               {!tournament.discord ?
+                                                       <div>
+                                                               <Button
+                                                                       href={inviteUrl}
+                                                                       target="_blank"
+                                                                       variant="discord"
+                                                               >
+                                                                       <Icon.DISCORD />
+                                                                       {' '}
+                                                                       {i18n.t('tournaments.inviteBot')}
+                                                               </Button>
+                                                       </div>
+                                               : null}
+                                       </div>
+                                       <DiscordSelect
+                                               onChange={({ target: { value } }) => setDiscord(tournament, value)}
+                                               value={tournament.discord}
+                                       />
                                </div>
-                       </div>
-                       <DiscordSelect
-                               onChange={({ target: { value } }) => setDiscord(tournament, value)}
-                               value={tournament.discord}
-                       />
-               </div>
+                       </Col>
+                       {tournament.discord ?
+                               <Col sm={6}>
+                                       <DiscordForm tournament={tournament} />
+                               </Col>
+                       : null}
+               </Row>
        </Modal.Body>
        <Modal.Footer>
                <Button onClick={onHide} variant="secondary">
index fc15a690af8f24a0068b5ad9b0d844661aa78892..5a4889ca2d301ace748c2eb1026136c303e08fea 100644 (file)
@@ -221,6 +221,10 @@ export default {
                        closeSuccess: 'Anmeldung geschlossen',
                        discord: 'Discord',
                        discordError: 'Fehler beim Zuordnen',
+                       discordRoundTemplate: 'Template für Runden-Kanäle',
+                       discordSettings: 'Discord Einstellungen',
+                       discordSettingsError: 'Fehler beim Speichern der Discord Einstellungen',
+                       discordSettingsSuccess: 'Discord Einstellungen gespeichert',
                        discordSuccess: 'Discord verknüpft',
                        inviteBot: 'Bot einladen',
                        locked: 'Turnier sperren',
index 0acab1a739233b89d55c4ff64c09d7b71b3a8c1f..9d4b6210f5b45d1ffe0ba411b01aa09ace75a4ea 100644 (file)
@@ -221,6 +221,10 @@ export default {
                        closeSuccess: 'Registration closed',
                        discord: 'Discord',
                        discordError: 'Error connecting',
+                       discordRoundTemplate: 'Template for round channels',
+                       discordSettings: 'Discord settings',
+                       discordSettingsError: 'Error saving discord settings',
+                       discordSettingsSuccess: 'Discord settings saved',
                        discordSuccess: 'Discord associated',
                        inviteBot: 'Invite bot',
                        locked: 'Lock rounds',
index 502373393d4bcf24167ab1beb0ac7de1f17686c4..fc7fd9ca7aea32833a478b68b52b745edd8fc907 100644 (file)
@@ -37,6 +37,7 @@ Route::get('tournaments/{id}', 'App\Http\Controllers\TournamentController@single
 Route::post('tournaments/{tournament}/apply', 'App\Http\Controllers\TournamentController@apply');
 Route::post('tournaments/{tournament}/close', 'App\Http\Controllers\TournamentController@close');
 Route::post('tournaments/{tournament}/discord', 'App\Http\Controllers\TournamentController@discord');
+Route::post('tournaments/{tournament}/discord-settings', 'App\Http\Controllers\TournamentController@discordSettings');
 Route::post('tournaments/{tournament}/lock', 'App\Http\Controllers\TournamentController@lock');
 Route::post('tournaments/{tournament}/open', 'App\Http\Controllers\TournamentController@open');
 Route::post('tournaments/{tournament}/unlock', 'App\Http\Controllers\TournamentController@unlock');