@vtex/faststore-plugin-buyer-portal 1.3.45 → 1.3.47

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/CHANGELOG.md +15 -1
  2. package/package.json +1 -1
  3. package/public/buyer-portal-icons.svg +35 -13
  4. package/src/features/org-units/clients/OrgUnitClient.ts +17 -0
  5. package/src/features/org-units/components/AddAllToOrgUnitDropdown/AddAllToOrgUnitDropdown.tsx +5 -2
  6. package/src/features/org-units/components/AuthSetupDrawer/AuthSetupDrawer.tsx +346 -0
  7. package/src/features/org-units/components/AuthSetupDrawer/auth-setup-drawer.scss +138 -0
  8. package/src/features/org-units/components/AuthSetupDrawer/index.ts +1 -0
  9. package/src/features/org-units/components/DeleteOrgUnitDrawer/DeleteOrgUnitDrawer.tsx +30 -5
  10. package/src/features/org-units/components/OrgUnitDetailsDropdownMenu/OrgUnitDetailsDropdownMenu.tsx +80 -0
  11. package/src/features/org-units/components/OrgUnitDetailsDropdownMenu/index.ts +4 -0
  12. package/src/features/org-units/components/OrgUnitsDropdownMenu/OrgUnitsDropdownMenu.tsx +5 -2
  13. package/src/features/org-units/components/index.ts +8 -0
  14. package/src/features/org-units/hooks/index.ts +2 -0
  15. package/src/features/org-units/hooks/useGetOrgUnitSettings.ts +20 -0
  16. package/src/features/org-units/hooks/useUpdateOrgUnitSettings.ts +27 -0
  17. package/src/features/org-units/layouts/OrgUnitDetailsLayout/OrgUnitDetailsLayout.tsx +23 -1
  18. package/src/features/org-units/layouts/OrgUnitDetailsLayout/org-units-details.scss +1 -0
  19. package/src/features/org-units/services/get-org-unit-settings.service.ts +13 -0
  20. package/src/features/org-units/services/index.ts +8 -0
  21. package/src/features/org-units/services/update-org-unit-settings.service.ts +17 -0
  22. package/src/features/org-units/types/OrgUnitSettings.ts +25 -0
  23. package/src/features/org-units/types/index.ts +2 -0
  24. package/src/features/shared/components/BuyerPortalProvider/BuyerPortalProvider.tsx +5 -0
  25. package/src/features/shared/components/Toast/Toast.tsx +43 -2
  26. package/src/features/shared/components/Toast/toast.scss +23 -5
  27. package/src/features/shared/components/index.ts +1 -0
  28. package/src/features/shared/layouts/LoadingTabsLayout/LoadingTabsLayout.tsx +13 -0
  29. package/src/features/shared/utils/constants.ts +2 -2
  30. package/src/features/shared/utils/withBuyerPortal.tsx +4 -1
  31. package/src/features/users/clients/UsersClient.ts +105 -4
  32. package/src/features/users/components/CreateUserDrawer/CreateUserDrawer.tsx +1 -1
  33. package/src/features/users/components/CreateUserDrawerSelector/CreateUserDrawerSelector.tsx +20 -0
  34. package/src/features/users/components/CreateUserDrawerWithUsername/CreateUserDrawerWithUsername.tsx +696 -0
  35. package/src/features/users/components/CreateUserDrawerWithUsername/create-user-drawer-with-username.scss +116 -0
  36. package/src/features/users/components/UserDropdownMenu/user-dropdown-menu.scss +1 -0
  37. package/src/features/users/components/UsersCard/UsersCard.tsx +2 -2
  38. package/src/features/users/components/index.ts +5 -0
  39. package/src/features/users/hooks/index.ts +2 -0
  40. package/src/features/users/hooks/useAddUserToOrgUnit.ts +12 -5
  41. package/src/features/users/hooks/useResetPassword.ts +39 -0
  42. package/src/features/users/hooks/useValidateUsername.ts +38 -0
  43. package/src/features/users/layouts/UserDetailsLayout/UserDetailsLayout.tsx +10 -0
  44. package/src/features/users/layouts/UsersLayout/UsersLayout.tsx +55 -10
  45. package/src/features/users/layouts/UsersLayout/users-layout.scss +19 -0
  46. package/src/features/users/services/add-user-to-org-unit.service.ts +8 -6
  47. package/src/features/users/services/get-users-by-org-unit-id.service.ts +1 -0
  48. package/src/features/users/services/index.ts +10 -0
  49. package/src/features/users/services/reset-password.service.ts +24 -0
  50. package/src/features/users/services/validate-username.service.ts +25 -0
  51. package/src/features/users/types/UserData.ts +1 -0
  52. package/src/features/users/types/UserDataService.ts +1 -0
  53. package/src/pages/org-unit-details.tsx +20 -2
@@ -0,0 +1,696 @@
1
+ import { useCallback, useEffect, useState } from "react";
2
+
3
+ import { useRouter } from "next/router";
4
+
5
+ import { Button, IconButton, useUI } from "@faststore/ui";
6
+
7
+ // TODO: Uncomment when 2FA settings are ready
8
+ // import { useGetOrgUnitSettings } from "../../../org-units/hooks";
9
+ import {
10
+ type BasicDrawerProps,
11
+ BasicDrawer,
12
+ ErrorMessage,
13
+ Icon,
14
+ InputText,
15
+ } from "../../../shared/components";
16
+ import { useAnalytics, useBuyerPortal } from "../../../shared/hooks";
17
+ import { ANALYTICS_EVENTS } from "../../../shared/services/logger/analytics/constants";
18
+ import { buyerPortalRoutes } from "../../../shared/utils/buyerPortalRoutes";
19
+ import { maskPhoneNumber } from "../../../shared/utils/phoneNumber";
20
+ import {
21
+ useAddUserToOrgUnit,
22
+ useResetPassword,
23
+ useValidateUsername,
24
+ } from "../../hooks";
25
+
26
+ import type { RoleData } from "../../../roles/types";
27
+
28
+ type DrawerState =
29
+ | "CREATE_USER"
30
+ | "CONFIRM_SHARED_EMAIL"
31
+ | "ACCESS_TOKEN_DISPLAY";
32
+
33
+ export type CreateUserDrawerProps = Omit<BasicDrawerProps, "children"> & {
34
+ onCreate?: () => void;
35
+ orgUnit: { id: string; name: string };
36
+ rolesOptions: RoleData[] | null;
37
+ };
38
+
39
+ const generateUsernameSuggestion = (email: string): string => {
40
+ if (!email || !email.includes("@")) return "";
41
+ return email.split("@")[0];
42
+ };
43
+
44
+ export const CreateUserDrawerWithUsername = ({
45
+ close,
46
+ onCreate,
47
+ orgUnit: { id: orgUnitId },
48
+ rolesOptions,
49
+ ...props
50
+ }: CreateUserDrawerProps) => {
51
+ const { pushToast } = useUI();
52
+ const router = useRouter();
53
+ const { featureFlags } = useBuyerPortal();
54
+ const { trackEntityCreated, trackEntityCreateError } = useAnalytics({
55
+ entityType: "user",
56
+ defaultTimerName: "user_creation",
57
+ shouldTrackDefaultTimer: true,
58
+ });
59
+
60
+ // TODO[2FA]: Uncomment when 2FA settings are ready
61
+ // const { settings } = useGetOrgUnitSettings(orgUnitId);
62
+
63
+ const [drawerState, setDrawerState] = useState<DrawerState>("CREATE_USER");
64
+ const [accessToken, setAccessToken] = useState<string>("");
65
+ const [hasTokenBeenCopied, setHasTokenBeenCopied] = useState(false);
66
+
67
+ const [sharedEmailData, setSharedEmailData] = useState<{
68
+ email: string;
69
+ existingUserName: string;
70
+ }>({
71
+ email: "",
72
+ existingUserName: "",
73
+ });
74
+
75
+ const [form, setForm] = useState<{
76
+ name: string;
77
+ userName: string;
78
+ email: string;
79
+ phone: string;
80
+ roles: number[];
81
+ }>({
82
+ name: "",
83
+ userName: "",
84
+ email: "",
85
+ phone: "",
86
+ roles: [],
87
+ });
88
+
89
+ const [userNameSuggestions, setUsernameSuggestions] = useState<string[]>([]);
90
+ const [showUsernameSuggestions, setShowUsernameSuggestions] = useState(false);
91
+ const [isUsernameValid, setIsUsernameValid] = useState<boolean | null>(null);
92
+
93
+ const [isTouched, setIsTouched] = useState(false);
94
+ const [isUsernameTouched, setIsUsernameTouched] = useState(false);
95
+
96
+ const { name, userName, email, roles, phone } = form;
97
+
98
+ const updateField = (
99
+ field: keyof typeof form,
100
+ value: string | string[] | number[]
101
+ ) => {
102
+ setForm((prev) => ({
103
+ ...prev,
104
+ [field]: value,
105
+ }));
106
+
107
+ if (field === "userName") {
108
+ setIsUsernameValid(null);
109
+ }
110
+ };
111
+
112
+ const handleValidateUsernameSuccess = useCallback(
113
+ (data: { valid: boolean; userNameSuggestions?: string[] }) => {
114
+ if (data.valid) {
115
+ setIsUsernameValid(true);
116
+ setShowUsernameSuggestions(false);
117
+ } else {
118
+ setIsUsernameValid(false);
119
+ if (data.userNameSuggestions && data.userNameSuggestions.length > 0) {
120
+ setUsernameSuggestions(data.userNameSuggestions);
121
+ setShowUsernameSuggestions(true);
122
+ }
123
+ }
124
+ },
125
+ []
126
+ );
127
+
128
+ const handleValidateUsernameError = useCallback(() => {
129
+ setIsUsernameValid(false);
130
+ }, []);
131
+
132
+ const { validateUsername, isValidateUsernameLoading } = useValidateUsername({
133
+ onSuccess: handleValidateUsernameSuccess,
134
+ onError: handleValidateUsernameError,
135
+ });
136
+
137
+ const handleResetPasswordSuccess = useCallback(
138
+ (data: { accessCode: string }) => {
139
+ setAccessToken(data.accessCode);
140
+ setDrawerState("ACCESS_TOKEN_DISPLAY");
141
+ },
142
+ []
143
+ );
144
+
145
+ const handleResetPasswordError = useCallback(() => {
146
+ pushToast({
147
+ message:
148
+ "Failed to generate access code. Please try resetting the password later.",
149
+ status: "ERROR",
150
+ });
151
+ onCreate?.();
152
+ close();
153
+ }, [pushToast, onCreate, close]);
154
+
155
+ const { resetPassword, isResetPasswordLoading } = useResetPassword({
156
+ onSuccess: handleResetPasswordSuccess,
157
+ onError: handleResetPasswordError,
158
+ });
159
+
160
+ useEffect(() => {
161
+ if (email && email.includes("@") && !userName) {
162
+ const suggestion = generateUsernameSuggestion(email);
163
+ if (suggestion) {
164
+ updateField("userName", suggestion);
165
+ }
166
+ }
167
+ }, [email]);
168
+
169
+ const handleUsernameBlur = useCallback(() => {
170
+ setIsUsernameTouched(true);
171
+ if (userName?.trim()) {
172
+ validateUsername({
173
+ orgUnitId,
174
+ userName: userName.trim(),
175
+ });
176
+ }
177
+ }, [userName, orgUnitId, validateUsername]);
178
+
179
+ const handleRefreshUsernameSuggestions = useCallback(() => {
180
+ if (userName?.trim()) {
181
+ validateUsername({
182
+ orgUnitId,
183
+ userName: userName.trim(),
184
+ });
185
+ }
186
+ }, [userName, orgUnitId, validateUsername]);
187
+
188
+ const handleSelectUsernameSuggestion = (suggestion: string) => {
189
+ updateField("userName", suggestion);
190
+ setShowUsernameSuggestions(false);
191
+ setIsUsernameValid(null);
192
+ validateUsername({
193
+ orgUnitId,
194
+ userName: suggestion,
195
+ });
196
+ };
197
+
198
+ const handleAddUserSuccess = ({ user }: { user: { id: string } }) => {
199
+ trackEntityCreated(ANALYTICS_EVENTS.USER_CREATED, "user", {
200
+ user_name: name,
201
+ user_userName: userName,
202
+ user_email: email,
203
+ user_phone: phone,
204
+ roles_count: roles.length,
205
+ role_ids: roles,
206
+ org_unit_id: orgUnitId,
207
+ });
208
+
209
+ const shouldResetPassword =
210
+ !email?.trim() || drawerState === "CONFIRM_SHARED_EMAIL";
211
+
212
+ if (shouldResetPassword) {
213
+ resetPassword({
214
+ orgUnitId,
215
+ userId: user.id,
216
+ });
217
+ return;
218
+ }
219
+
220
+ pushToast({
221
+ title: "User added successfully",
222
+ message: `An access code has been sent to [${email}](mailto:${email}) for first-time login.`,
223
+ status: "INFO",
224
+ icon: (
225
+ <button
226
+ data-fs-bp-toast-view-button
227
+ type="button"
228
+ onClick={() => {
229
+ window.location.href = buyerPortalRoutes.userDetails({
230
+ userId: user.id,
231
+ orgUnitId: orgUnitId,
232
+ });
233
+ }}
234
+ >
235
+ View
236
+ </button>
237
+ ),
238
+ });
239
+ onCreate?.();
240
+ close();
241
+ };
242
+
243
+ const { addUserToOrgUnit, isAddUserToOrgUnitLoading } = useAddUserToOrgUnit({
244
+ enableUsernameCreation: featureFlags?.enableUsernameCreation,
245
+ onSuccess: handleAddUserSuccess,
246
+ onError: (err) => {
247
+ let error: {
248
+ message: string;
249
+ orgUnit?: string;
250
+ code?: string;
251
+ user?: {
252
+ id: string;
253
+ email: string;
254
+ name: string;
255
+ };
256
+ existingUserName?: string;
257
+ };
258
+
259
+ try {
260
+ error = JSON.parse(err.message);
261
+ } catch {
262
+ error = { message: err.message };
263
+ }
264
+
265
+ trackEntityCreateError(ANALYTICS_EVENTS.USER_CREATE_ERROR, err, {
266
+ org_unit_id: orgUnitId,
267
+ error_type: error.code || "unknown",
268
+ });
269
+
270
+ if (error.code === "EMAIL_ALREADY_EXISTS") {
271
+ setSharedEmailData({
272
+ email: email,
273
+ existingUserName: error.existingUserName || "another user",
274
+ });
275
+ setDrawerState("CONFIRM_SHARED_EMAIL");
276
+ return;
277
+ }
278
+
279
+ if (error.code === "USERNAME_ALREADY_EXISTS") {
280
+ setIsUsernameValid(false);
281
+ handleRefreshUsernameSuggestions();
282
+ pushToast({
283
+ message: "Username is already taken. Please choose another one.",
284
+ status: "ERROR",
285
+ });
286
+ return;
287
+ }
288
+
289
+ pushToast({
290
+ message: error.message || "An error occurred while creating the user",
291
+ status: "ERROR",
292
+ });
293
+ },
294
+ });
295
+
296
+ // TODO[2FA]: Uncomment when 2FA settings are ready
297
+ // const is2FAEnabled = settings?.["2FA"]?.verificationCode ?? false;
298
+
299
+ const isFormValid = () => {
300
+ if (!userName?.trim()) return false;
301
+
302
+ if (roles.length === 0) return false;
303
+
304
+ if (isUsernameValid === false) return false;
305
+
306
+ // TODO[2FA]: Uncomment when 2FA settings are ready
307
+ // if (is2FAEnabled && !email?.trim() && !phone?.trim()) return false;
308
+
309
+ return true;
310
+ };
311
+
312
+ const handleConfirmClick = () => {
313
+ setIsTouched(true);
314
+ setIsUsernameTouched(true);
315
+
316
+ if (!isFormValid()) {
317
+ return;
318
+ }
319
+
320
+ if (isUsernameValid === null && userName?.trim()) {
321
+ validateUsername({
322
+ orgUnitId,
323
+ userName: userName.trim(),
324
+ });
325
+ return;
326
+ }
327
+
328
+ addUserToOrgUnit({
329
+ name,
330
+ userName: userName,
331
+ email: email || undefined,
332
+ phone: phone || undefined,
333
+ roles,
334
+ orgUnitId,
335
+ });
336
+ };
337
+
338
+ const handleConfirmSharedEmail = () => {
339
+ addUserToOrgUnit({
340
+ name,
341
+ userName: userName,
342
+ email,
343
+ phone: phone || undefined,
344
+ roles,
345
+ orgUnitId,
346
+ });
347
+ };
348
+
349
+ const handleCopyAccessToken = async () => {
350
+ try {
351
+ await navigator.clipboard.writeText(accessToken);
352
+ setHasTokenBeenCopied(true);
353
+ pushToast({
354
+ message: "Access code copied to clipboard",
355
+ status: "INFO",
356
+ });
357
+ } catch {
358
+ pushToast({
359
+ message: "Failed to copy access code",
360
+ status: "ERROR",
361
+ });
362
+ }
363
+ };
364
+
365
+ const handleCloseAccessTokenModal = () => {
366
+ if (!hasTokenBeenCopied) {
367
+ return;
368
+ }
369
+ onCreate?.();
370
+ close();
371
+ };
372
+
373
+ const isConfirmButtonEnabled =
374
+ isFormValid() && !isAddUserToOrgUnitLoading && isUsernameValid !== false;
375
+
376
+ const backToCreateUser = () => {
377
+ setDrawerState("CREATE_USER");
378
+ setIsTouched(false);
379
+ setIsUsernameTouched(false);
380
+ setForm({
381
+ name: "",
382
+ userName: "",
383
+ email: "",
384
+ phone: "",
385
+ roles: [],
386
+ });
387
+ setIsUsernameValid(null);
388
+ setUsernameSuggestions([]);
389
+ setShowUsernameSuggestions(false);
390
+ };
391
+
392
+ const hasRolesError = !rolesOptions || rolesOptions.length === 0;
393
+
394
+ const handleRetryRoles = () => {
395
+ router.reload();
396
+ };
397
+
398
+ const getDrawerTitle = () => {
399
+ switch (drawerState) {
400
+ case "CONFIRM_SHARED_EMAIL":
401
+ return (
402
+ <>
403
+ <button type="button" onClick={backToCreateUser}>
404
+ <Icon
405
+ name="Back"
406
+ height={20}
407
+ width={20}
408
+ data-fs-bp-create-user-drawer-back-icon
409
+ />
410
+ </button>
411
+ Confirm shared email
412
+ </>
413
+ );
414
+ case "ACCESS_TOKEN_DISPLAY":
415
+ return "";
416
+ default:
417
+ return "Add User";
418
+ }
419
+ };
420
+
421
+ return (
422
+ <BasicDrawer data-fs-bp-create-user-drawer close={close} {...props}>
423
+ <BasicDrawer.Heading title={getDrawerTitle()} onClose={close} />
424
+
425
+ {drawerState === "CREATE_USER" && (
426
+ <>
427
+ <BasicDrawer.Body>
428
+ {hasRolesError ? (
429
+ <div data-fs-bp-create-user-error-state>
430
+ <Icon name="Warning" height={48} width={48} />
431
+ <h3>Something went wrong</h3>
432
+ <p>The add user form could not be loaded.</p>
433
+ <Button variant="secondary" onClick={handleRetryRoles}>
434
+ Try again
435
+ </Button>
436
+ </div>
437
+ ) : (
438
+ <>
439
+ {/* TODO: Uncomment when 2FA settings are ready
440
+ {settings?.["2FA"]?.verificationCode && (
441
+ <div data-fs-bp-create-user-2fa-disclaimer>
442
+ <Icon name="2FAEnabled" height={13} width={16} />
443
+ <p>
444
+ Two-factor authentication is enabled. All users must have
445
+ an email or phone number registered to authenticate.
446
+ </p>
447
+ </div>
448
+ )}
449
+ */}
450
+
451
+ <InputText
452
+ label="Full Name (optional)"
453
+ value={name}
454
+ onChange={(event) => updateField("name", event.target.value)}
455
+ />
456
+
457
+ <InputText
458
+ // TODO[2FA]: Uncomment when 2FA settings are ready
459
+ // label={is2FAEnabled ? "Email" : "Email (optional)"}
460
+ // hasError={is2FAEnabled && isTouched && !email?.trim() && !phone?.trim()}
461
+ label="Email (optional)"
462
+ value={email}
463
+ wrapperProps={{ style: { marginTop: 16 } }}
464
+ onChange={(event) => updateField("email", event.target.value)}
465
+ />
466
+
467
+ <InputText
468
+ // TODO[2FA]: Uncomment when 2FA settings are ready
469
+ // label={is2FAEnabled ? "Phone number" : "Phone number (optional)"}
470
+ // hasError={is2FAEnabled && isTouched && !email?.trim() && !phone?.trim()}
471
+ label="Phone number (optional)"
472
+ value={phone}
473
+ wrapperProps={{ style: { marginTop: 16 } }}
474
+ onChange={(event) =>
475
+ // TODO: Update this when implementing i18n
476
+ updateField(
477
+ "phone",
478
+ maskPhoneNumber(event.target.value, "USA")
479
+ )
480
+ }
481
+ />
482
+
483
+ {/* TODO: Uncomment when 2FA settings are ready
484
+ <ErrorMessage
485
+ show={
486
+ is2FAEnabled &&
487
+ isTouched &&
488
+ !email?.trim() &&
489
+ !phone?.trim()
490
+ }
491
+ message="Email or phone number is required for two-factor authentication"
492
+ />
493
+ */}
494
+
495
+ <div data-fs-bp-create-user-userName-wrapper>
496
+ <div data-fs-bp-create-user-userName-suggestions-wrapper>
497
+ <InputText
498
+ label="Username"
499
+ value={userName}
500
+ hasError={
501
+ (isUsernameTouched && !userName?.trim()) ||
502
+ isUsernameValid === false
503
+ }
504
+ onChange={(event) =>
505
+ updateField("userName", event.target.value)
506
+ }
507
+ onBlur={handleUsernameBlur}
508
+ />
509
+
510
+ {showUsernameSuggestions &&
511
+ userNameSuggestions.length > 0 && (
512
+ <IconButton
513
+ data-fs-bp-create-user-userName-suggestions-icon
514
+ icon={<Icon name="Refresh" width={20} height={20} />}
515
+ size="small"
516
+ aria-label={"refresh userName suggestions"}
517
+ onClick={() => {
518
+ handleRefreshUsernameSuggestions();
519
+ }}
520
+ />
521
+ )}
522
+ </div>
523
+
524
+ {showUsernameSuggestions &&
525
+ userNameSuggestions.length > 0 && (
526
+ <div data-fs-bp-create-user-userName-suggestions>
527
+ <ul>
528
+ {userNameSuggestions.map((suggestion) => (
529
+ <li key={suggestion}>
530
+ <button
531
+ type="button"
532
+ onClick={() =>
533
+ handleSelectUsernameSuggestion(suggestion)
534
+ }
535
+ >
536
+ {suggestion}
537
+ </button>
538
+ </li>
539
+ ))}
540
+ </ul>
541
+ </div>
542
+ )}
543
+ </div>
544
+
545
+ <ErrorMessage
546
+ show={isUsernameTouched && !userName?.trim()}
547
+ message="Username is required"
548
+ />
549
+
550
+ <div data-fs-bp-create-user-roles>
551
+ <span data-fs-bp-create-user-roles-label>Roles</span>
552
+
553
+ {rolesOptions &&
554
+ rolesOptions.map((role) => {
555
+ const id = `role-${role.roleName
556
+ .toLowerCase()
557
+ .replace(" ", "-")}`;
558
+ return (
559
+ <span
560
+ data-fs-bp-create-user-role-wrapper
561
+ key={role.roleName}
562
+ >
563
+ <input
564
+ type="checkbox"
565
+ key={role.roleName}
566
+ value={role.roleName}
567
+ id={id}
568
+ checked={roles?.includes(role.roleId)}
569
+ onChange={(event) => {
570
+ const newRoles = event.target.checked
571
+ ? [...(roles ?? []), role.roleId]
572
+ : roles?.filter((r) => r !== role.roleId) ?? [];
573
+ updateField("roles", newRoles);
574
+ }}
575
+ />
576
+ <label htmlFor={id}>{role.roleName}</label>
577
+ </span>
578
+ );
579
+ })}
580
+
581
+ <ErrorMessage
582
+ show={isTouched && roles.length === 0}
583
+ message="At least one role is required"
584
+ />
585
+ </div>
586
+ </>
587
+ )}
588
+ </BasicDrawer.Body>
589
+ <BasicDrawer.Footer>
590
+ <BasicDrawer.Button variant="ghost" onClick={close}>
591
+ Cancel
592
+ </BasicDrawer.Button>
593
+ <BasicDrawer.Button
594
+ variant="confirm"
595
+ disabled={!isConfirmButtonEnabled || hasRolesError}
596
+ onClick={handleConfirmClick}
597
+ isLoading={
598
+ isAddUserToOrgUnitLoading ||
599
+ isValidateUsernameLoading ||
600
+ isResetPasswordLoading
601
+ }
602
+ >
603
+ Add
604
+ </BasicDrawer.Button>
605
+ </BasicDrawer.Footer>
606
+ </>
607
+ )}
608
+
609
+ {drawerState === "CONFIRM_SHARED_EMAIL" && (
610
+ <>
611
+ <BasicDrawer.Body data-fs-bp-create-user-shared-email-body>
612
+ <div data-fs-bp-create-user-shared-email-warning>
613
+ <Icon name="Warning" height={48} width={48} />
614
+ <h3>This email is already registered</h3>
615
+ </div>
616
+ <p>
617
+ The email <strong>{sharedEmailData.email}</strong> is already
618
+ registered for another user (
619
+ <strong>{sharedEmailData.existingUserName}</strong>).
620
+ </p>
621
+ <p>
622
+ Are you sure you want to continue? If you proceed, a temporary
623
+ access code will be generated for the new user.
624
+ </p>
625
+ </BasicDrawer.Body>
626
+ <BasicDrawer.Footer>
627
+ <BasicDrawer.Button variant="ghost" onClick={backToCreateUser}>
628
+ Cancel
629
+ </BasicDrawer.Button>
630
+ <BasicDrawer.Button
631
+ variant="confirm"
632
+ onClick={handleConfirmSharedEmail}
633
+ isLoading={isAddUserToOrgUnitLoading}
634
+ >
635
+ Continue
636
+ </BasicDrawer.Button>
637
+ </BasicDrawer.Footer>
638
+ </>
639
+ )}
640
+
641
+ {drawerState === "ACCESS_TOKEN_DISPLAY" && (
642
+ <>
643
+ <BasicDrawer.Body data-fs-bp-create-user-access-token-body>
644
+ <div data-fs-bp-create-user-access-token-success>
645
+ <Icon name="CheckSuccess" height={60} width={60} />
646
+
647
+ <h3>Password reset successfully</h3>
648
+ </div>
649
+
650
+ <div data-fs-bp-create-user-access-token-description>
651
+ <p>
652
+ A temporary access code has been generated. <br />
653
+ Share it with the user, as they have no individual email
654
+ registered.
655
+ <br />
656
+ <br />
657
+ The code expires in 12 hours. Copy it before closing this
658
+ window. Once closed, access will be lost and a password reset
659
+ will be required.
660
+ </p>
661
+ </div>
662
+
663
+ <div data-fs-bp-create-user-access-token-display>
664
+ <div data-fs-bp-create-user-access-token-value>
665
+ <span>{accessToken}</span>
666
+
667
+ <IconButton
668
+ icon={
669
+ <Icon
670
+ name={hasTokenBeenCopied ? "Check" : "Copy"}
671
+ height={20}
672
+ width={20}
673
+ />
674
+ }
675
+ size="small"
676
+ aria-label={hasTokenBeenCopied ? "Copied" : "Copy"}
677
+ onClick={handleCopyAccessToken}
678
+ data-fs-bp-create-user-copy-button
679
+ data-copied={hasTokenBeenCopied}
680
+ />
681
+ </div>
682
+ </div>
683
+ </BasicDrawer.Body>
684
+ <BasicDrawer.Footer>
685
+ <BasicDrawer.Button
686
+ variant="confirm"
687
+ onClick={handleCloseAccessTokenModal}
688
+ >
689
+ Done
690
+ </BasicDrawer.Button>
691
+ </BasicDrawer.Footer>
692
+ </>
693
+ )}
694
+ </BasicDrawer>
695
+ );
696
+ };