Skip to content

Commit

Permalink
[NEW] Added custom fields to Add/Edit user (#17681)
Browse files Browse the repository at this point in the history
Co-authored-by: Guilherme Gazzo <[email protected]>
  • Loading branch information
gabriellsh and ggazzo authored May 20, 2020
1 parent d33d114 commit 1d9820a
Show file tree
Hide file tree
Showing 7 changed files with 303 additions and 244 deletions.
161 changes: 41 additions & 120 deletions client/admin/users/AddUser.js
Original file line number Diff line number Diff line change
@@ -1,147 +1,68 @@
import React, { useMemo, useState, useCallback } from 'react';
import { Field, TextInput, Box, ToggleSwitch, Icon, TextAreaInput, MultiSelectFiltered, Margins, Button } from '@rocket.chat/fuselage';
import React, { useMemo, useCallback } from 'react';
import { Field, Box, Button } from '@rocket.chat/fuselage';

import { useTranslation } from '../../contexts/TranslationContext';
import { useEndpointData } from '../../hooks/useEndpointData';
import { useEndpointAction } from '../../hooks/useEndpointAction';
import { isEmail } from '../../../app/utils/lib/isEmail.js';
import { useRoute } from '../../contexts/RouterContext';
import VerticalBar from '../../components/basic/VerticalBar';
import { useForm } from '../../hooks/useForm';
import UserForm from './UserForm';


export function AddUser({ roles, ...props }) {
const t = useTranslation();

const [newData, setNewData] = useState({});

const router = useRoute('admin-users');

const roleData = useEndpointData('roles.list', '') || {};

const {
values,
handlers,
reset,
hasUnsavedChanges,
} = useForm({
roles: [],
name: '',
username: '',
statusText: '',
bio: '',
email: '',
password: '',
verified: false,
requirePasswordChange: false,
setRandomPassword: false,
sendWelcomeEmail: true,
joinDefaultChannels: true,
customFields: {},
});

const goToUser = (id) => router.push({
context: 'info',
id,
});

const saveQuery = useMemo(() => ({
...Object.fromEntries(Object.entries(newData).filter(([, value]) => value !== null)),
}), [JSON.stringify(newData)]);
const saveQuery = useMemo(() => values, [JSON.stringify(values)]);

const saveAction = useEndpointAction('POST', 'users.create', saveQuery, t('User_created_successfully'));

const handleSave = async () => {
if (Object.keys(newData).length) {
const result = await saveAction();
if (result.success) {
goToUser(result.user._id);
}
const handleSave = useCallback(async () => {
const result = await saveAction();
if (result.success) {
goToUser(result.user._id);
}
};

const handleChange = (field, getValue = (e) => e.currentTarget.value) => (e) => setNewData({ ...newData, [field]: getValue(e) });
}, [saveAction]);

const {
roles: selectedRoles = [],
name = '',
username = '',
statusText = '',
bio = '',
email = '',
password = '',
verified = false,
requirePasswordChange = false,
setRandomPassword = false,
sendWelcomeEmail = true,
joinDefaultChannels = true,
} = newData;
const availableRoles = useMemo(() => (roleData && roleData.roles ? roleData.roles.map(({ _id, description }) => [_id, description || _id]) : []), [JSON.stringify(roleData)]);

const availableRoles = roleData && roleData.roles ? roleData.roles.map(({ _id, description }) => [_id, description || _id]) : [];
const append = useMemo(() => <Field>
<Field.Row>
<Box display='flex' flexDirection='row' justifyContent='space-between' w='full'>
<Button flexGrow={1} disabled={!hasUnsavedChanges} onClick={reset} mie='x4'>{t('Cancel')}</Button>
<Button flexGrow={1} disabled={!hasUnsavedChanges} onClick={handleSave}>{t('Save')}</Button>
</Box>
</Field.Row>
</Field>, [reset, handleSave]);

return <VerticalBar.ScrollableContent is='form' onSubmit={useCallback((e) => e.preventDefault(), [])} { ...props }>
<Field>
<Field.Label>{t('Name')}</Field.Label>
<Field.Row>
<TextInput flexGrow={1} value={name} onChange={handleChange('name')}/>
</Field.Row>
</Field>
<Field>
<Field.Label>{t('Username')}</Field.Label>
<Field.Row>
<TextInput flexGrow={1} value={username} onChange={handleChange('username')} addon={<Icon name='at' size='x20'/>}/>
</Field.Row>
</Field>
<Field>
<Field.Label>{t('Email')}</Field.Label>
<Field.Row>
<TextInput flexGrow={1} value={email} error={!isEmail(email) && email.length > 0 ? 'error' : undefined} onChange={handleChange('email')} addon={<Icon name='mail' size='x20'/>}/>
</Field.Row>
<Field.Row>
<Box flexGrow={1} display='flex' flexDirection='row' alignItems='center' justifyContent='space-between' mbs='x4'>
<Box>{t('Verified')}</Box><ToggleSwitch checked={verified} onChange={handleChange('verified', () => !verified)} />
</Box>
</Field.Row>
</Field>
<Field>
<Field.Label>{t('StatusMessage')}</Field.Label>
<Field.Row>
<TextInput flexGrow={1} value={statusText} onChange={handleChange('statusText')} addon={<Icon name='edit' size='x20'/>}/>
</Field.Row>
</Field>
<Field>
<Field.Label>{t('Bio')}</Field.Label>
<Field.Row>
<TextAreaInput rows={3} flexGrow={1} value={bio} onChange={handleChange('bio')} addon={<Icon name='edit' size='x20' alignSelf='center'/>}/>
</Field.Row>
</Field>
<Field>
<Field.Label>{t('Password')}</Field.Label>
<Field.Row>
<TextInput flexGrow={1} value={password} onChange={handleChange('password')} addon={<Icon name='key' size='x20'/>}/>
</Field.Row>
</Field>
<Field>
<Field.Row>
<Box flexGrow={1} display='flex' flexDirection='row' alignItems='center' justifyContent='space-between'>
<Box>{t('Require_password_change')}</Box><ToggleSwitch disabled={setRandomPassword} checked={setRandomPassword || requirePasswordChange} onChange={handleChange('requirePasswordChange', () => !requirePasswordChange)} />
</Box>
</Field.Row>
</Field>
<Field>
<Field.Row>
<Box flexGrow={1} display='flex' flexDirection='row' alignItems='center' justifyContent='space-between'>
<Box>{t('Set_random_password_and_send_by_email')}</Box><ToggleSwitch checked={setRandomPassword} onChange={handleChange('setRandomPassword', () => !setRandomPassword)} />
</Box>
</Field.Row>
</Field>
<Field>
<Field.Label>{t('Roles')}</Field.Label>
<Field.Row>
<MultiSelectFiltered options={availableRoles} value={selectedRoles} onChange={handleChange('roles', (value) => value)} placeholder={t('Select_role')} />
</Field.Row>
</Field>
<Field>
<Field.Row>
<Box flexGrow={1} display='flex' flexDirection='row' alignItems='center' justifyContent='space-between'>
<Box>{t('Join_default_channels')}</Box><ToggleSwitch checked={joinDefaultChannels} onChange={handleChange('joinDefaultChannels', () => !joinDefaultChannels)} />
</Box>
</Field.Row>
</Field>
<Field>
<Field.Row>
<Box flexGrow={1} display='flex' flexDirection='row' alignItems='center' justifyContent='space-between'>
<Box>{t('Send_welcome_email')}</Box><ToggleSwitch checked={sendWelcomeEmail} onChange={handleChange('sendWelcomeEmail', () => !sendWelcomeEmail)} />
</Box>
</Field.Row>
</Field>
<Field>
<Field.Row>
<Box display='flex' flexDirection='row' justifyContent='space-between' w='full'>
<Margins inlineEnd='x4'>
<Button flexGrow={1} onClick={() => setNewData({})}>{t('Cancel')}</Button>
<Button mie='none' flexGrow={1} onClick={handleSave}>{t('Save')}</Button>
</Margins>
</Box>
</Field.Row>
</Field>
</VerticalBar.ScrollableContent>;
return <UserForm formValues={values} formHandlers={handlers} availableRoles={availableRoles} append={append} {...props}/>;
}
80 changes: 80 additions & 0 deletions client/admin/users/CustomFieldsForm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import React, { useMemo, useEffect } from 'react';
import { TextInput, Select, Field, Divider, Box } from '@rocket.chat/fuselage';

import { useSetting } from '../../contexts/SettingsContext';
import { useForm } from '../../hooks/useForm';
import { useTranslation } from '../../contexts/TranslationContext';
import { capitalize } from '../../helpers/capitalize';

const CustomTextInput = (props) => {
const t = useTranslation();
const { name, required, minLength, maxLength, setState, state } = props;
const verify = useMemo(() => {
const error = [];
if (!state && required) { error.push(t('Field_required')); }
if (state.length < minLength) { error.push(t('Min_length_is', minLength)); }
return error.join(', ');
}, [required, minLength, maxLength, state]);

return useMemo(() => <Field>
<Field.Label>{name}</Field.Label>
<Field.Row>
<TextInput name={name} error={verify} maxLength={maxLength} flexGrow={1} value={state} required={required} onChange={(e) => setState(e.currentTarget.value)}/>
</Field.Row>
<Field.Error>{verify}</Field.Error>
</Field>, [name, verify, state, required]);
};

const CustomSelect = (props) => {
const t = useTranslation();
const { name, required, options, setState, state } = props;
const mappedOptions = useMemo(() => Object.values(options).map((value) => [value, value]), [...options]);
const verify = useMemo(() => (!state.length && required ? t('Field_required') : ''), [required, state]);

return useMemo(() => <Field>
<Field.Label>{name}</Field.Label>
<Field.Row>
<Select name={name} error={verify} flexGrow={1} value={state} options={mappedOptions} required={required} onChange={(val) => setState(val)}/>
</Field.Row>
<Field.Error>{verify}</Field.Error>
</Field>, [name, verify, state, required, JSON.stringify(mappedOptions)]);
};

const CustomFieldsAssembler = ({ formValues, formHandlers, customFields, ...props }) => Object.entries(customFields).map(([key, value]) => {
const extraProps = {
key,
name: key,
setState: formHandlers[`handle${ capitalize(key) }`],
state: formValues[key],
...value,
};
return value.type === 'text'
? <CustomTextInput {...extraProps} {...props}/>
: <CustomSelect {...extraProps} {...props}/>;
});

export default function CustomFieldsForm({ customFieldsData, setCustomFieldsData, ...props }) {
const t = useTranslation();

const customFieldsJson = useSetting('Accounts_CustomFields');

const customFields = useMemo(() => JSON.parse(customFieldsJson || '{}'), []);

if (!Object.values(customFields).length) {
return null;
}

const defaultFields = useMemo(() => Object.entries(customFields).reduce((data, [key, value]) => { data[key] = value.defaultValue ?? ''; return data; }, {}), []);

const { values, handlers } = useForm({ ...defaultFields, ...customFieldsData });

useEffect(() => {
setCustomFieldsData(values);
}, [JSON.stringify(values)]);

return <>
<Divider />
<Box fontScale='s2'>{t('Custom_Fields')}</Box>
<CustomFieldsAssembler formValues={values} formHandlers={handlers} customFields={customFields} {...props}/>
</>;
}
Loading

0 comments on commit 1d9820a

Please sign in to comment.