@oxy.so/services 8.1.0 → 8.2.0
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/lib/commonjs/ui/components/signIn/OxyAuthenticatorPanel.js +3 -7
- package/lib/commonjs/ui/components/signIn/OxyAuthenticatorPanel.js.map +1 -1
- package/lib/commonjs/ui/components/signIn/OxySignInPanel.js +34 -27
- package/lib/commonjs/ui/components/signIn/OxySignInPanel.js.map +1 -1
- package/lib/commonjs/ui/components/signIn/ReauthStep.js +27 -19
- package/lib/commonjs/ui/components/signIn/ReauthStep.js.map +1 -1
- package/lib/commonjs/ui/components/signIn/accountFlowParts.js +54 -22
- package/lib/commonjs/ui/components/signIn/accountFlowParts.js.map +1 -1
- package/lib/module/ui/components/signIn/OxyAuthenticatorPanel.js +4 -8
- package/lib/module/ui/components/signIn/OxyAuthenticatorPanel.js.map +1 -1
- package/lib/module/ui/components/signIn/OxySignInPanel.js +36 -29
- package/lib/module/ui/components/signIn/OxySignInPanel.js.map +1 -1
- package/lib/module/ui/components/signIn/ReauthStep.js +29 -21
- package/lib/module/ui/components/signIn/ReauthStep.js.map +1 -1
- package/lib/module/ui/components/signIn/accountFlowParts.js +51 -19
- package/lib/module/ui/components/signIn/accountFlowParts.js.map +1 -1
- package/lib/typescript/commonjs/ui/components/signIn/OxyAuthenticatorPanel.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/signIn/OxySignInPanel.d.ts +6 -3
- package/lib/typescript/commonjs/ui/components/signIn/OxySignInPanel.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/signIn/ReauthStep.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/signIn/accountFlowParts.d.ts +29 -9
- package/lib/typescript/commonjs/ui/components/signIn/accountFlowParts.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts +2 -2
- package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/signIn/OxyAuthenticatorPanel.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/signIn/OxySignInPanel.d.ts +6 -3
- package/lib/typescript/module/ui/components/signIn/OxySignInPanel.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/signIn/ReauthStep.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/signIn/accountFlowParts.d.ts +29 -9
- package/lib/typescript/module/ui/components/signIn/accountFlowParts.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts +2 -2
- package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
- package/package.json +7 -7
- package/src/ui/components/signIn/OxyAuthenticatorPanel.tsx +4 -8
- package/src/ui/components/signIn/OxySignInPanel.tsx +41 -27
- package/src/ui/components/signIn/ReauthStep.tsx +33 -21
- package/src/ui/components/signIn/accountFlowParts.tsx +64 -22
|
@@ -10,9 +10,12 @@
|
|
|
10
10
|
* photo carousel, in the right column (the account dialog grows to
|
|
11
11
|
* that card); below `md` it is "Continue with Oxy" at the top. On
|
|
12
12
|
* native it is "Continue with Oxy" ("Get Commons" without Commons).
|
|
13
|
-
* check-email one email carries a code and a link. The code is typed here
|
|
14
|
-
* digits, or
|
|
15
|
-
*
|
|
13
|
+
* check-email one email carries a code and a link. The code is typed here: 6
|
|
14
|
+
* digits, or — "Does your code have letters?" — the 10-character
|
|
15
|
+
* long code an account past its daily guessing ceiling is sent
|
|
16
|
+
* (the start response does not say which, by design); meanwhile
|
|
17
|
+
* the screen asks whether the link was opened in this browser,
|
|
18
|
+
* and signs in when it was.
|
|
16
19
|
* password the alternative for an account that has one.
|
|
17
20
|
* second-factor the authenticator's code, or a backup code, when the account has one.
|
|
18
21
|
*
|
|
@@ -32,6 +35,7 @@ import { toast } from '@oxy.so/bloom/toast';
|
|
|
32
35
|
import { Text } from '@oxy.so/bloom/typography';
|
|
33
36
|
import type { SwitcherContextRow } from '@oxy.so/core/session';
|
|
34
37
|
import {
|
|
38
|
+
EMAIL_CODE_LENGTH,
|
|
35
39
|
EMAIL_SIGNIN_LONG_CODE_LENGTH,
|
|
36
40
|
SIGN_IN_ERROR_CODES,
|
|
37
41
|
TOTP_DIGITS,
|
|
@@ -54,11 +58,12 @@ import { OxyAccountPicker } from './OxyAccountPicker';
|
|
|
54
58
|
import { OxyAuthScreen, OxyAuthScreenHeader, OxyAuthSplit, OxyAuthTerms } from './OxyAuthScreen';
|
|
55
59
|
import {
|
|
56
60
|
AccountFlowAction,
|
|
61
|
+
AccountFlowCodeField,
|
|
57
62
|
AccountFlowField,
|
|
58
63
|
AccountFlowNote,
|
|
64
|
+
BACKUP_CODE_LENGTH,
|
|
59
65
|
describeSignInError,
|
|
60
66
|
errorCode,
|
|
61
|
-
formatSignInCodeInput,
|
|
62
67
|
isCompleteSignInCode,
|
|
63
68
|
isRateLimited,
|
|
64
69
|
retryAfterSeconds,
|
|
@@ -79,6 +84,8 @@ interface SavedSignInFlow {
|
|
|
79
84
|
identifier: string;
|
|
80
85
|
showForm: boolean;
|
|
81
86
|
useBackupCode: boolean;
|
|
87
|
+
/** The code field takes the 10-character long code ("Does your code have letters?"). */
|
|
88
|
+
longCode: boolean;
|
|
82
89
|
/** When "Send a new email" may be pressed again (ms). */
|
|
83
90
|
resendUntil: number;
|
|
84
91
|
}
|
|
@@ -139,6 +146,7 @@ export const OxySignInPanel: React.FC<OxySignInPanelProps> = ({
|
|
|
139
146
|
const [password, setPassword] = useState('');
|
|
140
147
|
const [secondFactorCode, setSecondFactorCode] = useState('');
|
|
141
148
|
const [useBackupCode, setUseBackupCode] = useState(saved?.useBackupCode ?? false);
|
|
149
|
+
const [longCode, setLongCode] = useState(saved?.longCode ?? false);
|
|
142
150
|
const [pending, setPending] = useState(false);
|
|
143
151
|
const [error, setError] = useState<string | null>(null);
|
|
144
152
|
const [notice, setNotice] = useState<string | null>(null);
|
|
@@ -153,9 +161,10 @@ export const OxySignInPanel: React.FC<OxySignInPanelProps> = ({
|
|
|
153
161
|
identifier,
|
|
154
162
|
showForm,
|
|
155
163
|
useBackupCode,
|
|
164
|
+
longCode,
|
|
156
165
|
resendUntil: Date.now() + resendSeconds * 1000,
|
|
157
166
|
} satisfies SavedSignInFlow);
|
|
158
|
-
}, [flowOwner, step, identifier, showForm, useBackupCode, resendSeconds]);
|
|
167
|
+
}, [flowOwner, step, identifier, showForm, useBackupCode, longCode, resendSeconds]);
|
|
159
168
|
const blocked = pending || rateLimitSeconds > 0;
|
|
160
169
|
|
|
161
170
|
// The countdown after a 429: one tick a second until the person may retry.
|
|
@@ -244,7 +253,9 @@ export const OxySignInPanel: React.FC<OxySignInPanelProps> = ({
|
|
|
244
253
|
}
|
|
245
254
|
setCode('');
|
|
246
255
|
setResendSeconds(EMAIL_RESEND_COOLDOWN_SECONDS);
|
|
256
|
+
// A resend keeps the field as the person set it; a new request starts at 6 digits.
|
|
247
257
|
if (step.name === 'check-email') setNotice(t('signin.checkEmail.resent'));
|
|
258
|
+
else setLongCode(false);
|
|
248
259
|
setStep({ name: 'check-email', identifier: name, requestId: started.requestId, requestSecret: started.requestSecret });
|
|
249
260
|
})
|
|
250
261
|
.catch(fail)
|
|
@@ -362,6 +373,7 @@ export const OxySignInPanel: React.FC<OxySignInPanelProps> = ({
|
|
|
362
373
|
setError(null);
|
|
363
374
|
setNotice(null);
|
|
364
375
|
setCode('');
|
|
376
|
+
setLongCode(false);
|
|
365
377
|
setPassword('');
|
|
366
378
|
setStep({ name: 'start' });
|
|
367
379
|
};
|
|
@@ -443,24 +455,34 @@ export const OxySignInPanel: React.FC<OxySignInPanelProps> = ({
|
|
|
443
455
|
title={t('signin.checkEmail.title')}
|
|
444
456
|
description={t('signin.checkEmail.description', { identifier: request.identifier })}
|
|
445
457
|
/>
|
|
446
|
-
<
|
|
458
|
+
<AccountFlowCodeField
|
|
459
|
+
key={longCode ? 'long' : 'digits'}
|
|
447
460
|
label={t('signin.checkEmail.codeLabel')}
|
|
448
461
|
value={code}
|
|
449
462
|
onChange={(value) => {
|
|
450
|
-
|
|
451
|
-
setCode(typed);
|
|
463
|
+
setCode(value);
|
|
452
464
|
clearError();
|
|
453
|
-
if (isCompleteSignInCode(typed)) submitCode(typed);
|
|
454
465
|
}}
|
|
455
|
-
|
|
466
|
+
onComplete={submitCode}
|
|
456
467
|
error={shownError}
|
|
457
468
|
disabled={blocked}
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
469
|
+
length={longCode ? EMAIL_SIGNIN_LONG_CODE_LENGTH : EMAIL_CODE_LENGTH}
|
|
470
|
+
type={longCode ? 'alphanumeric' : 'numeric'}
|
|
471
|
+
groupEvery={longCode ? EMAIL_SIGNIN_LONG_CODE_LENGTH / 2 : undefined}
|
|
461
472
|
hint={t('signin.checkEmail.codeHint')}
|
|
462
473
|
testID="signin-code"
|
|
463
474
|
/>
|
|
475
|
+
<SubtleLink
|
|
476
|
+
label={longCode ? t('signin.checkEmail.codeDigitsOnly') : t('signin.checkEmail.codeHasLetters')}
|
|
477
|
+
theme={theme}
|
|
478
|
+
onPress={() => {
|
|
479
|
+
setLongCode(!longCode);
|
|
480
|
+
setCode('');
|
|
481
|
+
setError(null);
|
|
482
|
+
}}
|
|
483
|
+
disabled={pending}
|
|
484
|
+
testID="signin-code-letters"
|
|
485
|
+
/>
|
|
464
486
|
{notice ? <AccountFlowNote testID="signin-notice">{notice}</AccountFlowNote> : null}
|
|
465
487
|
<AccountFlowAction
|
|
466
488
|
label={t('signin.actions.continue')}
|
|
@@ -553,28 +575,20 @@ export const OxySignInPanel: React.FC<OxySignInPanelProps> = ({
|
|
|
553
575
|
title={t('signin.secondFactor.title')}
|
|
554
576
|
description={useBackupCode ? t('signin.secondFactor.backupDescription') : t('signin.secondFactor.description')}
|
|
555
577
|
/>
|
|
556
|
-
<
|
|
578
|
+
<AccountFlowCodeField
|
|
557
579
|
key={useBackupCode ? 'backup' : 'totp'}
|
|
558
580
|
label={useBackupCode ? t('signin.secondFactor.backupLabel') : t('signin.secondFactor.label')}
|
|
559
581
|
value={secondFactorCode}
|
|
560
582
|
onChange={(value) => {
|
|
561
|
-
|
|
562
|
-
setSecondFactorCode(value);
|
|
563
|
-
clearError();
|
|
564
|
-
return;
|
|
565
|
-
}
|
|
566
|
-
const digits = value.replace(/\D/g, '').slice(0, TOTP_DIGITS);
|
|
567
|
-
setSecondFactorCode(digits);
|
|
583
|
+
setSecondFactorCode(value);
|
|
568
584
|
clearError();
|
|
569
|
-
if (digits.length === TOTP_DIGITS) submitSecondFactor(digits);
|
|
570
585
|
}}
|
|
571
|
-
|
|
586
|
+
onComplete={submitSecondFactor}
|
|
572
587
|
error={shownError}
|
|
573
588
|
disabled={blocked}
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
maxLength={useBackupCode ? 11 : TOTP_DIGITS}
|
|
589
|
+
length={useBackupCode ? BACKUP_CODE_LENGTH : TOTP_DIGITS}
|
|
590
|
+
type={useBackupCode ? 'alphanumeric' : 'numeric'}
|
|
591
|
+
groupEvery={useBackupCode ? BACKUP_CODE_LENGTH / 2 : undefined}
|
|
578
592
|
testID="signin-second-factor"
|
|
579
593
|
/>
|
|
580
594
|
<AccountFlowAction
|
|
@@ -9,16 +9,18 @@
|
|
|
9
9
|
import type React from 'react';
|
|
10
10
|
import { useState } from 'react';
|
|
11
11
|
import { useTheme } from '@oxy.so/bloom/theme';
|
|
12
|
-
import { EMAIL_CODE_LENGTH, type ReauthAction, type ReauthProof } from '@oxy.so/contracts';
|
|
12
|
+
import { EMAIL_CODE_LENGTH, TOTP_DIGITS, type ReauthAction, type ReauthProof } from '@oxy.so/contracts';
|
|
13
13
|
import { useOxy } from '../../context/OxyContext';
|
|
14
14
|
import { useI18n } from '../../hooks/useI18n';
|
|
15
15
|
import { SubtleLink } from '../authChooser/primitives';
|
|
16
16
|
import { OxyAuthScreen, OxyAuthScreenHeader } from './OxyAuthScreen';
|
|
17
17
|
import {
|
|
18
18
|
AccountFlowAction,
|
|
19
|
+
AccountFlowCodeField,
|
|
19
20
|
AccountFlowErrorLine,
|
|
20
21
|
AccountFlowField,
|
|
21
22
|
AccountFlowNote,
|
|
23
|
+
BACKUP_CODE_LENGTH,
|
|
22
24
|
describeSignInError,
|
|
23
25
|
} from './accountFlowParts';
|
|
24
26
|
|
|
@@ -64,6 +66,7 @@ export const ReauthStep: React.FC<ReauthStepProps> = ({
|
|
|
64
66
|
const [emailCode, setEmailCode] = useState('');
|
|
65
67
|
const [password, setPassword] = useState('');
|
|
66
68
|
const [totpCode, setTotpCode] = useState('');
|
|
69
|
+
const [useBackupCode, setUseBackupCode] = useState(false);
|
|
67
70
|
const [pending, setPending] = useState(false);
|
|
68
71
|
const [error, setError] = useState<string | null>(null);
|
|
69
72
|
|
|
@@ -138,17 +141,13 @@ export const ReauthStep: React.FC<ReauthStepProps> = ({
|
|
|
138
141
|
) : verificationId ? (
|
|
139
142
|
<>
|
|
140
143
|
<AccountFlowNote>{t('reauth.codeSent')}</AccountFlowNote>
|
|
141
|
-
<
|
|
144
|
+
<AccountFlowCodeField
|
|
142
145
|
label={t('reauth.codeLabel')}
|
|
143
146
|
value={emailCode}
|
|
144
|
-
onChange={
|
|
145
|
-
onSubmit={submit}
|
|
147
|
+
onChange={setEmailCode}
|
|
146
148
|
error={null}
|
|
147
149
|
disabled={pending}
|
|
148
|
-
|
|
149
|
-
autoComplete="one-time-code"
|
|
150
|
-
keyboardType="number-pad"
|
|
151
|
-
maxLength={EMAIL_CODE_LENGTH}
|
|
150
|
+
length={EMAIL_CODE_LENGTH}
|
|
152
151
|
testID="reauth-code"
|
|
153
152
|
/>
|
|
154
153
|
</>
|
|
@@ -156,19 +155,32 @@ export const ReauthStep: React.FC<ReauthStepProps> = ({
|
|
|
156
155
|
<AccountFlowNote>{t('reauth.emailDescription')}</AccountFlowNote>
|
|
157
156
|
)}
|
|
158
157
|
{totpEnabled && !needsCode ? (
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
158
|
+
<>
|
|
159
|
+
<AccountFlowCodeField
|
|
160
|
+
key={useBackupCode ? 'backup' : 'totp'}
|
|
161
|
+
label={useBackupCode ? t('signin.secondFactor.backupLabel') : t('signin.secondFactor.label')}
|
|
162
|
+
value={totpCode}
|
|
163
|
+
onChange={setTotpCode}
|
|
164
|
+
error={null}
|
|
165
|
+
disabled={pending}
|
|
166
|
+
length={useBackupCode ? BACKUP_CODE_LENGTH : TOTP_DIGITS}
|
|
167
|
+
type={useBackupCode ? 'alphanumeric' : 'numeric'}
|
|
168
|
+
groupEvery={useBackupCode ? BACKUP_CODE_LENGTH / 2 : undefined}
|
|
169
|
+
autoFocus={false}
|
|
170
|
+
testID="reauth-totp"
|
|
171
|
+
/>
|
|
172
|
+
<SubtleLink
|
|
173
|
+
label={useBackupCode ? t('signin.secondFactor.useAuthenticator') : t('signin.secondFactor.useBackup')}
|
|
174
|
+
theme={theme}
|
|
175
|
+
onPress={() => {
|
|
176
|
+
setUseBackupCode(!useBackupCode);
|
|
177
|
+
setTotpCode('');
|
|
178
|
+
setError(null);
|
|
179
|
+
}}
|
|
180
|
+
disabled={pending}
|
|
181
|
+
testID="reauth-toggle-backup"
|
|
182
|
+
/>
|
|
183
|
+
</>
|
|
172
184
|
) : null}
|
|
173
185
|
{error ? <AccountFlowErrorLine message={error} /> : null}
|
|
174
186
|
<AccountFlowAction
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The pieces every Oxy sign-in and account screen shares: the error vocabulary,
|
|
3
|
-
* one labelled field, the one primary action, a note,
|
|
3
|
+
* one labelled field, one one-time-code field, the one primary action, a note,
|
|
4
|
+
* and the email-code step.
|
|
4
5
|
* Each screen is built from the same shell as `OxySignInPanel`
|
|
5
6
|
* (`OxyAuthScreen`), so they read as one product in the account dialog, in an
|
|
6
7
|
* app's settings and on auth.oxy.so.
|
|
@@ -10,6 +11,8 @@ import type React from 'react';
|
|
|
10
11
|
import { useState } from 'react';
|
|
11
12
|
import { StyleSheet, View } from 'react-native';
|
|
12
13
|
import { Button } from '@oxy.so/bloom/button';
|
|
14
|
+
import { Field } from '@oxy.so/bloom/field';
|
|
15
|
+
import { InputOtp } from '@oxy.so/bloom/input-otp';
|
|
13
16
|
import { useTheme } from '@oxy.so/bloom/theme';
|
|
14
17
|
import { TextField, TextFieldHint, TextFieldInput, TextFieldLabel } from '@oxy.so/bloom/text-field';
|
|
15
18
|
import { Text } from '@oxy.so/bloom/typography';
|
|
@@ -105,9 +108,7 @@ const LONG_CODE = new RegExp(`^[${EMAIL_SIGNIN_LONG_CODE_ALPHABET}]{${EMAIL_SIGN
|
|
|
105
108
|
|
|
106
109
|
/**
|
|
107
110
|
* Whether a typed sign-in code is complete: 6 digits, or the 10-character long
|
|
108
|
-
* code (`XXXXX-XXXXX`, any case, with or without its dash)
|
|
109
|
-
* only when typed without a separator: the long code is shown with its dash
|
|
110
|
-
* after the fifth character, so a long code being typed never reads as 6 digits.
|
|
111
|
+
* code (`XXXXX-XXXXX`, any case, with or without its dash) in its own alphabet.
|
|
111
112
|
*/
|
|
112
113
|
export function isCompleteSignInCode(typed: string): boolean {
|
|
113
114
|
const trimmed = typed.trim();
|
|
@@ -115,10 +116,8 @@ export function isCompleteSignInCode(typed: string): boolean {
|
|
|
115
116
|
return LONG_CODE.test(normalizeEmailSignInCode(trimmed));
|
|
116
117
|
}
|
|
117
118
|
|
|
118
|
-
/**
|
|
119
|
-
export
|
|
120
|
-
return typed.toUpperCase().replace(/[^0-9A-Z-\s]/g, '').slice(0, EMAIL_SIGNIN_LONG_CODE_LENGTH + 1);
|
|
121
|
-
}
|
|
119
|
+
/** Characters in a backup code (`xxxxx-xxxxx`, its dash not counted). */
|
|
120
|
+
export const BACKUP_CODE_LENGTH = 10;
|
|
122
121
|
|
|
123
122
|
/** One labelled input, the way the sign-in screen draws its fields. */
|
|
124
123
|
export const AccountFlowField: React.FC<{
|
|
@@ -129,11 +128,10 @@ export const AccountFlowField: React.FC<{
|
|
|
129
128
|
error: string | null;
|
|
130
129
|
disabled?: boolean;
|
|
131
130
|
placeholder?: string;
|
|
132
|
-
autoComplete?: 'username' | 'email' | '
|
|
133
|
-
keyboardType?: 'default' | 'email-address'
|
|
131
|
+
autoComplete?: 'username' | 'email' | 'off' | 'current-password' | 'new-password';
|
|
132
|
+
keyboardType?: 'default' | 'email-address';
|
|
134
133
|
secureTextEntry?: boolean;
|
|
135
134
|
autoFocus?: boolean;
|
|
136
|
-
maxLength?: number;
|
|
137
135
|
hint?: string;
|
|
138
136
|
testID: string;
|
|
139
137
|
}> = ({
|
|
@@ -148,7 +146,6 @@ export const AccountFlowField: React.FC<{
|
|
|
148
146
|
keyboardType,
|
|
149
147
|
secureTextEntry,
|
|
150
148
|
autoFocus = true,
|
|
151
|
-
maxLength,
|
|
152
149
|
hint,
|
|
153
150
|
testID,
|
|
154
151
|
}) => (
|
|
@@ -164,7 +161,6 @@ export const AccountFlowField: React.FC<{
|
|
|
164
161
|
autoComplete={autoComplete}
|
|
165
162
|
keyboardType={keyboardType}
|
|
166
163
|
secureTextEntry={secureTextEntry}
|
|
167
|
-
maxLength={maxLength}
|
|
168
164
|
autoCapitalize="none"
|
|
169
165
|
autoCorrect={false}
|
|
170
166
|
autoFocus={autoFocus}
|
|
@@ -177,6 +173,57 @@ export const AccountFlowField: React.FC<{
|
|
|
177
173
|
</View>
|
|
178
174
|
);
|
|
179
175
|
|
|
176
|
+
/**
|
|
177
|
+
* A one-time code: Bloom's `InputOtp`, one box per character, named by the
|
|
178
|
+
* field's label (the boxes are the field's `multiple` case). `numeric` codes
|
|
179
|
+
* are the email's 6 digits and the authenticator's; `alphanumeric` ones are
|
|
180
|
+
* the long sign-in code and a backup code, both `XXXXX-XXXXX` — upper-cased,
|
|
181
|
+
* the dash dropped, so a pasted code fills every box.
|
|
182
|
+
*/
|
|
183
|
+
export const AccountFlowCodeField: React.FC<{
|
|
184
|
+
label: string;
|
|
185
|
+
value: string;
|
|
186
|
+
onChange: (value: string) => void;
|
|
187
|
+
/** Every box is filled. */
|
|
188
|
+
onComplete?: (value: string) => void;
|
|
189
|
+
error: string | null;
|
|
190
|
+
disabled?: boolean;
|
|
191
|
+
length: number;
|
|
192
|
+
type?: 'numeric' | 'alphanumeric';
|
|
193
|
+
groupEvery?: number;
|
|
194
|
+
autoFocus?: boolean;
|
|
195
|
+
hint?: string;
|
|
196
|
+
testID: string;
|
|
197
|
+
}> = ({
|
|
198
|
+
label,
|
|
199
|
+
value,
|
|
200
|
+
onChange,
|
|
201
|
+
onComplete,
|
|
202
|
+
error,
|
|
203
|
+
disabled,
|
|
204
|
+
length,
|
|
205
|
+
type = 'numeric',
|
|
206
|
+
groupEvery,
|
|
207
|
+
autoFocus = true,
|
|
208
|
+
hint,
|
|
209
|
+
testID,
|
|
210
|
+
}) => (
|
|
211
|
+
<Field label={label} multiple description={hint} error={error} disabled={disabled}>
|
|
212
|
+
<InputOtp
|
|
213
|
+
length={length}
|
|
214
|
+
type={type}
|
|
215
|
+
groupEvery={groupEvery}
|
|
216
|
+
value={value}
|
|
217
|
+
onChange={onChange}
|
|
218
|
+
onComplete={onComplete}
|
|
219
|
+
invalid={error !== null}
|
|
220
|
+
disabled={disabled}
|
|
221
|
+
autoFocus={autoFocus}
|
|
222
|
+
testID={testID}
|
|
223
|
+
/>
|
|
224
|
+
</Field>
|
|
225
|
+
);
|
|
226
|
+
|
|
180
227
|
/** The screen's one primary action. */
|
|
181
228
|
export const AccountFlowAction: React.FC<{
|
|
182
229
|
label: string;
|
|
@@ -270,22 +317,17 @@ export const EmailCodeStep: React.FC<EmailCodeStepProps> = ({ description, verif
|
|
|
270
317
|
return (
|
|
271
318
|
<OxyAuthScreen>
|
|
272
319
|
<OxyAuthScreenHeader title={t('emailCode.title')} description={description} />
|
|
273
|
-
<
|
|
320
|
+
<AccountFlowCodeField
|
|
274
321
|
label={t('emailCode.label')}
|
|
275
322
|
value={code}
|
|
276
323
|
onChange={(value) => {
|
|
277
|
-
|
|
278
|
-
setCode(digits);
|
|
324
|
+
setCode(value);
|
|
279
325
|
if (error) setError(null);
|
|
280
|
-
if (digits.length === EMAIL_CODE_LENGTH) confirm(digits);
|
|
281
326
|
}}
|
|
282
|
-
|
|
327
|
+
onComplete={confirm}
|
|
283
328
|
error={error}
|
|
284
329
|
disabled={pending}
|
|
285
|
-
|
|
286
|
-
autoComplete="one-time-code"
|
|
287
|
-
keyboardType="number-pad"
|
|
288
|
-
maxLength={EMAIL_CODE_LENGTH}
|
|
330
|
+
length={EMAIL_CODE_LENGTH}
|
|
289
331
|
testID="email-code"
|
|
290
332
|
/>
|
|
291
333
|
{notice ? <Text style={[styles.note, { color: theme.colors.textSecondary }]}>{notice}</Text> : null}
|