return $user->toJson();
}
+ public function setNickname(Request $request, User $user) {
+ $this->authorize('setNickname', $user);
+
+ $validatedData = $request->validate([
+ 'nickname' => 'string',
+ ]);
+
+ $user->nickname = $validatedData['nickname'];
+ $user->update();
+
+ UserChanged::dispatch($user);
+
+ return $user->toJson();
+ }
+
public function setStreamLink(Request $request, User $user) {
$this->authorize('setStreamLink', $user);
return false;
}
+ /**
+ * Determine whether the user change the stream link of the model.
+ *
+ * @param \App\Models\User $user
+ * @param \App\Models\User $model
+ * @return \Illuminate\Auth\Access\Response|bool
+ */
+ public function setNickname(User $user, User $model)
+ {
+ return $user->role == 'admin' || $user->id == $model->id;
+ }
+
/**
* Determine whether the user change the stream link of the model.
*
--- /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('users', function(Blueprint $table) {
+ $table->string('nickname')->nullable()->default(null);
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::table('users', function(Blueprint $table) {
+ $table->dropColumn('nickname');
+ });
+ }
+};
variant="link"
>
<img alt="" src={getAvatarUrl(user)} />
- <span>{user.username}</span>
+ <span>{discriminator || !user.nickname ? user.username : user.nickname}</span>
{discriminator ?
<span className="text-muted">
{'#'}
user: PropTypes.shape({
discriminator: PropTypes.string,
id: PropTypes.string,
+ nickname: PropTypes.string,
username: PropTypes.string,
}),
};
--- /dev/null
+import PropTypes from 'prop-types';
+import React, { useState } from 'react';
+import { Button } from 'react-bootstrap';
+import { withTranslation } from 'react-i18next';
+
+import EditNicknameDialog from './EditNicknameDialog';
+import Icon from '../common/Icon';
+import { mayEditNickname } from '../../helpers/permissions';
+import { withUser } from '../../helpers/UserContext';
+import i18n from '../../i18n';
+
+const EditNicknameButton = ({ authUser, user }) => {
+ const [showDialog, setShowDialog] = useState(false);
+
+ if (mayEditNickname(authUser, user)) {
+ return <>
+ <EditNicknameDialog
+ onHide={() => setShowDialog(false)}
+ show={showDialog}
+ user={user}
+ />
+ <Button
+ onClick={() => setShowDialog(true)}
+ title={i18n.t('button.edit')}
+ variant="outline-secondary"
+ >
+ <Icon.EDIT title="" />
+ </Button>
+ </>;
+ }
+ return null;
+};
+
+EditNicknameButton.propTypes = {
+ authUser: PropTypes.shape({
+ }),
+ user: PropTypes.shape({
+ }),
+};
+
+export default withTranslation()(withUser(EditNicknameButton, 'authUser'));
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Modal } from 'react-bootstrap';
+import { withTranslation } from 'react-i18next';
+
+import EditNicknameForm from './EditNicknameForm';
+import i18n from '../../i18n';
+
+const EditNicknameDialog = ({
+ onHide,
+ show,
+ user,
+}) =>
+<Modal className="edit-stream-link-dialog" onHide={onHide} show={show}>
+ <Modal.Header closeButton>
+ <Modal.Title>
+ {i18n.t('users.editNickname')}
+ </Modal.Title>
+ </Modal.Header>
+ <EditNicknameForm
+ onCancel={onHide}
+ user={user}
+ />
+</Modal>;
+
+EditNicknameDialog.propTypes = {
+ onHide: PropTypes.func,
+ show: PropTypes.bool,
+ user: PropTypes.shape({
+ }),
+};
+
+export default withTranslation()(EditNicknameDialog);
--- /dev/null
+import axios from 'axios';
+import { withFormik } from 'formik';
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Button, Col, Form, Modal, 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 EditStreamLinkForm = ({
+ errors,
+ handleBlur,
+ handleChange,
+ handleSubmit,
+ onCancel,
+ touched,
+ user,
+ values,
+}) =>
+<Form noValidate onSubmit={handleSubmit}>
+ <Modal.Body>
+ <Row>
+ <Form.Group as={Col} controlId="user.nickname">
+ <Form.Label>{i18n.t('users.nickname')}</Form.Label>
+ <Form.Control
+ isInvalid={!!(touched.nickname && errors.nickname)}
+ name="nickname"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ placeholder={user.username}
+ type="text"
+ value={values.nickname || ''}
+ />
+ {touched.nickname && errors.nickname ?
+ <Form.Control.Feedback type="invalid">
+ {i18n.t(errors.nickname)}
+ </Form.Control.Feedback>
+ : null}
+ </Form.Group>
+ </Row>
+ </Modal.Body>
+ <Modal.Footer>
+ {onCancel ?
+ <Button onClick={onCancel} variant="secondary">
+ {i18n.t('button.cancel')}
+ </Button>
+ : null}
+ <Button type="submit" variant="primary">
+ {i18n.t('button.save')}
+ </Button>
+ </Modal.Footer>
+</Form>;
+
+EditStreamLinkForm.propTypes = {
+ errors: PropTypes.shape({
+ nickname: PropTypes.string,
+ }),
+ handleBlur: PropTypes.func,
+ handleChange: PropTypes.func,
+ handleSubmit: PropTypes.func,
+ onCancel: PropTypes.func,
+ touched: PropTypes.shape({
+ nickname: PropTypes.bool,
+ }),
+ user: PropTypes.shape({
+ username: PropTypes.bool,
+ }),
+ values: PropTypes.shape({
+ nickname: PropTypes.string,
+ }),
+};
+
+export default withFormik({
+ displayName: 'SeedForm',
+ enableReinitialize: true,
+ handleSubmit: async (values, actions) => {
+ const { user_id, nickname } = values;
+ const { setErrors } = actions;
+ const { onCancel } = actions.props;
+ try {
+ await axios.post(`/api/users/${user_id}/setNickname`, {
+ nickname,
+ });
+ toastr.success(i18n.t('users.setNicknameSuccess'));
+ if (onCancel) {
+ onCancel();
+ }
+ } catch (e) {
+ toastr.error(i18n.t('users.setNicknameError'));
+ if (e.response && e.response.data && e.response.data.errors) {
+ setErrors(laravelErrorsToFormik(e.response.data.errors));
+ }
+ }
+ },
+ mapPropsToValues: ({ user }) => ({
+ user_id: user.id,
+ nickname: user.nickname || '',
+ }),
+ validationSchema: yup.object().shape({
+ nickname: yup.string(),
+ }),
+})(withTranslation()(EditStreamLinkForm));
import Box from './Box';
import Records from './Records';
+import EditNicknameButton from './EditNicknameButton';
import EditStreamLinkButton from './EditStreamLinkButton';
import Participation from './Participation';
import Icon from '../common/Icon';
import i18n from '../../i18n';
const Profile = ({ user }) => <Container>
- <h1>{user.username}</h1>
+ <h1>
+ {user.nickname || user.username}
+ {' '}
+ <EditNicknameButton user={user} />
+ </h1>
<Row>
<Col md={6} className="mb-5">
<h2>{i18n.t('users.discordTag')}</h2>
Profile.propTypes = {
user: PropTypes.shape({
+ nickname: PropTypes.string,
participation: PropTypes.arrayOf(PropTypes.shape({
})),
round_first_count: PropTypes.number,
// Users
+export const mayEditNickname = (user, subject) =>
+ isAdmin(user) || isSameUser(user, subject);
+
export const mayEditStreamLink = (user, subject) =>
isAdmin(user) || isSameUser(user, subject);
},
users: {
discordTag: 'Discord Tag',
+ editNickname: 'Name bearbeiten',
editStreamLink: 'Stream Link bearbeiten',
+ nickname: 'Name',
noStream: 'Kein Stream gesetzt',
participationEmpty: 'Hat noch an keinen Turnieren teilgenommen.',
roundRecords: 'Renn-Platzierungen',
+ setNicknameError: 'Konnte Namen nicht speichern',
+ setNicknameSuccess: 'Name geƤndert',
setStreamLinkError: 'Konnte Stream Link nicht speichern',
setStreamLinkSuccess: 'Stream Link gespeichert',
stream: 'Stream',
},
users: {
discordTag: 'Discord tag',
+ editNickname: 'Edit name',
editStreamLink: 'Edit stream link',
+ nickname: 'Name',
noStream: 'No stream set',
participationEmpty: 'Has not participated in any tourneys yet.',
roundRecords: 'Race records',
+ setNicknameError: 'Could not save name',
+ setNicknameSuccess: 'Name changed',
setStreamLinkError: 'Could not save stream link',
setStreamLinkSuccess: 'Stream link saved',
stream: 'Stream',
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');
Route::post('users/{user}/setStreamLink', 'App\Http\Controllers\UserController@setStreamLink');