From: Daniel Karbach Date: Tue, 25 Oct 2022 14:13:26 +0000 (+0200) Subject: temporary user select X-Git-Url: https://git.localhorst.tv/?a=commitdiff_plain;ds=sidebyside;h=c5b15dc5518c627daa4f02f653ff900e4fe13db7;p=alttp.git temporary user select still need to fix the FK datatypes --- diff --git a/app/Http/Controllers/RoundController.php b/app/Http/Controllers/RoundController.php index 337e111..6d31e90 100644 --- a/app/Http/Controllers/RoundController.php +++ b/app/Http/Controllers/RoundController.php @@ -45,11 +45,13 @@ class RoundController extends Controller $validatedData = $request->validate([ 'code' => 'array', 'code.*' => 'string', + 'rolled_by' => 'nullable|exists:App\\Models\\User,id', 'seed' => 'url', 'title' => 'string', ]); $round->code = array_filter($validatedData['code']); + $round->rolled_by = $validatedData['rolled_by']; $round->seed = $validatedData['seed']; $round->title = $validatedData['title']; $round->update(); diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index da3ebde..4bf2bb3 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -9,6 +9,20 @@ use Illuminate\Http\Request; class UserController extends Controller { + public function search(Request $request) { + $validatedData = $request->validate([ + 'phrase' => 'string|nullable', + ]); + + $users = User::query(); + if (!empty($validatedData['phrase'])) { + $users = $users->where('username', 'LIKE', '%'.$validatedData['phrase'].'%') + ->orWhere('nickname', 'LIKE', '%'.$validatedData['phrase'].'%'); + } + $users = $users->limit(5); + return $users->get()->toJson(); + } + public function setLanguage(Request $request) { $user = $request->user(); if (!$user) return; diff --git a/resources/js/components/common/Icon.js b/resources/js/components/common/Icon.js index f3e63e5..617ca03 100644 --- a/resources/js/components/common/Icon.js +++ b/resources/js/components/common/Icon.js @@ -73,6 +73,7 @@ Icon.LOGOUT = makePreset('LogoutIcon', 'sign-out-alt'); Icon.PENDING = makePreset('PendingIcon', 'clock'); Icon.PROTOCOL = makePreset('ProtocolIcon', 'file-alt'); Icon.REJECT = makePreset('RejectIcon', 'square-xmark'); +Icon.REMOVE = makePreset('RemoveIcon', 'square-xmark'); Icon.RESULT = makePreset('ResultIcon', 'clock'); Icon.SECOND_PLACE = makePreset('SecondPlaceIcon', 'medal'); Icon.SETTINGS = makePreset('SettingsIcon', 'cog'); diff --git a/resources/js/components/common/UserSelect.js b/resources/js/components/common/UserSelect.js new file mode 100644 index 0000000..be473b6 --- /dev/null +++ b/resources/js/components/common/UserSelect.js @@ -0,0 +1,118 @@ +import axios from 'axios'; +import PropTypes from 'prop-types'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import { Button, Form, ListGroup } from 'react-bootstrap'; + +import Icon from '../common/Icon'; +import UserBox from '../users/Box'; +import debounce from '../../helpers/debounce'; + +const UserSelect = ({ name, onChange, value }) => { + const [resolved, setResolved] = useState(null); + const [results, setResults] = useState([]); + const [search, setSearch] = useState(''); + const [showResults, setShowResults] = useState(false); + + const ref = useRef(null); + + 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 phrase => { + if (ctrl) { + ctrl.abort(); + } + ctrl = new AbortController(); + if (!phrase || phrase.length < 3) { + setResults([]); + return; + } + try { + const response = await axios.get(`/api/users`, { + params: { + phrase, + }, + signal: ctrl.signal, + }); + ctrl = null; + setResults(response.data); + } catch (e) { + ctrl = null; + console.error(e); + } + }, 300), []); + + useEffect(() => { + fetch(search); + }, [search]); + + useEffect(() => { + if (value) { + axios + .get(`/api/users/${value}`) + .then(response => { + setResolved(response.data); + }); + } else { + setResolved(null); + } + }, [value]); + + if (value) { + return
+ {resolved ? : value} + +
; + } + return
+ setSearch(e.target.value)} + onFocus={() => setShowResults(true)} + type="search" + value={search} + /> +
+ + {results.map(result => + onChange({ + target: { name, value: result.id }, + })} + > + + + )} + +
+
; +}; + +UserSelect.propTypes = { + name: PropTypes.string, + onChange: PropTypes.func, + value: PropTypes.string, +}; + +export default UserSelect; diff --git a/resources/js/components/rounds/EditForm.js b/resources/js/components/rounds/EditForm.js index 3d0d0b3..6472c84 100644 --- a/resources/js/components/rounds/EditForm.js +++ b/resources/js/components/rounds/EditForm.js @@ -7,6 +7,7 @@ import { withTranslation } from 'react-i18next'; import toastr from 'toastr'; import SeedCodeInput from './SeedCodeInput'; +import UserSelect from '../common/UserSelect'; import laravelErrorsToFormik from '../../helpers/laravelErrorsToFormik'; import i18n from '../../i18n'; import yup from '../../schema/yup'; @@ -77,6 +78,24 @@ const EditForm = ({ : null} + + + {i18n.t('rounds.rolled_by')} + + {touched.rolled_by && errors.rolled_by ? + + {i18n.t(errors.rolled_by)} + + : null} + + {onCancel ? @@ -93,6 +112,7 @@ const EditForm = ({ EditForm.propTypes = { errors: PropTypes.shape({ code: PropTypes.arrayOf(PropTypes.string), + rolled_by: PropTypes.string, seed: PropTypes.string, title: PropTypes.string, }), @@ -102,12 +122,14 @@ EditForm.propTypes = { onCancel: PropTypes.func, touched: PropTypes.shape({ code: PropTypes.arrayOf(PropTypes.bool), + rolled_by: PropTypes.bool, seed: PropTypes.bool, title: PropTypes.bool, }), values: PropTypes.shape({ code: PropTypes.arrayOf(PropTypes.string), game: PropTypes.string, + rolled_by: PropTypes.string, seed: PropTypes.string, title: PropTypes.string, }), @@ -136,12 +158,14 @@ export default withFormik({ mapPropsToValues: ({ round }) => ({ code: round.code || [], game: round.game || 'mixed', + rolled_by: round.rolled_by || null, round_id: round.id, seed: round.seed || '', title: round.title || '', }), validationSchema: yup.object().shape({ code: yup.array().of(yup.string()), + rolled_by: yup.string(), seed: yup.string().url(), title: yup.string(), }), diff --git a/resources/js/components/rounds/SeedCodeInput.js b/resources/js/components/rounds/SeedCodeInput.js index 95ef788..4dc8abe 100644 --- a/resources/js/components/rounds/SeedCodeInput.js +++ b/resources/js/components/rounds/SeedCodeInput.js @@ -7,7 +7,6 @@ import i18n from '../../i18n'; const ALTTPR_CODES = [ 'big-key', - 'bow', 'blue-boomerang', 'bomb', 'bombos', diff --git a/resources/js/components/users/Box.js b/resources/js/components/users/Box.js index bc4673e..70006cb 100644 --- a/resources/js/components/users/Box.js +++ b/resources/js/components/users/Box.js @@ -7,18 +7,14 @@ import { useNavigate } from 'react-router-dom'; import { getAvatarUrl } from '../../helpers/User'; import i18n from '../../i18n'; -const Box = ({ discriminator, user }) => { +const Box = ({ discriminator, noLink, user }) => { const navigate = useNavigate(); if (!user) { return {i18n.t('general.anonymous')}; } - return ; }; Box.propTypes = { discriminator: PropTypes.bool, + noLink: PropTypes.bool, user: PropTypes.shape({ discriminator: PropTypes.string, id: PropTypes.string, diff --git a/routes/api.php b/routes/api.php index d9f44ad..3d7062a 100644 --- a/routes/api.php +++ b/routes/api.php @@ -54,6 +54,7 @@ Route::post('tournaments/{tournament}/lock', 'App\Http\Controllers\TournamentCon Route::post('tournaments/{tournament}/open', 'App\Http\Controllers\TournamentController@open'); Route::post('tournaments/{tournament}/unlock', 'App\Http\Controllers\TournamentController@unlock'); +Route::get('users', 'App\Http\Controllers\UserController@search'); Route::get('users/{id}', 'App\Http\Controllers\UserController@single'); Route::post('users/set-language', 'App\Http\Controllers\UserController@setLanguage'); Route::post('users/{user}/setNickname', 'App\Http\Controllers\UserController@setNickname');