mirror of
https://github.com/ToolJet/ToolJet
synced 2026-05-24 09:28:31 +00:00
325 lines
13 KiB
JavaScript
325 lines
13 KiB
JavaScript
import React, { useState, useCallback, useRef, useEffect } from 'react';
|
||
import cx from 'classnames';
|
||
import SolidIcon from '@/_ui/Icon/SolidIcons';
|
||
import { ToolTip } from '@/_components/ToolTip';
|
||
import { useTranslation } from 'react-i18next';
|
||
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
|
||
import { toast } from 'react-hot-toast';
|
||
import { FileDropzone } from './FileDropzone';
|
||
import { USER_DRAWER_MODES } from '@/_helpers/utils';
|
||
import { UserGroupsSelect } from './UserGroupsSelect';
|
||
|
||
function InviteUsersForm({
|
||
onClose,
|
||
manageUser,
|
||
changeNewUserOption,
|
||
errors,
|
||
fields,
|
||
handleFileChange,
|
||
uploadingUsers,
|
||
onCancel,
|
||
inviteBulkUsers,
|
||
groups = [],
|
||
currentEditingUser,
|
||
userDrawerMode,
|
||
setUserValues,
|
||
creatingUser,
|
||
}) {
|
||
const { t } = useTranslation();
|
||
const [activeTab, setActiveTab] = useState(1);
|
||
const [selectedGroups, setSelectedGroups] = useState([]);
|
||
const [existingGroups, setExistingGroups] = useState([]);
|
||
const customGroups = groups.filter((group) => group.groupType === 'custom');
|
||
const roleGroups = groups.filter((group) => group.groupType === 'default');
|
||
const groupedOptions = [
|
||
{
|
||
label: 'default',
|
||
options: roleGroups,
|
||
},
|
||
{
|
||
label: 'custom',
|
||
options: customGroups,
|
||
},
|
||
];
|
||
|
||
const hiddenFileInput = useRef(null);
|
||
|
||
useEffect(() => {
|
||
if (currentEditingUser && groups.length) {
|
||
const { first_name, last_name, email, groups: addedToCustomGroups, role_group } = currentEditingUser;
|
||
const addedToGroups = [...addedToCustomGroups, ...role_group];
|
||
setUserValues({
|
||
fullName: `${first_name}${last_name && ` ${last_name}`}`,
|
||
email: email,
|
||
});
|
||
const preSelectedGroups = groups
|
||
.filter((group) => addedToGroups.map((group) => group.name).includes(group.value))
|
||
.map((filteredGroup) => ({
|
||
...filteredGroup,
|
||
label: filteredGroup.name,
|
||
}));
|
||
setExistingGroups(
|
||
groups.filter((group) => addedToCustomGroups.map((gp) => gp.name).includes(group.value)).map((g) => g.id)
|
||
);
|
||
onChangeHandler(preSelectedGroups);
|
||
}
|
||
}, [currentEditingUser, groups]);
|
||
|
||
const onDrop = useCallback((acceptedFiles) => {
|
||
const file = acceptedFiles[0];
|
||
if (Math.round(file.size / 1024) > 1024) {
|
||
toast.error('File size cannot exceed more than 1MB');
|
||
} else {
|
||
handleFileChange(file);
|
||
}
|
||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||
}, []);
|
||
|
||
const handleClick = () => {
|
||
hiddenFileInput.current.click();
|
||
};
|
||
|
||
const onChangeHandler = (items) => {
|
||
let finalGroup = items;
|
||
const roleGroups = items.filter((group) => group.groupType === 'default');
|
||
const currentRole = selectedGroups.find((group) => group.groupType === 'default');
|
||
if (roleGroups.length == 2) {
|
||
finalGroup = items.filter((group) => group.value !== currentRole.value);
|
||
}
|
||
setSelectedGroups(finalGroup);
|
||
};
|
||
|
||
const handleCreateUser = (e) => {
|
||
e.preventDefault();
|
||
const role = selectedGroups.find((group) => group.groupType === 'default').value;
|
||
const selectedGroupsIds = selectedGroups.filter((group) => group.groupType !== 'default').map((group) => group.id);
|
||
manageUser(currentEditingUser?.id, selectedGroupsIds, role);
|
||
};
|
||
|
||
const handleEditUser = (e) => {
|
||
e.preventDefault();
|
||
const { newGroupsToAdd, groupsToRemove, selectedGroupsIds, role } = getEditedGroups();
|
||
manageUser(currentEditingUser.id, selectedGroupsIds, role, newGroupsToAdd, groupsToRemove);
|
||
};
|
||
|
||
const getEditedGroups = () => {
|
||
let role = null;
|
||
if (currentEditingUser)
|
||
role = selectedGroups.find(
|
||
(group) =>
|
||
group.groupType === 'default' && !currentEditingUser.role_group.map((role) => role.name).includes(group.value)
|
||
)?.value;
|
||
const selectedGroupsIds = selectedGroups.filter((group) => group.groupType !== 'default').map((group) => group.id);
|
||
const newGroupsToAdd = selectedGroupsIds.filter((selectedGroupId) => !existingGroups.includes(selectedGroupId));
|
||
const groupsToRemove = existingGroups.filter((existingGroup) => !selectedGroupsIds.includes(existingGroup));
|
||
return { newGroupsToAdd, groupsToRemove, selectedGroupsIds, role };
|
||
};
|
||
|
||
const isEdited = () => {
|
||
const { newGroupsToAdd, groupsToRemove, role } = getEditedGroups();
|
||
const { first_name, last_name } = currentEditingUser || {};
|
||
return isEditing
|
||
? fields['fullName'] !== `${first_name}${last_name && ` ${last_name}`}` ||
|
||
groupsToRemove.length ||
|
||
role !== undefined ||
|
||
newGroupsToAdd.length
|
||
: true;
|
||
};
|
||
|
||
const isEditing = userDrawerMode === USER_DRAWER_MODES.EDIT;
|
||
const containRoleGroup =
|
||
selectedGroups.filter((item) => ['admin', 'end-user', 'builder'].includes(item.value)).length > 0;
|
||
|
||
return (
|
||
<div>
|
||
<div className="animation-fade invite-user-drawer-wrap">
|
||
<div className="drawer-card-wrap invite-user-drawer-wrap">
|
||
<div className="card-header">
|
||
<div className="card-header-inner-wrap">
|
||
<h3 className="tj-text-lg tj-text font-weight-500" data-cy="add-users-card-title">
|
||
{!isEditing
|
||
? t('header.organization.menus.manageUsers.addNewUser', 'Add new user')
|
||
: 'Edit user details'}
|
||
</h3>
|
||
<div
|
||
onClick={() => {
|
||
onCancel();
|
||
onClose();
|
||
}}
|
||
style={{ cursor: 'pointer' }}
|
||
data-cy="close-button"
|
||
>
|
||
<SolidIcon name="remove" width="16" />
|
||
</div>
|
||
</div>
|
||
{!isEditing && (
|
||
<div className="tj-drawer-tabs-container-outer">
|
||
<div className="tj-drawer-tabs-container">
|
||
<button
|
||
className={`tj-drawer-tabs-btn tj-text-xsm ${activeTab == 1 && 'tj-drawer-tabs-btn-active'}`}
|
||
onClick={() => setActiveTab(1)}
|
||
data-cy="button-invite-with-email"
|
||
>
|
||
<SolidIcon name="mail" width="14" fill={activeTab == 1 ? '#11181C' : '#687076'} />
|
||
<span> Invite with email</span>
|
||
</button>
|
||
<button
|
||
className={`tj-drawer-tabs-btn tj-text-xsm ${activeTab == 2 && 'tj-drawer-tabs-btn-active'}`}
|
||
onClick={() => setActiveTab(2)}
|
||
data-cy="button-upload-csv-file"
|
||
>
|
||
<SolidIcon name="fileupload" width="14" fill={activeTab == 2 ? '#11181C' : '#687076'} />
|
||
<span>Upload CSV file</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
{activeTab == 1 ? (
|
||
<div className="manage-users-drawer-content">
|
||
<div className="invite-user-by-email">
|
||
<form
|
||
onSubmit={isEditing ? handleEditUser : handleCreateUser}
|
||
noValidate
|
||
className="invite-email-body"
|
||
id="inviteByEmail"
|
||
>
|
||
<label className="form-label" data-cy="label-full-name-input-field">
|
||
Name
|
||
</label>
|
||
<div className="form-group mb-3 ">
|
||
<ToolTip
|
||
delay={{ show: '0', hide: '0' }}
|
||
placement="bottom"
|
||
message="Only user can edit their name"
|
||
show={isEditing}
|
||
>
|
||
<div className="tj-app-input">
|
||
<input
|
||
type="text"
|
||
className={cx('form-control', { disabled: isEditing })}
|
||
placeholder={t('header.organization.menus.manageUsers.enterFullName', 'Enter full name')}
|
||
name="fullName"
|
||
onChange={changeNewUserOption.bind(this, 'fullName')}
|
||
value={fields['fullName']}
|
||
data-cy="input-field-full-name"
|
||
disabled={isEditing}
|
||
/>
|
||
<span className="text-danger" data-cy="error-message-fullname">
|
||
{errors['fullName']}
|
||
</span>
|
||
</div>
|
||
</ToolTip>
|
||
</div>
|
||
<div className="form-group mb-3 ">
|
||
<label className="form-label" data-cy="label-email-input-field">
|
||
{t('header.organization.menus.manageUsers.emailAddress', 'Email Address')}
|
||
</label>
|
||
<ToolTip
|
||
delay={{ show: '0', hide: '0' }}
|
||
placement="bottom"
|
||
message="Cannot edit user email address"
|
||
show={isEditing}
|
||
>
|
||
<div className="tj-app-input">
|
||
<input
|
||
type="text"
|
||
className={cx('form-control', { disabled: isEditing })}
|
||
aria-describedby="emailHelp"
|
||
placeholder={t('header.organization.menus.manageUsers.enterEmail', 'Enter Email')}
|
||
name="email"
|
||
onChange={changeNewUserOption.bind(this, 'email')}
|
||
value={fields['email']}
|
||
data-cy="input-field-email"
|
||
disabled={isEditing}
|
||
/>
|
||
<span className="text-danger" data-cy="error-message-email">
|
||
{errors['email']}
|
||
</span>
|
||
</div>
|
||
</ToolTip>
|
||
</div>
|
||
<div className="form-group mb-3 manage-groups-invite-form" data-cy="user-group-select">
|
||
<label className="form-label" data-cy="label-group-input-field">
|
||
{isEditing
|
||
? 'User groups'
|
||
: t('header.organization.menus.manageUsers.selectGroup', 'Select Group')}
|
||
</label>
|
||
<UserGroupsSelect value={selectedGroups} onChange={onChangeHandler} options={groupedOptions} />
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
) : (
|
||
<div className="manage-users-drawer-content-bulk">
|
||
<div className="manage-users-drawer-content-bulk-download-prompt">
|
||
<div className="user-csv-template-wrap">
|
||
<div>
|
||
<SolidIcon name="information" fill="#F76808" width="26" />
|
||
</div>
|
||
<div>
|
||
<p className="tj-text tj-text-sm" data-cy="helper-text-bulk-upload">
|
||
Download the ToolJet template to add user details or format your file in the same as the template.
|
||
ToolJet won’t be able to recognise files in any other format.{' '}
|
||
</p>
|
||
<ButtonSolid
|
||
href="../../assets/csv/sample_upload.csv"
|
||
download="sample_upload.csv"
|
||
variant="tertiary"
|
||
className="download-template-btn"
|
||
as={'a'}
|
||
leftIcon="folderdownload"
|
||
iconWidth="13"
|
||
data-cy="button-download-template"
|
||
>
|
||
Download Template
|
||
</ButtonSolid>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<FileDropzone
|
||
handleClick={handleClick}
|
||
hiddenFileInput={hiddenFileInput}
|
||
errors={errors}
|
||
handleFileChange={handleFileChange}
|
||
inviteBulkUsers={inviteBulkUsers}
|
||
onDrop={onDrop}
|
||
/>
|
||
</div>
|
||
)}
|
||
<div className="manage-users-drawer-footer">
|
||
<ButtonSolid
|
||
data-cy="cancel-button"
|
||
onClick={() => {
|
||
onCancel();
|
||
onClose();
|
||
}}
|
||
variant="tertiary"
|
||
>
|
||
{t('globals.cancel', 'Cancel')}
|
||
</ButtonSolid>
|
||
|
||
<ButtonSolid
|
||
form={activeTab == 1 ? 'inviteByEmail' : 'inviteBulkUsers'}
|
||
type="submit"
|
||
variant="primary"
|
||
disabled={uploadingUsers || creatingUser || !isEdited() || (!isEditing && !containRoleGroup)}
|
||
data-cy={activeTab == 1 ? 'button-invite-users' : 'button-upload-users'}
|
||
leftIcon={activeTab == 1 ? 'sent' : 'fileupload'}
|
||
width="20"
|
||
fill={'#FDFDFE'}
|
||
isLoading={uploadingUsers || creatingUser}
|
||
>
|
||
{!isEditing
|
||
? activeTab == 1
|
||
? t('header.organization.menus.manageUsers.inviteUsers', 'Invite Users')
|
||
: 'Upload users'
|
||
: 'Update'}
|
||
</ButtonSolid>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
export default InviteUsersForm;
|