mirror of
https://github.com/plankanban/planka.git
synced 2025-07-23 15:19:44 +02:00
Add phone and organization fields to user
This commit is contained in:
parent
c6ecf126d0
commit
9b4e3931a9
11 changed files with 102 additions and 13 deletions
|
@ -18,6 +18,8 @@ const AccountPane = React.memo(
|
||||||
name,
|
name,
|
||||||
username,
|
username,
|
||||||
avatar,
|
avatar,
|
||||||
|
phone,
|
||||||
|
organization,
|
||||||
isAvatarUploading,
|
isAvatarUploading,
|
||||||
usernameUpdateForm,
|
usernameUpdateForm,
|
||||||
emailUpdateForm,
|
emailUpdateForm,
|
||||||
|
@ -53,6 +55,8 @@ const AccountPane = React.memo(
|
||||||
<EditInformation
|
<EditInformation
|
||||||
defaultData={{
|
defaultData={{
|
||||||
name,
|
name,
|
||||||
|
phone,
|
||||||
|
organization,
|
||||||
}}
|
}}
|
||||||
onUpdate={onUpdate}
|
onUpdate={onUpdate}
|
||||||
/>
|
/>
|
||||||
|
@ -120,6 +124,8 @@ AccountPane.propTypes = {
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
username: PropTypes.string,
|
username: PropTypes.string,
|
||||||
avatar: PropTypes.string,
|
avatar: PropTypes.string,
|
||||||
|
phone: PropTypes.string,
|
||||||
|
organization: PropTypes.string,
|
||||||
isAvatarUploading: PropTypes.bool.isRequired,
|
isAvatarUploading: PropTypes.bool.isRequired,
|
||||||
/* eslint-disable react/forbid-prop-types */
|
/* eslint-disable react/forbid-prop-types */
|
||||||
usernameUpdateForm: PropTypes.object.isRequired,
|
usernameUpdateForm: PropTypes.object.isRequired,
|
||||||
|
@ -139,6 +145,8 @@ AccountPane.propTypes = {
|
||||||
AccountPane.defaultProps = {
|
AccountPane.defaultProps = {
|
||||||
username: undefined,
|
username: undefined,
|
||||||
avatar: undefined,
|
avatar: undefined,
|
||||||
|
phone: undefined,
|
||||||
|
organization: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AccountPane;
|
export default AccountPane;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import dequal from 'dequal';
|
import dequal from 'dequal';
|
||||||
import React, { useCallback, useRef } from 'react';
|
import pickBy from 'lodash/pickBy';
|
||||||
|
import React, { useCallback, useMemo, useRef } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Button, Form, Input } from 'semantic-ui-react';
|
import { Button, Form, Input } from 'semantic-ui-react';
|
||||||
|
@ -11,26 +12,33 @@ import styles from './EditInformation.module.css';
|
||||||
const EditInformation = React.memo(({ defaultData, onUpdate }) => {
|
const EditInformation = React.memo(({ defaultData, onUpdate }) => {
|
||||||
const [t] = useTranslation();
|
const [t] = useTranslation();
|
||||||
|
|
||||||
const [data, handleFieldChange] = useForm({
|
const [data, handleFieldChange] = useForm(() => ({
|
||||||
name: '',
|
name: '',
|
||||||
...defaultData,
|
phone: '',
|
||||||
});
|
organization: '',
|
||||||
|
...pickBy(defaultData),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const cleanData = useMemo(
|
||||||
|
() => ({
|
||||||
|
...data,
|
||||||
|
name: data.name.trim(),
|
||||||
|
phone: data.phone.trim() || null,
|
||||||
|
organization: data.organization.trim() || null,
|
||||||
|
}),
|
||||||
|
[data],
|
||||||
|
);
|
||||||
|
|
||||||
const nameField = useRef(null);
|
const nameField = useRef(null);
|
||||||
|
|
||||||
const handleSubmit = useCallback(() => {
|
const handleSubmit = useCallback(() => {
|
||||||
const cleanData = {
|
|
||||||
...data,
|
|
||||||
name: data.name.trim(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!cleanData.name) {
|
if (!cleanData.name) {
|
||||||
nameField.current.select();
|
nameField.current.select();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
onUpdate(cleanData);
|
onUpdate(cleanData);
|
||||||
}, [onUpdate, data]);
|
}, [onUpdate, cleanData]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form onSubmit={handleSubmit}>
|
<Form onSubmit={handleSubmit}>
|
||||||
|
@ -43,7 +51,23 @@ const EditInformation = React.memo(({ defaultData, onUpdate }) => {
|
||||||
className={styles.field}
|
className={styles.field}
|
||||||
onChange={handleFieldChange}
|
onChange={handleFieldChange}
|
||||||
/>
|
/>
|
||||||
<Button positive disabled={dequal(data, defaultData)} content={t('action.save')} />
|
<div className={styles.text}>{t('common.phone')}</div>
|
||||||
|
<Input
|
||||||
|
fluid
|
||||||
|
name="phone"
|
||||||
|
value={data.phone}
|
||||||
|
className={styles.field}
|
||||||
|
onChange={handleFieldChange}
|
||||||
|
/>
|
||||||
|
<div className={styles.text}>{t('common.organization')}</div>
|
||||||
|
<Input
|
||||||
|
fluid
|
||||||
|
name="organization"
|
||||||
|
value={data.organization}
|
||||||
|
className={styles.field}
|
||||||
|
onChange={handleFieldChange}
|
||||||
|
/>
|
||||||
|
<Button positive disabled={dequal(cleanData, defaultData)} content={t('action.save')} />
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -11,6 +11,8 @@ const UserSettingsModal = React.memo(
|
||||||
name,
|
name,
|
||||||
username,
|
username,
|
||||||
avatar,
|
avatar,
|
||||||
|
phone,
|
||||||
|
organization,
|
||||||
isAvatarUploading,
|
isAvatarUploading,
|
||||||
usernameUpdateForm,
|
usernameUpdateForm,
|
||||||
emailUpdateForm,
|
emailUpdateForm,
|
||||||
|
@ -38,6 +40,8 @@ const UserSettingsModal = React.memo(
|
||||||
name={name}
|
name={name}
|
||||||
username={username}
|
username={username}
|
||||||
avatar={avatar}
|
avatar={avatar}
|
||||||
|
phone={phone}
|
||||||
|
organization={organization}
|
||||||
isAvatarUploading={isAvatarUploading}
|
isAvatarUploading={isAvatarUploading}
|
||||||
usernameUpdateForm={usernameUpdateForm}
|
usernameUpdateForm={usernameUpdateForm}
|
||||||
emailUpdateForm={emailUpdateForm}
|
emailUpdateForm={emailUpdateForm}
|
||||||
|
@ -70,6 +74,8 @@ UserSettingsModal.propTypes = {
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
username: PropTypes.string,
|
username: PropTypes.string,
|
||||||
avatar: PropTypes.string,
|
avatar: PropTypes.string,
|
||||||
|
phone: PropTypes.string,
|
||||||
|
organization: PropTypes.string,
|
||||||
isAvatarUploading: PropTypes.bool.isRequired,
|
isAvatarUploading: PropTypes.bool.isRequired,
|
||||||
/* eslint-disable react/forbid-prop-types */
|
/* eslint-disable react/forbid-prop-types */
|
||||||
usernameUpdateForm: PropTypes.object.isRequired,
|
usernameUpdateForm: PropTypes.object.isRequired,
|
||||||
|
@ -90,6 +96,8 @@ UserSettingsModal.propTypes = {
|
||||||
UserSettingsModal.defaultProps = {
|
UserSettingsModal.defaultProps = {
|
||||||
username: undefined,
|
username: undefined,
|
||||||
avatar: undefined,
|
avatar: undefined,
|
||||||
|
phone: undefined,
|
||||||
|
organization: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default UserSettingsModal;
|
export default UserSettingsModal;
|
||||||
|
|
|
@ -21,6 +21,8 @@ const mapStateToProps = (state) => {
|
||||||
name,
|
name,
|
||||||
username,
|
username,
|
||||||
avatar,
|
avatar,
|
||||||
|
phone,
|
||||||
|
organization,
|
||||||
isAvatarUploading,
|
isAvatarUploading,
|
||||||
emailUpdateForm,
|
emailUpdateForm,
|
||||||
passwordUpdateForm,
|
passwordUpdateForm,
|
||||||
|
@ -32,6 +34,8 @@ const mapStateToProps = (state) => {
|
||||||
name,
|
name,
|
||||||
username,
|
username,
|
||||||
avatar,
|
avatar,
|
||||||
|
phone,
|
||||||
|
organization,
|
||||||
isAvatarUploading,
|
isAvatarUploading,
|
||||||
emailUpdateForm,
|
emailUpdateForm,
|
||||||
passwordUpdateForm,
|
passwordUpdateForm,
|
||||||
|
|
|
@ -84,6 +84,8 @@ export default {
|
||||||
noUnreadNotifications: 'No unread notifications',
|
noUnreadNotifications: 'No unread notifications',
|
||||||
openBoard_title: 'Open Board',
|
openBoard_title: 'Open Board',
|
||||||
optional_inline: 'optional',
|
optional_inline: 'optional',
|
||||||
|
organization: 'Organization',
|
||||||
|
phone: 'Phone',
|
||||||
projectNotFound_title: 'Project Not Found',
|
projectNotFound_title: 'Project Not Found',
|
||||||
refreshPageToLoadLastDataAndReceiveUpdates:
|
refreshPageToLoadLastDataAndReceiveUpdates:
|
||||||
'<0>Refresh the page</0> to load last data<br />and receive updates',
|
'<0>Refresh the page</0> to load last data<br />and receive updates',
|
||||||
|
|
|
@ -88,6 +88,8 @@ export default {
|
||||||
noUnreadNotifications: 'Уведомлений нет',
|
noUnreadNotifications: 'Уведомлений нет',
|
||||||
openBoard: 'Откройте доску',
|
openBoard: 'Откройте доску',
|
||||||
optional_inline: 'необязательно',
|
optional_inline: 'необязательно',
|
||||||
|
organization: 'Организация',
|
||||||
|
phone: 'Телефон',
|
||||||
projectNotFound: 'Доска не найдена',
|
projectNotFound: 'Доска не найдена',
|
||||||
refreshPageToLoadLastDataAndReceiveUpdates:
|
refreshPageToLoadLastDataAndReceiveUpdates:
|
||||||
'<0>Обновите страницу</0>, чтобы загрузить<br />актуальные данные и получать обновления',
|
'<0>Обновите страницу</0>, чтобы загрузить<br />актуальные данные и получать обновления',
|
||||||
|
|
|
@ -37,6 +37,8 @@ export default class extends Model {
|
||||||
email: attr(),
|
email: attr(),
|
||||||
name: attr(),
|
name: attr(),
|
||||||
avatar: attr(),
|
avatar: attr(),
|
||||||
|
phone: attr(),
|
||||||
|
organization: attr(),
|
||||||
deletedAt: attr(),
|
deletedAt: attr(),
|
||||||
isAdmin: attr({
|
isAdmin: attr({
|
||||||
getDefault: () => false,
|
getDefault: () => false,
|
||||||
|
|
|
@ -30,6 +30,16 @@ module.exports = {
|
||||||
regex: /^[a-zA-Z0-9]+(_?[a-zA-Z0-9])*$/,
|
regex: /^[a-zA-Z0-9]+(_?[a-zA-Z0-9])*$/,
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
},
|
},
|
||||||
|
phone: {
|
||||||
|
type: 'string',
|
||||||
|
isNotEmptyString: true,
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
organization: {
|
||||||
|
type: 'string',
|
||||||
|
isNotEmptyString: true,
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
exits: {
|
exits: {
|
||||||
|
@ -42,7 +52,14 @@ module.exports = {
|
||||||
},
|
},
|
||||||
|
|
||||||
async fn(inputs, exits) {
|
async fn(inputs, exits) {
|
||||||
const values = _.pick(inputs, ['email', 'password', 'name', 'username']);
|
const values = _.pick(inputs, [
|
||||||
|
'email',
|
||||||
|
'password',
|
||||||
|
'name',
|
||||||
|
'username',
|
||||||
|
'phone',
|
||||||
|
'organization',
|
||||||
|
]);
|
||||||
|
|
||||||
const user = await sails.helpers
|
const user = await sails.helpers
|
||||||
.createUser(values, this.req)
|
.createUser(values, this.req)
|
||||||
|
|
|
@ -22,6 +22,16 @@ module.exports = {
|
||||||
type: 'json',
|
type: 'json',
|
||||||
custom: (value) => _.isNull(value),
|
custom: (value) => _.isNull(value),
|
||||||
},
|
},
|
||||||
|
phone: {
|
||||||
|
type: 'string',
|
||||||
|
isNotEmptyString: true,
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
organization: {
|
||||||
|
type: 'string',
|
||||||
|
isNotEmptyString: true,
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
exits: {
|
exits: {
|
||||||
|
@ -47,7 +57,7 @@ module.exports = {
|
||||||
throw Errors.USER_NOT_FOUND;
|
throw Errors.USER_NOT_FOUND;
|
||||||
}
|
}
|
||||||
|
|
||||||
const values = _.pick(inputs, ['isAdmin', 'name', 'avatar']);
|
const values = _.pick(inputs, ['isAdmin', 'name', 'avatar', 'phone', 'organization']);
|
||||||
|
|
||||||
user = await sails.helpers.updateUser(user, values, this.req);
|
user = await sails.helpers.updateUser(user, values, this.req);
|
||||||
|
|
||||||
|
|
|
@ -42,6 +42,16 @@ module.exports = {
|
||||||
isNotEmptyString: true,
|
isNotEmptyString: true,
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
},
|
},
|
||||||
|
phone: {
|
||||||
|
type: 'string',
|
||||||
|
isNotEmptyString: true,
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
organization: {
|
||||||
|
type: 'string',
|
||||||
|
isNotEmptyString: true,
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
deletedAt: {
|
deletedAt: {
|
||||||
type: 'ref',
|
type: 'ref',
|
||||||
columnName: 'deleted_at',
|
columnName: 'deleted_at',
|
||||||
|
|
|
@ -11,6 +11,8 @@ module.exports.up = (knex) =>
|
||||||
table.text('name').notNullable();
|
table.text('name').notNullable();
|
||||||
table.text('username');
|
table.text('username');
|
||||||
table.text('avatar');
|
table.text('avatar');
|
||||||
|
table.text('phone');
|
||||||
|
table.text('organization');
|
||||||
|
|
||||||
table.timestamp('created_at', true);
|
table.timestamp('created_at', true);
|
||||||
table.timestamp('updated_at', true);
|
table.timestamp('updated_at', true);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue