Skip to content

Commit 629e7b7

Browse files
committed
feat: add JourneyModal component for managing journey steps in Getting Started widget
Signed-off-by: amitamrutiya <amitamrutiya2210@gmail.com>
1 parent da73da4 commit 629e7b7

2 files changed

Lines changed: 565 additions & 0 deletions

File tree

Lines changed: 389 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,389 @@
1+
/* eslint-disable @typescript-eslint/no-explicit-any */
2+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
3+
// @ts-nocheck
4+
5+
import { Theme } from '@mui/material';
6+
import { useEffect, useState } from 'react';
7+
import {
8+
Box,
9+
Chip,
10+
FormControl,
11+
InputLabel,
12+
MenuItem,
13+
OutlinedInput,
14+
Select,
15+
TextField
16+
} from '../../../base';
17+
import { InviteUserIcon } from '../../../icons';
18+
import { styled, useTheme } from '../../../theme';
19+
import { Modal, ModalBody, ModalFooter, PrimaryActionButtons } from '../../Modal';
20+
import { withDefaultPageArgs } from '../../PerformersSection/PerformersSection';
21+
import TeamSearchField from './TeamSearchField';
22+
23+
const CreateUserInputField = styled(TextField)(() => ({
24+
width: 'auto'
25+
}));
26+
27+
const FormControlSelect = styled(FormControl)(() => ({
28+
width: '100%',
29+
'& .MuiSelect-select': {
30+
padding: '0.8rem 0.6rem',
31+
['@media (max-width : 899px)']: {
32+
width: '18.5rem'
33+
}
34+
},
35+
'& .MuiChip-root': {
36+
height: '1.5rem'
37+
}
38+
}));
39+
40+
const EMAIL_REGEXP =
41+
/^[\w!#$%&'*+\-\\/=?^_`{|}~]+(\.[\w!#$%&'*+\-\\/=?^_`{|}~]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*(\.[a-zA-Z]{2,})$/;
42+
43+
interface Organization {
44+
id: string;
45+
name: string;
46+
}
47+
48+
interface Team {
49+
id: string;
50+
name: string;
51+
}
52+
53+
interface ErrorMessages {
54+
inviteeEmail: string;
55+
}
56+
57+
interface UserInviteModalProps {
58+
open: boolean;
59+
handleInviteModalClose: () => void;
60+
setLoading: (loading: boolean) => void;
61+
setInviteModal: (open: boolean) => void;
62+
currentOrgId: string;
63+
useGetOrgsQuery: any;
64+
useGetUserOrgRolesQuery: any;
65+
useHandleUserInviteMutation: any;
66+
useNotificationHandlers: () => {
67+
handleSuccess: (message: string) => void;
68+
handleError: (message: string) => void;
69+
};
70+
isAssignUserRolesAllowed: boolean;
71+
useLazyGetTeamsQuery: any;
72+
}
73+
74+
export default function UserInviteModal({
75+
open,
76+
handleInviteModalClose,
77+
setLoading,
78+
setInviteModal,
79+
currentOrgId,
80+
useGetOrgsQuery,
81+
useGetUserOrgRolesQuery,
82+
useHandleUserInviteMutation,
83+
useNotificationHandlers,
84+
isAssignUserRolesAllowed,
85+
useLazyGetTeamsQuery
86+
}: UserInviteModalProps) {
87+
const [inviteeFirstName, setInviteeFirstName] = useState<string>('');
88+
const [inviteeLastName, setInviteeLastName] = useState<string>('');
89+
const [roles, setRoles] = useState<string[]>([]);
90+
const [inviteeEmail, setInviteeEmail] = useState<string>('');
91+
const [orgRoles, setOrgRoles] = useState<string[]>(['user']);
92+
const [teams, setTeams] = useState<Team[]>([]);
93+
const { handleSuccess, handleError } = useNotificationHandlers();
94+
const [errorMessages, setErrorMessages] = useState<ErrorMessages>({
95+
inviteeEmail: ''
96+
});
97+
const theme = useTheme();
98+
const [availableProviderRoles, setAvailableProviderRoles] = useState<string[]>([]);
99+
const [availableOrgRoles, setAvailableOrgRoles] = useState<string[]>([]);
100+
101+
const { data } = useGetOrgsQuery(withDefaultPageArgs());
102+
const orgs = data?.organizations;
103+
const defaultOrgSelection = { id: 'none', name: 'None' };
104+
const [organization, setOrganization] = useState<Organization>(defaultOrgSelection);
105+
const { data: providerRolesData } = useGetUserOrgRolesQuery({
106+
orgId: currentOrgId,
107+
all: true,
108+
order: 'role_name asc',
109+
selector: 'provider'
110+
});
111+
112+
const { data: organizationRolesData } = useGetUserOrgRolesQuery({
113+
orgId: currentOrgId,
114+
all: true,
115+
order: 'role_name asc',
116+
selector: 'organization'
117+
});
118+
const [userInvite] = useHandleUserInviteMutation();
119+
120+
useEffect(() => {
121+
if (currentOrgId) {
122+
const providerRoles: string[] = [];
123+
const organizationRoles: string[] = [];
124+
125+
if (providerRolesData) {
126+
providerRolesData?.roles?.forEach((role: { role_name: string }) =>
127+
providerRoles.push(role?.role_name)
128+
);
129+
setAvailableProviderRoles(providerRoles);
130+
}
131+
132+
if (organizationRolesData) {
133+
organizationRolesData?.roles?.forEach((role: { role_name: string }) =>
134+
organizationRoles.push(role?.role_name)
135+
);
136+
setAvailableOrgRoles(organizationRoles);
137+
}
138+
}
139+
}, [currentOrgId, providerRolesData, organizationRolesData]);
140+
141+
function getSelectStyle(ele: string, arr: string[], theme: Theme) {
142+
return {
143+
fontWeight:
144+
arr.indexOf(ele) === -1
145+
? theme.typography.fontWeightRegular
146+
: theme.typography.fontWeightMedium
147+
};
148+
}
149+
150+
const handleRoleChange = (event: { target: { value: string | string[] } }) => {
151+
let value = event.target.value;
152+
value = typeof value === 'string' ? value.split(',') : value;
153+
setRoles(value);
154+
};
155+
156+
const handleOrgRoleChange = (event: { target: { value: string | string[] } }) => {
157+
let value = event.target.value;
158+
value = typeof value === 'string' ? value.split(',') : value;
159+
setOrgRoles(value);
160+
};
161+
162+
const handleInviteeEmailChange = (e: React.ChangeEvent<HTMLInputElement>) => {
163+
const text = e.target.value;
164+
setErrorMessages((state) => ({ ...state, inviteeEmail: '' }));
165+
setInviteeEmail(text);
166+
};
167+
168+
const handleInviteeFirstNameChange = (e: React.ChangeEvent<HTMLInputElement>) =>
169+
setInviteeFirstName(e.target.value);
170+
171+
const handleInviteeLastNameChange = (e: React.ChangeEvent<HTMLInputElement>) =>
172+
setInviteeLastName(e.target.value);
173+
174+
const handleSubmit = () => {
175+
let isSuccess = true;
176+
if (inviteeEmail.trim().length === 0) {
177+
setErrorMessages((state) => ({ ...state, inviteeEmail: 'Email is Required' }));
178+
isSuccess = false;
179+
}
180+
if (!EMAIL_REGEXP.test(inviteeEmail)) {
181+
setErrorMessages((state) => ({ ...state, inviteeEmail: 'Email is invalid' }));
182+
isSuccess = false;
183+
}
184+
if (isSuccess) handleInvite();
185+
};
186+
187+
const isSendButtonDisabled = !inviteeEmail;
188+
189+
const handleInvite = async () => {
190+
setLoading(true);
191+
const inviteeName = inviteeFirstName + ' ' + inviteeLastName;
192+
193+
try {
194+
await userInvite({
195+
userInvite: {
196+
first_name: inviteeFirstName,
197+
last_name: inviteeLastName,
198+
email: inviteeEmail,
199+
roles: roles,
200+
org_name: organization.name,
201+
org_roles: orgRoles,
202+
teams: teams
203+
},
204+
orgId: organization.id
205+
}).unwrap();
206+
207+
handleSuccess(`Invite send to ${inviteeName.trim() === '' ? inviteeEmail : inviteeName}.`);
208+
} catch (e) {
209+
console.debug('cannot send user invite', e);
210+
handleError(`Invitation to ${inviteeFirstName} ${inviteeLastName} failed.`);
211+
}
212+
setInviteModal(false);
213+
setLoading(false);
214+
setInviteeFirstName('');
215+
setInviteeLastName('');
216+
setInviteeEmail('');
217+
setOrganization(defaultOrgSelection);
218+
setTeams([]);
219+
setRoles([]);
220+
setOrgRoles([]);
221+
};
222+
223+
const handleOrgChange = (event: { target: { value: string } }) => {
224+
if (event.target.value === 'none') {
225+
setOrganization(defaultOrgSelection);
226+
return;
227+
}
228+
const selectedOrg = orgs?.find((org: any) => org.id === event.target.value);
229+
if (selectedOrg) setOrganization(selectedOrg);
230+
};
231+
232+
let helpText = `Create a new user account and email new user with account setup instructions. Optionally, add the new user to one or more of your organizations and one or more teams.[Learn more about inviting users](https://docs.layer5.io/cloud/identity/users/user-management/).`;
233+
if (isAssignUserRolesAllowed) {
234+
helpText += ` Optionally, assign roles.`;
235+
}
236+
237+
return (
238+
<>
239+
{orgs && (
240+
<Modal
241+
open={open}
242+
closeModal={handleInviteModalClose}
243+
title={'Invite User'}
244+
headerIcon={<InviteUserIcon height="32" width="32" fill={theme.palette.common.white} />}
245+
>
246+
<ModalBody>
247+
<>
248+
<div
249+
style={{
250+
display: 'flex',
251+
justifyContent: 'space-between',
252+
marginBottom: '1rem'
253+
}}
254+
>
255+
<CreateUserInputField
256+
id="first-name"
257+
label="First Name"
258+
variant="outlined"
259+
value={inviteeFirstName}
260+
onChange={handleInviteeFirstNameChange}
261+
sx={{ width: '48% !important' }}
262+
/>
263+
264+
<CreateUserInputField
265+
id="last-name"
266+
label="Last Name"
267+
variant="outlined"
268+
value={inviteeLastName}
269+
onChange={handleInviteeLastNameChange}
270+
sx={{ width: '48% !important' }}
271+
/>
272+
</div>
273+
274+
<CreateUserInputField
275+
id="email"
276+
label="Email"
277+
variant="outlined"
278+
value={inviteeEmail}
279+
onChange={handleInviteeEmailChange}
280+
helperText={errorMessages.inviteeEmail}
281+
error={!!errorMessages.inviteeEmail}
282+
sx={{ width: '100% !important' }}
283+
required={true}
284+
/>
285+
<FormControl fullWidth sx={{ marginTop: '1rem', marginBottom: '1rem' }}>
286+
<InputLabel id="org-select-label">Organization</InputLabel>
287+
<Select
288+
labelId="org-select-label"
289+
id="outlined-org-select"
290+
value={organization}
291+
label="Organization Name"
292+
onChange={handleOrgChange}
293+
renderValue={(org: any) => org?.name}
294+
>
295+
<MenuItem key={defaultOrgSelection?.id} value={defaultOrgSelection?.id}>
296+
{defaultOrgSelection?.name}
297+
</MenuItem>
298+
{orgs.map((org: Organization) => (
299+
<MenuItem key={org.id} value={org.id}>
300+
{org.name}
301+
</MenuItem>
302+
))}
303+
</Select>
304+
</FormControl>
305+
306+
<TeamSearchField
307+
label={'Team(s)'}
308+
teamsData={teams}
309+
setTeamsData={setTeams}
310+
orgID={organization?.id === 'none' ? '' : organization?.id}
311+
disabled={organization.id == 'none'}
312+
useLazyGetTeamsQuery={useLazyGetTeamsQuery}
313+
useNotificationHandlers={useNotificationHandlers}
314+
/>
315+
316+
<FormControlSelect sx={{ marginTop: '1rem' }} disabled={organization.id == 'none'}>
317+
<InputLabel id="roles">Organization Roles</InputLabel>
318+
<Select
319+
disabled={organization.id == 'none'}
320+
labelId="roles"
321+
id="multiple-checkbox"
322+
multiple
323+
label="Organization Roles"
324+
value={orgRoles}
325+
onChange={handleOrgRoleChange}
326+
input={<OutlinedInput id="select-multiple-chip" label="Roles" />}
327+
renderValue={(selected) => (
328+
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
329+
{selected.map((value) => (
330+
<Chip key={value} label={value} />
331+
))}
332+
</Box>
333+
)}
334+
>
335+
{availableOrgRoles.map((name) => (
336+
<MenuItem key={name} value={name} style={getSelectStyle(name, roles, theme)}>
337+
{name}
338+
</MenuItem>
339+
))}
340+
</Select>
341+
</FormControlSelect>
342+
343+
{isAssignUserRolesAllowed && (
344+
<FormControlSelect sx={{ marginTop: '1rem' }}>
345+
<InputLabel id="roles">Provider Roles</InputLabel>
346+
<Select
347+
labelId="roles"
348+
id="multiple-checkbox"
349+
multiple
350+
label="Provider Roles"
351+
value={roles}
352+
onChange={handleRoleChange}
353+
input={<OutlinedInput id="select-multiple-chip" label="Provider Roles" />}
354+
renderValue={(selected) => (
355+
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
356+
{selected.map((value) => (
357+
<Chip key={value} label={value} />
358+
))}
359+
</Box>
360+
)}
361+
>
362+
{availableProviderRoles.map((name) => (
363+
<MenuItem key={name} value={name} style={getSelectStyle(name, roles, theme)}>
364+
{name}
365+
</MenuItem>
366+
))}
367+
</Select>
368+
</FormControlSelect>
369+
)}
370+
</>
371+
</ModalBody>
372+
<ModalFooter helpText={helpText} variant="filled">
373+
<PrimaryActionButtons
374+
primaryText="Send Invite"
375+
secondaryText="Cancel"
376+
primaryButtonProps={{
377+
onClick: handleSubmit,
378+
disabled: isSendButtonDisabled
379+
}}
380+
secondaryButtonProps={{
381+
onClick: handleInviteModalClose
382+
}}
383+
/>
384+
</ModalFooter>
385+
</Modal>
386+
)}
387+
</>
388+
);
389+
}

0 commit comments

Comments
 (0)