@visns-studio/visns-components 6.3.17 → 6.3.18
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.
- package/package.json +1 -1
- package/src/components/auth/Profile.jsx +315 -192
- package/src/components/styles/Profile.module.scss +436 -396
package/package.json
CHANGED
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
"react-dom": "^17.0.0 || ^18.0.0"
|
|
92
92
|
},
|
|
93
93
|
"name": "@visns-studio/visns-components",
|
|
94
|
-
"version": "6.3.
|
|
94
|
+
"version": "6.3.18",
|
|
95
95
|
"description": "Various packages to assist in the development of our Custom Applications.",
|
|
96
96
|
"main": "src/index.js",
|
|
97
97
|
"files": [
|
|
@@ -11,7 +11,18 @@ import CustomFetch from '../Fetch';
|
|
|
11
11
|
import TableFilter from '../TableFilter';
|
|
12
12
|
import styles from '../styles/Profile.module.scss';
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
/**
|
|
15
|
+
* One labelled control.
|
|
16
|
+
*
|
|
17
|
+
* The label used to be a `<span>` absolutely positioned inside the box, which
|
|
18
|
+
* is why this component carried two copies of the same `<input>`: the floating
|
|
19
|
+
* treatment needed the span after the input for text fields and before it for
|
|
20
|
+
* passwords, which start in the raised state. A label above the box needs
|
|
21
|
+
* neither trick, so there is one input, in one order, and it is addressable by
|
|
22
|
+
* a real `htmlFor`.
|
|
23
|
+
*
|
|
24
|
+
* Every prop, and the value each of them carries, is unchanged.
|
|
25
|
+
*/
|
|
15
26
|
const FormField = memo(
|
|
16
27
|
({
|
|
17
28
|
type = 'text',
|
|
@@ -23,39 +34,23 @@ const FormField = memo(
|
|
|
23
34
|
error = '',
|
|
24
35
|
children,
|
|
25
36
|
}) => {
|
|
26
|
-
const
|
|
37
|
+
const id = `profile-${name}`;
|
|
27
38
|
|
|
28
39
|
return (
|
|
29
40
|
<div className={styles.formItem}>
|
|
30
|
-
<label className={styles.
|
|
31
|
-
{
|
|
32
|
-
<input
|
|
33
|
-
type={type}
|
|
34
|
-
name={name}
|
|
35
|
-
onChange={onChange}
|
|
36
|
-
className={`${styles.input} ${error}`}
|
|
37
|
-
value={value}
|
|
38
|
-
readOnly={readOnly}
|
|
39
|
-
/>
|
|
40
|
-
)}
|
|
41
|
-
<span
|
|
42
|
-
className={`${styles.fi__span} ${
|
|
43
|
-
isPrefocused ? styles.prefocus : ''
|
|
44
|
-
}`}
|
|
45
|
-
>
|
|
46
|
-
{label}
|
|
47
|
-
</span>
|
|
48
|
-
{isPrefocused && (
|
|
49
|
-
<input
|
|
50
|
-
type={type}
|
|
51
|
-
name={name}
|
|
52
|
-
onChange={onChange}
|
|
53
|
-
value={value}
|
|
54
|
-
className={`${styles.input} ${error}`}
|
|
55
|
-
/>
|
|
56
|
-
)}
|
|
57
|
-
{children}
|
|
41
|
+
<label className={styles.fieldLabel} htmlFor={id}>
|
|
42
|
+
{label}
|
|
58
43
|
</label>
|
|
44
|
+
<input
|
|
45
|
+
id={id}
|
|
46
|
+
type={type}
|
|
47
|
+
name={name}
|
|
48
|
+
onChange={onChange}
|
|
49
|
+
className={`${styles.input} ${error}`}
|
|
50
|
+
value={value}
|
|
51
|
+
readOnly={readOnly}
|
|
52
|
+
/>
|
|
53
|
+
{children}
|
|
59
54
|
</div>
|
|
60
55
|
);
|
|
61
56
|
}
|
|
@@ -451,69 +446,143 @@ const Profile = ({ userProfile, setUserProfile }) => {
|
|
|
451
446
|
<div className={styles.gridtxt__header}>
|
|
452
447
|
<span>Overview</span>
|
|
453
448
|
</div>
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
449
|
+
{/* Three decisions, three sections, rather than one
|
|
450
|
+
flat run of fields: what the account is called,
|
|
451
|
+
whether to change the password, and what the
|
|
452
|
+
signature looks like. Each says what it is on the
|
|
453
|
+
left and carries only its own controls on the
|
|
454
|
+
right. */}
|
|
455
|
+
<form
|
|
456
|
+
className={styles.form}
|
|
457
|
+
onSubmit={handleSubmitProfile}
|
|
458
|
+
>
|
|
459
|
+
<section className={styles.section}>
|
|
460
|
+
<div className={styles.sectionAside}>
|
|
461
|
+
<h3 className={styles.sectionTitle}>
|
|
462
|
+
Account
|
|
463
|
+
</h3>
|
|
464
|
+
<p className={styles.sectionDescription}>
|
|
465
|
+
The name shown beside your work. Your
|
|
466
|
+
sign-in address is set by an
|
|
467
|
+
administrator and cannot be edited
|
|
468
|
+
here.
|
|
469
|
+
</p>
|
|
470
|
+
</div>
|
|
471
|
+
<div className={styles.sectionFields}>
|
|
472
|
+
<div className={styles.fieldPair}>
|
|
473
|
+
<FormField
|
|
474
|
+
name="name"
|
|
475
|
+
value={profile.name}
|
|
476
|
+
onChange={handleChangeProfile}
|
|
477
|
+
label="Name *"
|
|
478
|
+
error={inputClasses.name}
|
|
479
|
+
/>
|
|
480
|
+
|
|
481
|
+
<FormField
|
|
482
|
+
name="email"
|
|
483
|
+
value={profile.email}
|
|
484
|
+
readOnly={true}
|
|
485
|
+
label="Email *"
|
|
486
|
+
error={inputClasses.email}
|
|
487
|
+
/>
|
|
488
|
+
</div>
|
|
489
|
+
</div>
|
|
490
|
+
</section>
|
|
491
|
+
|
|
492
|
+
<section className={styles.section}>
|
|
493
|
+
<div className={styles.sectionAside}>
|
|
494
|
+
<h3 className={styles.sectionTitle}>
|
|
495
|
+
Change password
|
|
496
|
+
</h3>
|
|
497
|
+
<p className={styles.sectionDescription}>
|
|
498
|
+
Leave both fields blank to keep your
|
|
499
|
+
current password — nothing is sent
|
|
500
|
+
unless you type a new one.
|
|
501
|
+
</p>
|
|
502
|
+
</div>
|
|
503
|
+
<div className={styles.sectionFields}>
|
|
504
|
+
<div className={styles.fieldPair}>
|
|
505
|
+
<FormField
|
|
506
|
+
type="password"
|
|
507
|
+
name="password"
|
|
508
|
+
value={profile.password}
|
|
509
|
+
onChange={handleChangeProfile}
|
|
510
|
+
label="Password"
|
|
511
|
+
error={inputClasses.password}
|
|
512
|
+
>
|
|
513
|
+
{/* Only once there is something to
|
|
514
|
+
measure. Rendered
|
|
515
|
+
unconditionally it sat under an
|
|
516
|
+
empty password box reporting
|
|
517
|
+
"too short" before the user had
|
|
518
|
+
typed a character. */}
|
|
519
|
+
{profile.password ? (
|
|
520
|
+
<PasswordStrengthBar
|
|
521
|
+
password={profile.password}
|
|
522
|
+
className={
|
|
523
|
+
styles.strengthMeter
|
|
524
|
+
}
|
|
525
|
+
scoreWordClassName={
|
|
526
|
+
styles.strengthWord
|
|
527
|
+
}
|
|
528
|
+
/>
|
|
529
|
+
) : null}
|
|
530
|
+
</FormField>
|
|
531
|
+
|
|
532
|
+
<FormField
|
|
533
|
+
type="password"
|
|
534
|
+
name="password_confirmation"
|
|
535
|
+
value={
|
|
536
|
+
profile.password_confirmation
|
|
537
|
+
}
|
|
538
|
+
onChange={handleChangeProfile}
|
|
539
|
+
label="Password Confirmation"
|
|
540
|
+
error={
|
|
541
|
+
inputClasses.password_confirmation
|
|
494
542
|
}
|
|
495
543
|
/>
|
|
496
|
-
|
|
497
|
-
</
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
544
|
+
</div>
|
|
545
|
+
</div>
|
|
546
|
+
</section>
|
|
547
|
+
|
|
548
|
+
<section className={styles.section}>
|
|
549
|
+
<div className={styles.sectionAside}>
|
|
550
|
+
<h3 className={styles.sectionTitle}>
|
|
551
|
+
Signature
|
|
552
|
+
</h3>
|
|
553
|
+
<p className={styles.sectionDescription}>
|
|
554
|
+
Applied to the documents generated for
|
|
555
|
+
you. Drawing a new one replaces the
|
|
556
|
+
signature held on your account when you
|
|
557
|
+
save.
|
|
558
|
+
</p>
|
|
559
|
+
</div>
|
|
560
|
+
<div className={styles.sectionFields}>
|
|
511
561
|
<div className={styles.signatureField}>
|
|
512
|
-
|
|
513
|
-
|
|
562
|
+
{/* Clear belongs to the pad, not to
|
|
563
|
+
the page: it sits on the caption's
|
|
564
|
+
line, quiet and small, instead of
|
|
565
|
+
standing beside Save at the bottom
|
|
566
|
+
of the screen. */}
|
|
567
|
+
<div
|
|
568
|
+
className={styles.signatureHeader}
|
|
514
569
|
>
|
|
515
|
-
|
|
516
|
-
|
|
570
|
+
<span
|
|
571
|
+
className={
|
|
572
|
+
styles.signatureCaption
|
|
573
|
+
}
|
|
574
|
+
>
|
|
575
|
+
Draw your signature
|
|
576
|
+
</span>
|
|
577
|
+
<button
|
|
578
|
+
className={styles.btnQuiet}
|
|
579
|
+
onClick={handleClearSignature}
|
|
580
|
+
disabled={isLoading}
|
|
581
|
+
type="button"
|
|
582
|
+
>
|
|
583
|
+
Clear
|
|
584
|
+
</button>
|
|
585
|
+
</div>
|
|
517
586
|
<div
|
|
518
587
|
className={
|
|
519
588
|
styles.signaturePadWrapper
|
|
@@ -521,6 +590,27 @@ const Profile = ({ userProfile, setUserProfile }) => {
|
|
|
521
590
|
>
|
|
522
591
|
<SignaturePad
|
|
523
592
|
ref={signatureRef}
|
|
593
|
+
/* Do not lower this, and do
|
|
594
|
+
not narrow
|
|
595
|
+
`--profile-signature-width`
|
|
596
|
+
either. The effect below
|
|
597
|
+
loads the stored signature
|
|
598
|
+
into the pad on mount, so
|
|
599
|
+
the pad is non-empty on an
|
|
600
|
+
ordinary save that never
|
|
601
|
+
touched it, and
|
|
602
|
+
`handleSubmitProfile` then
|
|
603
|
+
re-exports the canvas with
|
|
604
|
+
`toDataURL()` at whatever
|
|
605
|
+
size the canvas currently
|
|
606
|
+
is. A smaller pad therefore
|
|
607
|
+
writes a signature cropped
|
|
608
|
+
to the new geometry back to
|
|
609
|
+
the server — silently, on a
|
|
610
|
+
save the user made for some
|
|
611
|
+
other reason. Growing it is
|
|
612
|
+
safe; shrinking it destroys
|
|
613
|
+
ink. */
|
|
524
614
|
height={200}
|
|
525
615
|
options={{
|
|
526
616
|
penColor: 'black',
|
|
@@ -529,11 +619,16 @@ const Profile = ({ userProfile, setUserProfile }) => {
|
|
|
529
619
|
}}
|
|
530
620
|
redrawOnResize
|
|
531
621
|
canvasProps={{
|
|
532
|
-
/* The frame
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
622
|
+
/* The frame is on the
|
|
623
|
+
wrapper. A border or
|
|
624
|
+
padding on the canvas
|
|
625
|
+
itself sits inside the
|
|
626
|
+
bounding rect
|
|
627
|
+
signature_pad measures
|
|
628
|
+
against and outside the
|
|
629
|
+
drawing surface, which
|
|
630
|
+
offsets every stroke by
|
|
631
|
+
exactly that much. */
|
|
537
632
|
style: {
|
|
538
633
|
width: '100%',
|
|
539
634
|
},
|
|
@@ -541,26 +636,17 @@ const Profile = ({ userProfile, setUserProfile }) => {
|
|
|
541
636
|
}}
|
|
542
637
|
/>
|
|
543
638
|
</div>
|
|
544
|
-
<small
|
|
545
|
-
className={styles.signatureHint}
|
|
546
|
-
>
|
|
547
|
-
Draw your signature above
|
|
548
|
-
</small>
|
|
549
639
|
</div>
|
|
550
640
|
</div>
|
|
551
|
-
</
|
|
552
|
-
</
|
|
553
|
-
|
|
554
|
-
{/* The
|
|
641
|
+
</section>
|
|
642
|
+
</form>
|
|
643
|
+
|
|
644
|
+
{/* The panel's footer. Save stands alone now that
|
|
645
|
+
Clear lives with the pad it clears. It stays
|
|
646
|
+
outside the `<form>`, where it has always been — a
|
|
647
|
+
submit button inside it would fire
|
|
648
|
+
`handleSubmitProfile` twice. */}
|
|
555
649
|
<div className={styles.polActions}>
|
|
556
|
-
<button
|
|
557
|
-
className={`${styles.btn} ${styles.btnSecondary}`}
|
|
558
|
-
onClick={handleClearSignature}
|
|
559
|
-
disabled={isLoading}
|
|
560
|
-
type="button"
|
|
561
|
-
>
|
|
562
|
-
Clear Signature
|
|
563
|
-
</button>
|
|
564
650
|
<button
|
|
565
651
|
className={`${styles.btn} ${
|
|
566
652
|
isLoading ? styles.loading : ''
|
|
@@ -580,66 +666,97 @@ const Profile = ({ userProfile, setUserProfile }) => {
|
|
|
580
666
|
<div className={styles.gridtxt__header}>
|
|
581
667
|
<span>Two-Factor Authentication</span>
|
|
582
668
|
</div>
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
<span
|
|
593
|
-
className={styles.statusEnabled}
|
|
594
|
-
>
|
|
595
|
-
Enabled
|
|
596
|
-
</span>
|
|
597
|
-
) : (
|
|
598
|
-
<span
|
|
599
|
-
className={
|
|
600
|
-
styles.statusDisabled
|
|
601
|
-
}
|
|
602
|
-
>
|
|
603
|
-
Disabled
|
|
604
|
-
</span>
|
|
605
|
-
)}
|
|
669
|
+
{/* Same sectioning as Overview, so the two tabs read
|
|
670
|
+
as one screen: what 2FA is on the left, the state
|
|
671
|
+
of it and the control that changes that state on
|
|
672
|
+
the right. */}
|
|
673
|
+
<div className={styles.form}>
|
|
674
|
+
<section className={styles.section}>
|
|
675
|
+
<div className={styles.sectionAside}>
|
|
676
|
+
<h3 className={styles.sectionTitle}>
|
|
677
|
+
Authenticator app
|
|
606
678
|
</h3>
|
|
607
|
-
<p>
|
|
679
|
+
<p className={styles.sectionDescription}>
|
|
608
680
|
Two-factor authentication adds an extra
|
|
609
681
|
layer of security to your account. When
|
|
610
682
|
enabled, you'll need to provide both
|
|
611
683
|
your password and a verification code
|
|
612
|
-
from your authenticator app when
|
|
613
|
-
in.
|
|
684
|
+
from your authenticator app when
|
|
685
|
+
signing in.
|
|
614
686
|
</p>
|
|
687
|
+
</div>
|
|
688
|
+
<div className={styles.sectionFields}>
|
|
689
|
+
<div className={styles.twoFactorStatus}>
|
|
690
|
+
<h4>
|
|
691
|
+
Status:{' '}
|
|
692
|
+
{twoFactorEnabled ? (
|
|
693
|
+
<span
|
|
694
|
+
className={
|
|
695
|
+
styles.statusEnabled
|
|
696
|
+
}
|
|
697
|
+
>
|
|
698
|
+
Enabled
|
|
699
|
+
</span>
|
|
700
|
+
) : (
|
|
701
|
+
<span
|
|
702
|
+
className={
|
|
703
|
+
styles.statusDisabled
|
|
704
|
+
}
|
|
705
|
+
>
|
|
706
|
+
Disabled
|
|
707
|
+
</span>
|
|
708
|
+
)}
|
|
709
|
+
</h4>
|
|
615
710
|
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
711
|
+
{!twoFactorEnabled &&
|
|
712
|
+
!showTwoFactorSetup && (
|
|
713
|
+
<button
|
|
714
|
+
className={`${styles.btn} ${styles.btnPrimary}`}
|
|
715
|
+
onClick={
|
|
716
|
+
fetchTwoFactorSetupData
|
|
717
|
+
}
|
|
718
|
+
disabled={isLoading}
|
|
719
|
+
>
|
|
720
|
+
{isLoading
|
|
721
|
+
? 'Loading...'
|
|
722
|
+
: 'Set up two-factor authentication'}
|
|
723
|
+
</button>
|
|
724
|
+
)}
|
|
630
725
|
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
<p
|
|
726
|
+
{twoFactorEnabled &&
|
|
727
|
+
userProfile.two_factor_confirmed_at && (
|
|
728
|
+
<div
|
|
635
729
|
className={
|
|
636
|
-
styles.
|
|
730
|
+
styles.statusActions
|
|
637
731
|
}
|
|
638
732
|
>
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
733
|
+
<p
|
|
734
|
+
className={
|
|
735
|
+
styles.confirmedMessage
|
|
736
|
+
}
|
|
737
|
+
>
|
|
738
|
+
Two-factor
|
|
739
|
+
authentication is set up
|
|
740
|
+
and confirmed. Your
|
|
741
|
+
account is now more
|
|
742
|
+
secure.
|
|
743
|
+
</p>
|
|
744
|
+
<button
|
|
745
|
+
className={`${styles.btn} ${styles.btnDanger}`}
|
|
746
|
+
onClick={
|
|
747
|
+
disableTwoFactor
|
|
748
|
+
}
|
|
749
|
+
disabled={isLoading}
|
|
750
|
+
>
|
|
751
|
+
{isLoading
|
|
752
|
+
? 'Disabling...'
|
|
753
|
+
: 'Disable two-factor authentication'}
|
|
754
|
+
</button>
|
|
755
|
+
</div>
|
|
756
|
+
)}
|
|
757
|
+
|
|
758
|
+
{twoFactorEnabled &&
|
|
759
|
+
!userProfile.two_factor_confirmed_at && (
|
|
643
760
|
<button
|
|
644
761
|
className={`${styles.btn} ${styles.btnDanger}`}
|
|
645
762
|
onClick={disableTwoFactor}
|
|
@@ -649,29 +766,30 @@ const Profile = ({ userProfile, setUserProfile }) => {
|
|
|
649
766
|
? 'Disabling...'
|
|
650
767
|
: 'Disable two-factor authentication'}
|
|
651
768
|
</button>
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
{twoFactorEnabled &&
|
|
656
|
-
!userProfile.two_factor_confirmed_at && (
|
|
657
|
-
<button
|
|
658
|
-
className={`${styles.btn} ${styles.btnDanger}`}
|
|
659
|
-
onClick={disableTwoFactor}
|
|
660
|
-
disabled={isLoading}
|
|
661
|
-
>
|
|
662
|
-
{isLoading
|
|
663
|
-
? 'Disabling...'
|
|
664
|
-
: 'Disable two-factor authentication'}
|
|
665
|
-
</button>
|
|
666
|
-
)}
|
|
769
|
+
)}
|
|
770
|
+
</div>
|
|
667
771
|
</div>
|
|
772
|
+
</section>
|
|
668
773
|
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
774
|
+
{showTwoFactorSetup && (
|
|
775
|
+
<section className={styles.section}>
|
|
776
|
+
<div className={styles.sectionAside}>
|
|
777
|
+
<h3 className={styles.sectionTitle}>
|
|
672
778
|
Set up two-factor authentication
|
|
673
779
|
</h3>
|
|
674
|
-
<
|
|
780
|
+
<p
|
|
781
|
+
className={
|
|
782
|
+
styles.sectionDescription
|
|
783
|
+
}
|
|
784
|
+
>
|
|
785
|
+
Pair an app with your account, then
|
|
786
|
+
confirm the pairing with a code from
|
|
787
|
+
it. Keep the recovery codes.
|
|
788
|
+
</p>
|
|
789
|
+
</div>
|
|
790
|
+
<div className={styles.sectionFields}>
|
|
791
|
+
<div className={styles.twoFactorSetup}>
|
|
792
|
+
<ol className={styles.setupSteps}>
|
|
675
793
|
<li>
|
|
676
794
|
<p>
|
|
677
795
|
Install an authenticator app
|
|
@@ -904,22 +1022,27 @@ const Profile = ({ userProfile, setUserProfile }) => {
|
|
|
904
1022
|
device.
|
|
905
1023
|
</p>
|
|
906
1024
|
</li>
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
className={`${styles.btn} ${styles.btnSecondary}`}
|
|
911
|
-
onClick={() =>
|
|
912
|
-
setShowTwoFactorSetup(false)
|
|
913
|
-
}
|
|
914
|
-
type="button"
|
|
915
|
-
disabled={isLoading}
|
|
1025
|
+
</ol>
|
|
1026
|
+
<div
|
|
1027
|
+
className={styles.setupActions}
|
|
916
1028
|
>
|
|
917
|
-
|
|
918
|
-
|
|
1029
|
+
<button
|
|
1030
|
+
className={`${styles.btn} ${styles.btnSecondary}`}
|
|
1031
|
+
onClick={() =>
|
|
1032
|
+
setShowTwoFactorSetup(
|
|
1033
|
+
false
|
|
1034
|
+
)
|
|
1035
|
+
}
|
|
1036
|
+
type="button"
|
|
1037
|
+
disabled={isLoading}
|
|
1038
|
+
>
|
|
1039
|
+
Cancel
|
|
1040
|
+
</button>
|
|
1041
|
+
</div>
|
|
919
1042
|
</div>
|
|
920
1043
|
</div>
|
|
921
|
-
|
|
922
|
-
|
|
1044
|
+
</section>
|
|
1045
|
+
)}
|
|
923
1046
|
</div>
|
|
924
1047
|
</div>
|
|
925
1048
|
);
|