@visns-studio/visns-components 6.6.4 → 6.15.1
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/README.md +1623 -18
- package/package.json +10 -4
- package/src/components/DataGrid.jsx +181 -38
- package/src/components/Fetch.jsx +80 -3
- package/src/components/Form.jsx +9 -0
- package/src/components/Navigation.jsx +109 -50
- package/src/components/Notification.jsx +279 -9
- package/src/components/TableFilter.jsx +14 -1
- package/src/components/auth/AuthBrandPanel.jsx +43 -0
- package/src/components/auth/AuthLoading.jsx +78 -0
- package/src/components/auth/AuthShell.jsx +59 -0
- package/src/components/auth/ClientAuth.jsx +161 -0
- package/src/components/auth/ClientAuthFrame.jsx +59 -0
- package/src/components/auth/ClientLogin.jsx +266 -55
- package/src/components/auth/ClientOTPVerify.jsx +587 -115
- package/src/components/auth/ImpersonateGate.jsx +254 -0
- package/src/components/auth/Login.jsx +134 -41
- package/src/components/auth/LogoutScreen.jsx +112 -0
- package/src/components/auth/Reset.jsx +134 -77
- package/src/components/auth/TwoFactorAuth.jsx +475 -297
- package/src/components/auth/Verify.jsx +237 -126
- package/src/components/auth/authEndpoints.js +105 -0
- package/src/components/auth/authFont.js +23 -0
- package/src/components/auth/authHelpers.js +465 -0
- package/src/components/auth/clientAuthProtocols.js +240 -0
- package/src/components/auth/useOptionalRouter.js +49 -0
- package/src/components/callQueue/CallQueuePop.jsx +1502 -0
- package/src/components/callQueue/CallQueueSettings.jsx +508 -0
- package/src/components/callQueue/callQueueHelpers.js +280 -0
- package/src/components/callQueue/callQueueSettingsHelpers.js +75 -0
- package/src/components/columns/ColumnRenderers.jsx +53 -7
- package/src/components/generic/GenericAuth.jsx +163 -96
- package/src/components/generic/GenericDetail.jsx +215 -90
- package/src/components/generic/GenericIndex.jsx +20 -47
- package/src/components/generic/GenericMain.jsx +5 -0
- package/src/components/generic/GroupedReportRenderer.jsx +1 -5
- package/src/components/generic/StandardModal.jsx +17 -5
- package/src/components/generic/reportSemanticSteps/SemanticEntityStep.jsx +1 -6
- package/src/components/generic/reportSemanticSteps/SemanticFieldsStep.jsx +4 -11
- package/src/components/generic/reportSemanticSteps/SemanticFiltersStep.jsx +9 -27
- package/src/components/generic/reportSemanticSteps/SemanticGroupingStep.jsx +5 -13
- package/src/components/generic/reportSemanticSteps/SemanticParameterPrompt.jsx +5 -13
- package/src/components/generic/reportSemanticSteps/SemanticPreviewStep.jsx +14 -31
- package/src/components/generic/reportSemanticSteps/SemanticRelationsStep.jsx +3 -9
- package/src/components/generic/reportSemanticSteps/SemanticValueInput.jsx +2 -15
- package/src/components/navActive.js +60 -0
- package/src/components/notify/desktopNotifications.js +256 -0
- package/src/components/sketch/SketchField.jsx +12 -2
- package/src/components/sms/SmsComposeModal.jsx +495 -0
- package/src/components/sms/SmsInbox.jsx +596 -0
- package/src/components/sms/SmsInboxBadge.jsx +468 -0
- package/src/components/sms/SmsLineSettings.jsx +854 -0
- package/src/components/sms/SmsThreadPanel.jsx +1184 -0
- package/src/components/sms/smsEndpoints.js +56 -0
- package/src/components/sms/smsHelpers.js +1075 -0
- package/src/components/sms/smsLiveState.js +132 -0
- package/src/components/sms/useSmsLive.js +307 -0
- package/src/components/styles/CallQueuePop.module.scss +646 -0
- package/src/components/styles/CallQueueSettings.module.scss +460 -0
- package/src/components/styles/ClientAuth.module.scss +229 -0
- package/src/components/styles/GenericDetail.module.scss +153 -26
- package/src/components/styles/GenericIndex.module.scss +13 -0
- package/src/components/styles/GenericMain.module.scss +19 -2
- package/src/components/styles/ImpersonateGate.module.scss +85 -0
- package/src/components/styles/Login.module.scss +13 -122
- package/src/components/styles/LogoutScreen.module.scss +89 -0
- package/src/components/styles/Navigation.module.scss +509 -223
- package/src/components/styles/Notification.module.scss +103 -0
- package/src/components/styles/Reset.module.scss +52 -186
- package/src/components/styles/Sms.module.scss +2382 -0
- package/src/components/styles/TableFilter.module.scss +118 -98
- package/src/components/styles/TwoFactorAuth.module.scss +115 -176
- package/src/components/styles/Vault.module.scss +1983 -0
- package/src/components/styles/Verify.module.scss +57 -180
- package/src/components/styles/_authBrandPanel.scss +163 -0
- package/src/components/styles/_authShell.scss +369 -0
- package/src/components/styles/global-datagrid.css +27 -2
- package/src/components/utils/buildEnv.js +67 -0
- package/src/components/utils/displayValue.js +94 -0
- package/src/components/utils/useDensity.js +345 -9
- package/src/components/vault/OtpChip.jsx +208 -0
- package/src/components/vault/PasswordGenerator.jsx +145 -0
- package/src/components/vault/QrScanner.jsx +326 -0
- package/src/components/vault/VaultAccessLog.jsx +225 -0
- package/src/components/vault/VaultConfirmPanel.jsx +113 -0
- package/src/components/vault/VaultEntryForm.jsx +740 -0
- package/src/components/vault/VaultManager.jsx +1000 -0
- package/src/components/vault/VaultQuickSearch.jsx +681 -0
- package/src/components/vault/qrDecode.js +257 -0
- package/src/components/vault/useDebouncedValue.js +22 -0
- package/src/components/vault/useVaultReveal.js +160 -0
- package/src/components/vault/vaultClipboard.js +33 -0
- package/src/components/vault/vaultEndpoints.js +40 -0
- package/src/components/vault/vaultFit.js +116 -0
- package/src/components/vault/vaultHelpers.js +547 -0
- package/src/components/vault/vaultNavigation.jsx +58 -0
- package/src/components/vault/vaultOtp.js +105 -0
- package/src/index.js +233 -1
|
@@ -1,19 +1,146 @@
|
|
|
1
1
|
import '../styles/global.css';
|
|
2
2
|
|
|
3
3
|
import React, { useState } from 'react';
|
|
4
|
-
import { useLocation, useNavigate } from 'react-router-dom';
|
|
5
4
|
import { toast } from 'react-toastify';
|
|
6
5
|
import { User } from 'lucide-react';
|
|
7
6
|
|
|
8
7
|
import CustomFetch from '../Fetch';
|
|
8
|
+
import useOptionalRouter from './useOptionalRouter';
|
|
9
|
+
import ClientAuthFrame from './ClientAuthFrame';
|
|
10
|
+
import { resolveClientPaths } from './authEndpoints';
|
|
11
|
+
import {
|
|
12
|
+
applyUsernameDomain,
|
|
13
|
+
contactHint,
|
|
14
|
+
normaliseAuthError,
|
|
15
|
+
} from './authHelpers';
|
|
16
|
+
import { resolveClientProtocol } from './clientAuthProtocols';
|
|
9
17
|
|
|
10
18
|
import styles from '../styles/ClientAuth.module.scss';
|
|
11
19
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Client portal sign-in, step one: identify yourself, receive a code.
|
|
22
|
+
*
|
|
23
|
+
* Three things about this screen are configurable rather than assumed, because
|
|
24
|
+
* each was a blocker for the Next.js portal in 6.7.0:
|
|
25
|
+
*
|
|
26
|
+
* - the WIRE PROTOCOL (`protocol`), because the two backends in the wild
|
|
27
|
+
* disagree about the request body and about how success is signalled. See
|
|
28
|
+
* clientAuthProtocols.js.
|
|
29
|
+
* - the HAND-OFF to step two (`onChallengeIssued`), because a router
|
|
30
|
+
* navigation carrying router state is one way to get there, not the only
|
|
31
|
+
* one. An app rendering both steps on a single page just holds the
|
|
32
|
+
* challenge in its own state.
|
|
33
|
+
* - NAVIGATION (`onNavigate`), because react-router is not a given. The
|
|
34
|
+
* router hooks are consulted only when nothing else was supplied, and only
|
|
35
|
+
* when a Router actually exists.
|
|
36
|
+
*
|
|
37
|
+
* With none of those passed, this renders and behaves exactly as 6.6.3 did.
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
const DEFAULT_COPY = {
|
|
41
|
+
title: 'Client Login',
|
|
42
|
+
subtitle:
|
|
43
|
+
'Enter your email address or mobile number to receive a verification code',
|
|
44
|
+
fieldLabel: 'Email or Mobile',
|
|
45
|
+
placeholder: 'Enter your email address or mobile number',
|
|
46
|
+
/** Client-side validation. */
|
|
47
|
+
required: 'Please enter your email address or mobile number',
|
|
48
|
+
/** Fallback when the server declines without saying why. */
|
|
49
|
+
notFound:
|
|
50
|
+
'Could not find an account with this email address or mobile number.',
|
|
51
|
+
sending: 'Sending verification code...',
|
|
52
|
+
sent: 'Verification code sent',
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Button labels. These are the 6.6.3 strings, NOT the portal's — the
|
|
57
|
+
* backward-compatibility rule outranks matching the portal's wording, so an app
|
|
58
|
+
* that wants "Send Code" / "Please wait..." asks for them:
|
|
59
|
+
* labels={{ submit: 'Send Code', submitting: 'Please wait...' }}
|
|
60
|
+
*/
|
|
61
|
+
const DEFAULT_LABELS = {
|
|
62
|
+
submit: 'Continue',
|
|
63
|
+
submitting: 'Sending...',
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const ClientLogin = ({
|
|
67
|
+
logo,
|
|
68
|
+
setSystemAuth,
|
|
69
|
+
/** Endpoint map — `urls.login` is the request this screen makes. */
|
|
70
|
+
urls = {},
|
|
71
|
+
/** Route paths; `/client/*` unless overridden. See authEndpoints.js. */
|
|
72
|
+
paths,
|
|
73
|
+
/**
|
|
74
|
+
* Wire protocol: `'uuid'` (default, unchanged) or `'contact'` for
|
|
75
|
+
* ThroughLife's `/api/auth/*`. An object of overrides is accepted too.
|
|
76
|
+
*/
|
|
77
|
+
protocol,
|
|
78
|
+
/** Merged over DEFAULT_COPY. */
|
|
79
|
+
copy: copyOverrides,
|
|
80
|
+
/** Merged over DEFAULT_LABELS. */
|
|
81
|
+
labels: labelOverrides,
|
|
82
|
+
/** Show the live "Email address detected" hint under the field. */
|
|
83
|
+
showContactHint = false,
|
|
84
|
+
/** Merged over DEFAULT_CONTACT_HINTS in authHelpers.js. */
|
|
85
|
+
contactHints,
|
|
86
|
+
/**
|
|
87
|
+
* Complete a bare username to `name@domain` before sending, the way the
|
|
88
|
+
* portal does (`virtualportal.com.au`). Null leaves the input untouched.
|
|
89
|
+
*/
|
|
90
|
+
usernameDomain = null,
|
|
91
|
+
/**
|
|
92
|
+
* Overrides the protocol's own request-body key for the identifier. Left
|
|
93
|
+
* unset, `uuid` sends `email` and `contact` sends `contact`.
|
|
94
|
+
*/
|
|
95
|
+
contactField,
|
|
96
|
+
/**
|
|
97
|
+
* Carry the server's dev OTP through to the verify screen so it can offer
|
|
98
|
+
* an auto-fill. Only ever honoured outside production.
|
|
99
|
+
*/
|
|
100
|
+
showDevOtp = false,
|
|
101
|
+
/** Initial field value — used by the "keep what I typed" back link. */
|
|
102
|
+
initialContact,
|
|
103
|
+
/**
|
|
104
|
+
* 'card' (default, unchanged) or 'split' — the two-pane brand layout. See
|
|
105
|
+
* ClientAuthFrame; the form itself is identical either way.
|
|
106
|
+
*/
|
|
107
|
+
layout = 'card',
|
|
108
|
+
/** Split layout: the brand panel's photograph. */
|
|
109
|
+
loginBg,
|
|
110
|
+
/** Split layout: {eyebrow, name, tagline, footer}. */
|
|
111
|
+
branding,
|
|
112
|
+
/**
|
|
113
|
+
* Called with the whole challenge once a code has been sent. When present,
|
|
114
|
+
* this screen does NOT navigate: the consumer owns what happens next.
|
|
115
|
+
*
|
|
116
|
+
* The challenge is `{contact, rawContact, challengeId, maskedContact,
|
|
117
|
+
* contactMethod, devOtp, message, previousUrl}` — everything
|
|
118
|
+
* ClientOTPVerify needs, in the shape its `challenge` prop takes.
|
|
119
|
+
*/
|
|
120
|
+
onChallengeIssued,
|
|
121
|
+
/**
|
|
122
|
+
* Navigation, when this screen still owns it. `(path, options) => void`.
|
|
123
|
+
* Falls back to react-router's navigate, then to a plain page load.
|
|
124
|
+
*/
|
|
125
|
+
onNavigate,
|
|
126
|
+
/** Notified with the normalised message when the request is declined. */
|
|
127
|
+
onError,
|
|
128
|
+
}) => {
|
|
129
|
+
const { navigate, location } = useOptionalRouter();
|
|
130
|
+
|
|
131
|
+
const routes = resolveClientPaths(paths);
|
|
132
|
+
const wire = resolveClientProtocol(protocol);
|
|
133
|
+
const copy = { ...DEFAULT_COPY, ...(copyOverrides || {}) };
|
|
134
|
+
const labels = { ...DEFAULT_LABELS, ...(labelOverrides || {}) };
|
|
135
|
+
const field = contactField || wire.contactField;
|
|
136
|
+
const previousUrl = location?.state?.previousUrl || '';
|
|
137
|
+
|
|
15
138
|
const [isLoading, setIsLoading] = useState(false);
|
|
16
|
-
|
|
139
|
+
// Prefilled when the verify screen sent the user back with its "keep what I
|
|
140
|
+
// typed" back link. Nothing sets it otherwise, so this is '' as before.
|
|
141
|
+
const [email, setEmail] = useState(
|
|
142
|
+
initialContact ?? location?.state?.email ?? ''
|
|
143
|
+
);
|
|
17
144
|
const [inputError, setInputError] = useState(false);
|
|
18
145
|
|
|
19
146
|
const handleChange = (e) => {
|
|
@@ -21,77 +148,156 @@ const ClientLogin = ({ logo, setSystemAuth, urls = {} }) => {
|
|
|
21
148
|
setInputError(false);
|
|
22
149
|
};
|
|
23
150
|
|
|
151
|
+
/** Move, by whichever mechanism this app actually has. */
|
|
152
|
+
const go = (path, options) => {
|
|
153
|
+
if (typeof onNavigate === 'function') {
|
|
154
|
+
onNavigate(path, options);
|
|
155
|
+
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (navigate) {
|
|
160
|
+
navigate(path, options);
|
|
161
|
+
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (typeof window !== 'undefined') {
|
|
166
|
+
window.location.href = path;
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const fail = (error) => {
|
|
171
|
+
const message = normaliseAuthError(error, copy.notFound);
|
|
172
|
+
|
|
173
|
+
setInputError(true);
|
|
174
|
+
toast.error(message);
|
|
175
|
+
|
|
176
|
+
if (typeof onError === 'function') {
|
|
177
|
+
onError(message);
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
|
|
24
181
|
const handleSubmit = async (e) => {
|
|
25
182
|
e.preventDefault();
|
|
26
183
|
|
|
184
|
+
// The button is disabled too; this is the belt to that pair of braces,
|
|
185
|
+
// since a form can also be submitted by Enter in the field.
|
|
186
|
+
if (isLoading) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
27
190
|
if (!email.trim()) {
|
|
28
191
|
setInputError(true);
|
|
29
|
-
toast.error(
|
|
192
|
+
toast.error(copy.required);
|
|
30
193
|
return;
|
|
31
194
|
}
|
|
32
195
|
|
|
33
196
|
setIsLoading(true);
|
|
34
|
-
toast.info(
|
|
197
|
+
toast.info(copy.sending, { autoClose: 2000 });
|
|
198
|
+
|
|
199
|
+
const rawContact = email.trim();
|
|
200
|
+
// A no-op unless `usernameDomain` is set.
|
|
201
|
+
const contact = applyUsernameDomain(rawContact, usernameDomain);
|
|
35
202
|
|
|
36
203
|
try {
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
204
|
+
const result = await CustomFetch(
|
|
205
|
+
urls.login,
|
|
206
|
+
'POST',
|
|
207
|
+
wire.requestBody({
|
|
208
|
+
contact,
|
|
209
|
+
contactField: field,
|
|
210
|
+
previousUrl,
|
|
211
|
+
resend: false,
|
|
212
|
+
})
|
|
213
|
+
);
|
|
45
214
|
|
|
46
|
-
|
|
215
|
+
const body = result.data;
|
|
216
|
+
|
|
217
|
+
// Cleared per-branch: on success this screen hands over to step
|
|
218
|
+
// two, and dropping the button back to "Continue" first left a gap
|
|
219
|
+
// where a second code could be requested.
|
|
220
|
+
if (!wire.isSuccess(body)) {
|
|
221
|
+
setIsLoading(false);
|
|
222
|
+
|
|
223
|
+
if (typeof setSystemAuth === 'function') {
|
|
224
|
+
setSystemAuth(false);
|
|
225
|
+
}
|
|
47
226
|
|
|
48
|
-
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
227
|
+
// `{error: '…'}` bodies are as common here as `{message}` ones,
|
|
228
|
+
// and only one of them used to reach the user.
|
|
229
|
+
fail(body);
|
|
230
|
+
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const read = wire.readChallenge(body);
|
|
235
|
+
|
|
236
|
+
// Everything step two needs, in one object. Both forms of the
|
|
237
|
+
// identifier travel: which one the verify call sends is the
|
|
238
|
+
// protocol's decision, not this screen's.
|
|
239
|
+
const challenge = {
|
|
240
|
+
contact,
|
|
241
|
+
rawContact,
|
|
242
|
+
challengeId: read.challengeId,
|
|
243
|
+
maskedContact: read.maskedContact,
|
|
244
|
+
contactMethod: read.contactMethod,
|
|
245
|
+
message: read.message,
|
|
246
|
+
previousUrl,
|
|
247
|
+
// Never in a production build, and never without the opt-in —
|
|
248
|
+
// the panel that renders it is gated a second time.
|
|
249
|
+
devOtp:
|
|
250
|
+
process.env.NODE_ENV !== 'production' &&
|
|
251
|
+
(showDevOtp || process.env.NODE_ENV === 'development')
|
|
252
|
+
? read.devOtp
|
|
253
|
+
: null,
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
toast.success(read.message || copy.sent);
|
|
257
|
+
|
|
258
|
+
if (typeof onChallengeIssued === 'function') {
|
|
259
|
+
onChallengeIssued(challenge);
|
|
260
|
+
|
|
261
|
+
return;
|
|
73
262
|
}
|
|
263
|
+
|
|
264
|
+
// Router state is the 6.6.3 hand-off. Both the old key spellings
|
|
265
|
+
// and the whole challenge are written, so an app already reading
|
|
266
|
+
// `state.uuid` keeps working.
|
|
267
|
+
go(routes.verify, {
|
|
268
|
+
state: {
|
|
269
|
+
email: rawContact,
|
|
270
|
+
contact: challenge.contact,
|
|
271
|
+
uuid: challenge.challengeId,
|
|
272
|
+
challenge,
|
|
273
|
+
maskedContact: challenge.maskedContact,
|
|
274
|
+
contactMethod: challenge.contactMethod,
|
|
275
|
+
previousUrl,
|
|
276
|
+
...(challenge.devOtp ? { otp: challenge.devOtp } : {}),
|
|
277
|
+
},
|
|
278
|
+
});
|
|
74
279
|
} catch (error) {
|
|
75
280
|
setIsLoading(false);
|
|
76
|
-
|
|
77
|
-
toast.error(String(error));
|
|
281
|
+
fail(error);
|
|
78
282
|
}
|
|
79
283
|
};
|
|
80
284
|
|
|
81
285
|
return (
|
|
82
|
-
<
|
|
83
|
-
|
|
84
|
-
|
|
286
|
+
<ClientAuthFrame
|
|
287
|
+
layout={layout}
|
|
288
|
+
styles={styles}
|
|
289
|
+
loginBg={loginBg}
|
|
290
|
+
branding={branding}
|
|
291
|
+
>
|
|
292
|
+
<img src={logo} alt="Client Portal" className={styles.logo} />
|
|
85
293
|
|
|
86
|
-
<h1 className={styles.title}>
|
|
87
|
-
<p className={styles.subtitle}>
|
|
88
|
-
Enter your email address or mobile number to receive a verification code
|
|
89
|
-
</p>
|
|
294
|
+
<h1 className={styles.title}>{copy.title}</h1>
|
|
295
|
+
<p className={styles.subtitle}>{copy.subtitle}</p>
|
|
90
296
|
|
|
91
297
|
<form onSubmit={handleSubmit}>
|
|
92
298
|
<div className={styles.formGroup}>
|
|
93
299
|
<label htmlFor="email" className={styles.inputLabel}>
|
|
94
|
-
|
|
300
|
+
{copy.fieldLabel}
|
|
95
301
|
</label>
|
|
96
302
|
<input
|
|
97
303
|
id="email"
|
|
@@ -102,12 +308,18 @@ const ClientLogin = ({ logo, setSystemAuth, urls = {} }) => {
|
|
|
102
308
|
className={`${styles.input} ${
|
|
103
309
|
inputError ? styles.error : ''
|
|
104
310
|
}`}
|
|
105
|
-
placeholder=
|
|
311
|
+
placeholder={copy.placeholder}
|
|
106
312
|
autoComplete="username"
|
|
313
|
+
disabled={isLoading}
|
|
107
314
|
/>
|
|
108
315
|
<span className={styles.inputIcon}>
|
|
109
316
|
<User strokeWidth={2} size={18} />
|
|
110
317
|
</span>
|
|
318
|
+
{showContactHint ? (
|
|
319
|
+
<p className={styles.inputHint}>
|
|
320
|
+
{contactHint(email, contactHints)}
|
|
321
|
+
</p>
|
|
322
|
+
) : null}
|
|
111
323
|
</div>
|
|
112
324
|
|
|
113
325
|
<button
|
|
@@ -115,11 +327,10 @@ const ClientLogin = ({ logo, setSystemAuth, urls = {} }) => {
|
|
|
115
327
|
type="submit"
|
|
116
328
|
disabled={isLoading}
|
|
117
329
|
>
|
|
118
|
-
{isLoading ?
|
|
330
|
+
{isLoading ? labels.submitting : labels.submit}
|
|
119
331
|
</button>
|
|
120
332
|
</form>
|
|
121
|
-
|
|
122
|
-
</div>
|
|
333
|
+
</ClientAuthFrame>
|
|
123
334
|
);
|
|
124
335
|
};
|
|
125
336
|
|