add-frames #1
7 changed files with 267 additions and 1 deletions
2
src/modules/apps/index.ts
Normal file
2
src/modules/apps/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export { Apps } from './Apps';
|
||||||
|
export { AppSingle } from './AppSingle';
|
|
@ -1 +1,4 @@
|
||||||
|
export { Login } from './login';
|
||||||
export { Dashboard } from './dashboard';
|
export { Dashboard } from './dashboard';
|
||||||
|
export { Apps, AppSingle } from './apps';
|
||||||
|
export { Users } from './users';
|
||||||
|
|
|
@ -13,7 +13,7 @@ import { debounce } from 'lodash';
|
||||||
import { useAuth } from 'src/services/auth';
|
import { useAuth } from 'src/services/auth';
|
||||||
|
|
||||||
import { UserModal } from '../../components/UserModal';
|
import { UserModal } from '../../components/UserModal';
|
||||||
// import { MultipleUsersModal } from '../components/multipleUsersModal';
|
import { MultipleUsersModal } from './components/MultipleUsersModal';
|
||||||
|
|
||||||
export const Users: React.FC = () => {
|
export const Users: React.FC = () => {
|
||||||
const [selectedRowsIds, setSelectedRowsIds] = useState({});
|
const [selectedRowsIds, setSelectedRowsIds] = useState({});
|
||||||
|
@ -184,6 +184,7 @@ export const Users: React.FC = () => {
|
||||||
{configureModal && (
|
{configureModal && (
|
||||||
<UserModal open={configureModal} onClose={configureModalClose} userId={userId} setUserId={setUserId} />
|
<UserModal open={configureModal} onClose={configureModalClose} userId={userId} setUserId={setUserId} />
|
||||||
)}
|
)}
|
||||||
|
{multipleUsersModal && <MultipleUsersModal open={multipleUsersModal} onClose={multipleUsersModalClose} />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,248 @@
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { useFieldArray, useForm, useWatch } from 'react-hook-form';
|
||||||
|
|
||||||
|
import { Banner, StepsModal, ProgressSteps } from 'src/components';
|
||||||
|
import { Select, TextArea } from 'src/components/Form';
|
||||||
|
import { MultipleUsersData, UserRole, useUsers } from 'src/services/users';
|
||||||
|
import { allAppAccessList } from 'src/components/UserModal/consts';
|
||||||
|
import { ProgressStepInfo, ProgressStepStatus } from 'src/components/ProgressSteps/types';
|
||||||
|
import { initialMultipleUsersForm, MultipleUsersModalProps } from './types';
|
||||||
|
|
||||||
|
export const MultipleUsersModal = ({ open, onClose }: MultipleUsersModalProps) => {
|
||||||
|
const [steps, setSteps] = useState<ProgressStepInfo[]>([]);
|
||||||
|
const [isAdminRoleSelected, setAdminRoleSelected] = useState(false);
|
||||||
|
const { createUsers, userModalLoading } = useUsers();
|
||||||
|
|
||||||
|
const { control, handleSubmit } = useForm<MultipleUsersData>({
|
||||||
|
defaultValues: initialMultipleUsersForm,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { fields, update } = useFieldArray({
|
||||||
|
control,
|
||||||
|
name: 'appRoles',
|
||||||
|
});
|
||||||
|
|
||||||
|
const dashboardRole = useWatch({
|
||||||
|
control,
|
||||||
|
name: 'appRoles.0.role',
|
||||||
|
});
|
||||||
|
|
||||||
|
const csvDataWatch = useWatch({
|
||||||
|
control,
|
||||||
|
name: 'csvUserData',
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const isAdminDashboardRoleSelected = dashboardRole === UserRole.Admin;
|
||||||
|
setAdminRoleSelected(isAdminDashboardRoleSelected);
|
||||||
|
if (isAdminDashboardRoleSelected) {
|
||||||
|
fields.forEach((field, index) => update(index, { name: field.name, role: UserRole.Admin }));
|
||||||
|
} else {
|
||||||
|
fields.forEach((field, index) => update(index, { name: field.name, role: UserRole.User }));
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [dashboardRole]);
|
||||||
|
|
||||||
|
const renderUsersCsvDataInput = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="mt-8">
|
||||||
|
<h3 className="text-lg leading-6 font-medium text-gray-900">CSV data</h3>
|
||||||
|
</div>
|
||||||
|
<div className="mt-6">
|
||||||
|
<TextArea
|
||||||
|
control={control}
|
||||||
|
name="csvUserData"
|
||||||
|
placeholder={`Please paste users in CSV format: email, name\nuser1@example.com,User One\nuser2@example.com,User Two`}
|
||||||
|
rows={15}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderAppAccess = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="mt-8">
|
||||||
|
<h3 className="text-lg leading-6 font-medium text-gray-900">App Access</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isAdminRoleSelected && (
|
||||||
|
<div className="sm:col-span-6">
|
||||||
|
<Banner title="Admin users automatically have admin-level access to all apps." titleSm="Admin user" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div className="flow-root mt-6">
|
||||||
|
<ul className="-my-5 divide-y divide-gray-200">
|
||||||
|
{fields
|
||||||
|
.filter((field) => field.name === 'dashboard')
|
||||||
|
.map((item, index) => (
|
||||||
|
<li className="py-4" key={item.name}>
|
||||||
|
<div className="flex items-center space-x-4">
|
||||||
|
<div className="flex-shrink-0 flex-1 flex items-center">
|
||||||
|
<img
|
||||||
|
className="h-10 w-10 rounded-md overflow-hidden"
|
||||||
|
src={_.find(allAppAccessList, ['name', item.name!])?.image}
|
||||||
|
alt={item.name ?? 'Image'}
|
||||||
|
/>
|
||||||
|
<h3 className="ml-4 text-md leading-6 font-medium text-gray-900">
|
||||||
|
{_.find(allAppAccessList, ['name', item.name!])?.label}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div className="sm:col-span-2">
|
||||||
|
<Select
|
||||||
|
key={item.id}
|
||||||
|
control={control}
|
||||||
|
name={`appRoles.${index}.role`}
|
||||||
|
options={[
|
||||||
|
{ value: UserRole.User, name: 'User' },
|
||||||
|
{ value: UserRole.Admin, name: 'Admin' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
{!isAdminRoleSelected &&
|
||||||
|
fields.map((item, index) => {
|
||||||
|
if (item.name === 'dashboard') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li className="py-4" key={item.name}>
|
||||||
|
<div className="flex items-center space-x-4">
|
||||||
|
<div className="flex-shrink-0 flex-1 flex items-center">
|
||||||
|
<img
|
||||||
|
className="h-10 w-10 rounded-md overflow-hidden"
|
||||||
|
src={_.find(allAppAccessList, ['name', item.name!])?.image}
|
||||||
|
alt={item.name ?? 'Image'}
|
||||||
|
/>
|
||||||
|
<h3 className="ml-4 text-md leading-6 font-medium text-gray-900">
|
||||||
|
{_.find(allAppAccessList, ['name', item.name!])?.label}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div className="sm:col-span-2">
|
||||||
|
<Select
|
||||||
|
key={item.id}
|
||||||
|
control={control}
|
||||||
|
name={`appRoles.${index}.role`}
|
||||||
|
disabled={isAdminRoleSelected}
|
||||||
|
options={[
|
||||||
|
{ value: UserRole.NoAccess, name: 'No Access' },
|
||||||
|
{ value: UserRole.User, name: 'User' },
|
||||||
|
{ value: UserRole.Admin, name: 'Admin' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSteps([
|
||||||
|
{
|
||||||
|
id: 'Step 1',
|
||||||
|
name: 'Enter CSV user data',
|
||||||
|
status: ProgressStepStatus.Current,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Step 2',
|
||||||
|
name: 'Define app access roles',
|
||||||
|
status: ProgressStepStatus.Upcoming,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [open]);
|
||||||
|
|
||||||
|
const handleSave = async () => {
|
||||||
|
try {
|
||||||
|
await handleSubmit((data) => createUsers(data))();
|
||||||
|
} catch (e: any) {
|
||||||
|
// Continue
|
||||||
|
}
|
||||||
|
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
const getActiveStepIndex = () => _.findIndex(steps, { status: ProgressStepStatus.Current });
|
||||||
|
|
||||||
|
const updateStepsStatus = (nextIndex: number) => {
|
||||||
|
const updatedSteps = [...steps];
|
||||||
|
_.forEach(updatedSteps, (step, index) => {
|
||||||
|
if (index < nextIndex) {
|
||||||
|
step.status = ProgressStepStatus.Complete;
|
||||||
|
} else if (index === nextIndex) {
|
||||||
|
step.status = ProgressStepStatus.Current;
|
||||||
|
} else {
|
||||||
|
step.status = ProgressStepStatus.Upcoming;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setSteps(updatedSteps);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleStepClick = (stepId: string) => {
|
||||||
|
const activeStepIndex = _.findIndex(steps, { id: stepId });
|
||||||
|
updateStepsStatus(activeStepIndex);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleNext = () => {
|
||||||
|
const nextIndex = getActiveStepIndex() + 1;
|
||||||
|
updateStepsStatus(nextIndex);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePrevious = () => {
|
||||||
|
const nextIndex = getActiveStepIndex() - 1;
|
||||||
|
updateStepsStatus(nextIndex);
|
||||||
|
};
|
||||||
|
|
||||||
|
const activeStepIndex = getActiveStepIndex();
|
||||||
|
const showSave = !_.some(steps, { status: ProgressStepStatus.Upcoming });
|
||||||
|
const showPrevious = _.some(steps, { status: ProgressStepStatus.Complete });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StepsModal
|
||||||
|
onClose={handleClose}
|
||||||
|
open={open}
|
||||||
|
onSave={handleSave}
|
||||||
|
onNext={handleNext}
|
||||||
|
onPrevious={handlePrevious}
|
||||||
|
showPreviousButton={showPrevious}
|
||||||
|
isLoading={userModalLoading}
|
||||||
|
useCancelButton
|
||||||
|
showSaveButton={showSave}
|
||||||
|
saveButtonDisabled={_.isEmpty(csvDataWatch)}
|
||||||
|
>
|
||||||
|
<div className="bg-white px-4">
|
||||||
|
<div className="space-y-10 divide-y divide-gray-200">
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg leading-6 font-medium text-gray-900">Add new users</h3>
|
||||||
|
</div>
|
||||||
|
<div className="sm:px-6 pt-6">
|
||||||
|
<ProgressSteps steps={steps} onStepClick={handleStepClick}>
|
||||||
|
{activeStepIndex === 0 ? renderUsersCsvDataInput() : renderAppAccess()}
|
||||||
|
</ProgressSteps>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</StepsModal>
|
||||||
|
);
|
||||||
|
};
|
1
src/modules/users/components/MultipleUsersModal/index.ts
Normal file
1
src/modules/users/components/MultipleUsersModal/index.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export { MultipleUsersModal } from './MultipleUsersModal';
|
10
src/modules/users/components/MultipleUsersModal/types.ts
Normal file
10
src/modules/users/components/MultipleUsersModal/types.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import { initialAppRoles } from 'src/components/UserModal/consts';
|
||||||
|
|
||||||
|
export type MultipleUsersModalProps = {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const initialMultipleUsersForm = {
|
||||||
|
appRoles: initialAppRoles,
|
||||||
|
};
|
1
src/modules/users/index.ts
Normal file
1
src/modules/users/index.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export { Users } from './Users';
|
Loading…
Reference in a new issue